James Iter 8 anos atrás
pai
commit
193abed70d

+ 25 - 1
api/os_template_image.py

@@ -41,12 +41,18 @@ def r_create():
     os_template_image = OSTemplateImage()
     os_template_image = OSTemplateImage()
 
 
     args_rules = [
     args_rules = [
-        Rules.OS_TEMPLATE_PROFILE_ID_EXT.value,
+        Rules.LABEL.value,
+        Rules.DESCRIPTION.value,
         Rules.PATH.value,
         Rules.PATH.value,
+        Rules.ICON.value,
+        Rules.OS_TEMPLATE_PROFILE_ID_EXT.value,
         Rules.ACTIVE.value
         Rules.ACTIVE.value
     ]
     ]
 
 
+    os_template_image.label = request.json.get('label')
+    os_template_image.description = request.json.get('description')
     os_template_image.path = request.json.get('path')
     os_template_image.path = request.json.get('path')
+    os_template_image.icon = request.json.get('icon')
     os_template_image.active = request.json.get('active')
     os_template_image.active = request.json.get('active')
     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')
 
 
@@ -86,6 +92,16 @@ def r_update(_id):
         Rules.ID.value
         Rules.ID.value
     ]
     ]
 
 
+    if 'label' in request.json:
+        args_rules.append(
+            Rules.LABEL.value,
+        )
+
+    if 'description' in request.json:
+        args_rules.append(
+            Rules.DESCRIPTION.value,
+        )
+
     if 'path' in request.json:
     if 'path' in request.json:
         args_rules.append(
         args_rules.append(
             Rules.PATH.value,
             Rules.PATH.value,
@@ -96,6 +112,11 @@ def r_update(_id):
             Rules.ACTIVE.value,
             Rules.ACTIVE.value,
         )
         )
 
 
+    if 'icon' in request.json:
+        args_rules.append(
+            Rules.ICON.value,
+        )
+
     if 'os_template_profile_id' in request.json:
     if 'os_template_profile_id' in request.json:
         args_rules.append(
         args_rules.append(
             Rules.OS_TEMPLATE_PROFILE_ID_EXT.value,
             Rules.OS_TEMPLATE_PROFILE_ID_EXT.value,
@@ -113,8 +134,11 @@ def r_update(_id):
         os_template_image.id = request.json.get('id')
         os_template_image.id = request.json.get('id')
 
 
         os_template_image.get()
         os_template_image.get()
+        os_template_image.label = request.json.get('label', os_template_image.label)
+        os_template_image.description = request.json.get('description', os_template_image.description)
         os_template_image.path = request.json.get('path', os_template_image.path)
         os_template_image.path = request.json.get('path', os_template_image.path)
         os_template_image.active = request.json.get('active', os_template_image.active)
         os_template_image.active = request.json.get('active', os_template_image.active)
+        os_template_image.icon = request.json.get('icon', os_template_image.icon)
         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)
 
 

+ 6 - 6
api/os_template_initialize_operate_set.py

@@ -43,12 +43,12 @@ def r_create():
 
 
     args_rules = [
     args_rules = [
         Rules.LABEL.value,
         Rules.LABEL.value,
-        Rules.DESCRIBE.value,
+        Rules.DESCRIPTION.value,
         Rules.ACTIVE.value
         Rules.ACTIVE.value
     ]
     ]
 
 
     os_template_initialize_operate_set.label = request.json.get('label')
     os_template_initialize_operate_set.label = request.json.get('label')
-    os_template_initialize_operate_set.describe = request.json.get('describe')
+    os_template_initialize_operate_set.description = request.json.get('description')
     os_template_initialize_operate_set.active = request.json.get('active')
     os_template_initialize_operate_set.active = request.json.get('active')
 
 
     try:
     try:
@@ -85,9 +85,9 @@ def r_update(_id):
             Rules.LABEL.value,
             Rules.LABEL.value,
         )
         )
 
 
-    if 'describe' in request.json:
+    if 'description' in request.json:
         args_rules.append(
         args_rules.append(
-            Rules.DESCRIBE.value,
+            Rules.DESCRIPTION.value,
         )
         )
 
 
     if 'active' in request.json:
     if 'active' in request.json:
@@ -107,8 +107,8 @@ def r_update(_id):
         os_template_initialize_operate_set.id = request.json.get('id')
         os_template_initialize_operate_set.id = request.json.get('id')
         os_template_initialize_operate_set.get()
         os_template_initialize_operate_set.get()
         os_template_initialize_operate_set.label = request.json.get('label', os_template_initialize_operate_set.label)
         os_template_initialize_operate_set.label = request.json.get('label', os_template_initialize_operate_set.label)
-        os_template_initialize_operate_set.describe = \
-            request.json.get('describe', os_template_initialize_operate_set.describe)
+        os_template_initialize_operate_set.description = \
+            request.json.get('description', os_template_initialize_operate_set.description)
         os_template_initialize_operate_set.active = \
         os_template_initialize_operate_set.active = \
             request.json.get('active', os_template_initialize_operate_set.active)
             request.json.get('active', os_template_initialize_operate_set.active)
 
 

+ 5 - 5
api/os_template_profile.py

@@ -42,7 +42,7 @@ def r_create():
 
 
     args_rules = [
     args_rules = [
         Rules.LABEL.value,
         Rules.LABEL.value,
-        Rules.DESCRIBE.value,
+        Rules.DESCRIPTION.value,
         Rules.OS_TYPE.value,
         Rules.OS_TYPE.value,
         Rules.OS_DISTRO.value,
         Rules.OS_DISTRO.value,
         Rules.OS_MAJOR.value,
         Rules.OS_MAJOR.value,
@@ -55,7 +55,7 @@ def r_create():
     ]
     ]
 
 
     os_template_profile.label = request.json.get('label')
     os_template_profile.label = request.json.get('label')
-    os_template_profile.describe = request.json.get('describe')
+    os_template_profile.description = request.json.get('description')
     os_template_profile.os_type = request.json.get('os_type')
     os_template_profile.os_type = request.json.get('os_type')
     os_template_profile.os_distro = request.json.get('os_distro')
     os_template_profile.os_distro = request.json.get('os_distro')
     os_template_profile.os_major = request.json.get('os_major')
     os_template_profile.os_major = request.json.get('os_major')
@@ -101,9 +101,9 @@ def r_update(_id):
             Rules.LABEL.value,
             Rules.LABEL.value,
         )
         )
 
 
-    if 'describe' in request.json:
+    if 'description' in request.json:
         args_rules.append(
         args_rules.append(
-            Rules.DESCRIBE.value,
+            Rules.DESCRIPTION.value,
         )
         )
 
 
     if 'os_type' in request.json:
     if 'os_type' in request.json:
@@ -164,7 +164,7 @@ def r_update(_id):
 
 
         os_template_profile.get()
         os_template_profile.get()
         os_template_profile.label = request.json.get('label', os_template_profile.label)
         os_template_profile.label = request.json.get('label', os_template_profile.label)
-        os_template_profile.describe = request.json.get('describe', os_template_profile.describe)
+        os_template_profile.description = request.json.get('description', os_template_profile.description)
         os_template_profile.os_type = request.json.get('os_type', os_template_profile.os_type)
         os_template_profile.os_type = request.json.get('os_type', os_template_profile.os_type)
         os_template_profile.os_distro = request.json.get('os_distro', os_template_profile.os_distro)
         os_template_profile.os_distro = request.json.get('os_distro', os_template_profile.os_distro)
         os_template_profile.os_major = request.json.get('os_major', os_template_profile.os_major)
         os_template_profile.os_major = request.json.get('os_major', os_template_profile.os_major)

+ 28 - 0
main.py

@@ -25,12 +25,22 @@ from models import Database as db
 from models import Config
 from models import Config
 from models import User
 from models import User
 from api.user import blueprint as user_blueprint
 from api.user import blueprint as user_blueprint
+
 from api.boot_job import blueprint as boot_job_blueprint
 from api.boot_job import blueprint as boot_job_blueprint
 from api.boot_job import blueprints as boot_job_blueprints
 from api.boot_job import blueprints as boot_job_blueprints
 from api.operate_rule import blueprint as operate_rule_blueprint
 from api.operate_rule import blueprint as operate_rule_blueprint
 from api.operate_rule import blueprints as operate_rule_blueprints
 from api.operate_rule import blueprints as operate_rule_blueprints
 from api.os_template import blueprint as os_template_blueprint
 from api.os_template import blueprint as os_template_blueprint
 from api.os_template import blueprints as os_template_blueprints
 from api.os_template import blueprints as os_template_blueprints
+
+from api.os_template_image import blueprint as os_template_image_blueprint
+from api.os_template_image import blueprints as os_template_image_blueprints
+from api.os_template_initialize_operate_set import blueprint as os_template_initialize_operate_set_blueprint
+from api.os_template_initialize_operate_set import blueprints as os_template_initialize_operate_set_blueprints
+from api.os_template_initialize_operate import blueprint as os_template_initialize_operate_blueprint
+from api.os_template_initialize_operate import blueprints as os_template_initialize_operate_blueprints
+from api.os_template_profile import blueprint as os_template_profile_blueprint
+from api.os_template_profile import blueprints as os_template_profile_blueprints
 from api.guest import blueprint as guest_blueprint
 from api.guest import blueprint as guest_blueprint
 from api.guest import blueprints as guest_blueprints
 from api.guest import blueprints as guest_blueprints
 from api.disk import blueprint as disk_blueprint
 from api.disk import blueprint as disk_blueprint
@@ -55,12 +65,16 @@ from views.disk import blueprint as view_disk_blueprint
 from views.disk import blueprints as view_disk_blueprints
 from views.disk import blueprints as view_disk_blueprints
 from views.log import blueprint as view_log_blueprint
 from views.log import blueprint as view_log_blueprint
 from views.log import blueprints as view_log_blueprints
 from views.log import blueprints as view_log_blueprints
+from views.os_template_image import blueprint as view_os_template_image_blueprint
+from views.os_template_image import blueprints as view_os_template_image_blueprints
+
 from views.os_template import blueprint as view_os_template_blueprint
 from views.os_template import blueprint as view_os_template_blueprint
 from views.os_template import blueprints as view_os_template_blueprints
 from views.os_template import blueprints as view_os_template_blueprints
 from views.boot_job import blueprint as view_boot_job_blueprint
 from views.boot_job import blueprint as view_boot_job_blueprint
 from views.boot_job import blueprints as view_boot_job_blueprints
 from views.boot_job import blueprints as view_boot_job_blueprints
 from views.operate_rule import blueprint as view_operate_rule_blueprint
 from views.operate_rule import blueprint as view_operate_rule_blueprint
 from views.operate_rule import blueprints as view_operate_rule_blueprints
 from views.operate_rule import blueprints as view_operate_rule_blueprints
+
 from views.host import blueprint as view_host_blueprint
 from views.host import blueprint as view_host_blueprint
 from views.host import blueprints as view_host_blueprints
 from views.host import blueprints as view_host_blueprints
 
 
@@ -211,12 +225,22 @@ try:
     db.init_conn_redis()
     db.init_conn_redis()
 
 
     app.register_blueprint(user_blueprint)
     app.register_blueprint(user_blueprint)
+
     app.register_blueprint(boot_job_blueprint)
     app.register_blueprint(boot_job_blueprint)
     app.register_blueprint(boot_job_blueprints)
     app.register_blueprint(boot_job_blueprints)
     app.register_blueprint(operate_rule_blueprint)
     app.register_blueprint(operate_rule_blueprint)
     app.register_blueprint(operate_rule_blueprints)
     app.register_blueprint(operate_rule_blueprints)
     app.register_blueprint(os_template_blueprint)
     app.register_blueprint(os_template_blueprint)
     app.register_blueprint(os_template_blueprints)
     app.register_blueprint(os_template_blueprints)
+
+    app.register_blueprint(os_template_image_blueprint)
+    app.register_blueprint(os_template_image_blueprints)
+    app.register_blueprint(os_template_initialize_operate_set_blueprint)
+    app.register_blueprint(os_template_initialize_operate_set_blueprints)
+    app.register_blueprint(os_template_initialize_operate_blueprint)
+    app.register_blueprint(os_template_initialize_operate_blueprints)
+    app.register_blueprint(os_template_profile_blueprint)
+    app.register_blueprint(os_template_profile_blueprints)
     app.register_blueprint(guest_blueprint)
     app.register_blueprint(guest_blueprint)
     app.register_blueprint(guest_blueprints)
     app.register_blueprint(guest_blueprints)
     app.register_blueprint(disk_blueprint)
     app.register_blueprint(disk_blueprint)
@@ -240,12 +264,16 @@ try:
     app.register_blueprint(view_disk_blueprints)
     app.register_blueprint(view_disk_blueprints)
     app.register_blueprint(view_log_blueprint)
     app.register_blueprint(view_log_blueprint)
     app.register_blueprint(view_log_blueprints)
     app.register_blueprint(view_log_blueprints)
+    app.register_blueprint(view_os_template_image_blueprint)
+    app.register_blueprint(view_os_template_image_blueprints)
+
     app.register_blueprint(view_os_template_blueprint)
     app.register_blueprint(view_os_template_blueprint)
     app.register_blueprint(view_os_template_blueprints)
     app.register_blueprint(view_os_template_blueprints)
     app.register_blueprint(view_boot_job_blueprint)
     app.register_blueprint(view_boot_job_blueprint)
     app.register_blueprint(view_boot_job_blueprints)
     app.register_blueprint(view_boot_job_blueprints)
     app.register_blueprint(view_operate_rule_blueprint)
     app.register_blueprint(view_operate_rule_blueprint)
     app.register_blueprint(view_operate_rule_blueprints)
     app.register_blueprint(view_operate_rule_blueprints)
+
     app.register_blueprint(view_host_blueprint)
     app.register_blueprint(view_host_blueprint)
     app.register_blueprint(view_host_blueprints)
     app.register_blueprint(view_host_blueprints)
 
 

+ 17 - 14
misc/init.sql

@@ -115,9 +115,12 @@ ALTER TABLE disk ADD INDEX (remark);
 -- 操作系统模板镜像
 -- 操作系统模板镜像
 CREATE TABLE IF NOT EXISTS os_template_image(
 CREATE TABLE IF NOT EXISTS os_template_image(
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
-    os_template_profile_id BIGINT UNSIGNED NOT NULL,
+    label VARCHAR(255) NOT NULL,
+    description TEXT,
     path VARCHAR(255) NOT NULL,
     path VARCHAR(255) NOT NULL,
     active BOOLEAN NOT NULL DEFAULT TRUE,
     active BOOLEAN NOT NULL DEFAULT TRUE,
+    icon VARCHAR(255) NOT NULL,
+    os_template_profile_id BIGINT UNSIGNED NOT NULL,
     PRIMARY KEY (id))
     PRIMARY KEY (id))
     ENGINE=InnoDB
     ENGINE=InnoDB
     DEFAULT CHARSET=utf8;
     DEFAULT CHARSET=utf8;
@@ -127,7 +130,7 @@ CREATE TABLE IF NOT EXISTS os_template_image(
 CREATE TABLE IF NOT EXISTS os_template_profile(
 CREATE TABLE IF NOT EXISTS os_template_profile(
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
     label VARCHAR(255) NOT NULL,
     label VARCHAR(255) NOT NULL,
-    describe TEXT NOT NULL DEFAULT '',
+    description TEXT,
     -- http://libguestfs.org/guestfish.1.html#inspect-get-type
     -- http://libguestfs.org/guestfish.1.html#inspect-get-type
     os_type VARCHAR(10) NOT NULL,
     os_type VARCHAR(10) NOT NULL,
     -- http://libguestfs.org/guestfish.1.html#inspect-get-distro
     -- http://libguestfs.org/guestfish.1.html#inspect-get-distro
@@ -154,7 +157,7 @@ ALTER TABLE os_template_profile ADD INDEX (os_product_name);
 CREATE TABLE IF NOT EXISTS os_template_initialize_operate_set(
 CREATE TABLE IF NOT EXISTS os_template_initialize_operate_set(
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
     label VARCHAR(255) NOT NULL,
     label VARCHAR(255) NOT NULL,
-    describe TEXT NOT NULL DEFAULT '',
+    description TEXT,
     active BOOLEAN NOT NULL DEFAULT TRUE,
     active BOOLEAN NOT NULL DEFAULT TRUE,
     PRIMARY KEY (id))
     PRIMARY KEY (id))
     ENGINE=InnoDB
     ENGINE=InnoDB
@@ -170,8 +173,8 @@ CREATE TABLE IF NOT EXISTS os_template_initialize_operate(
     kind TINYINT UNSIGNED NOT NULL DEFAULT 0,
     kind TINYINT UNSIGNED NOT NULL DEFAULT 0,
     sequence TINYINT UNSIGNED NOT NULL DEFAULT 0,
     sequence TINYINT UNSIGNED NOT NULL DEFAULT 0,
     path VARCHAR(255) NOT NULL,
     path VARCHAR(255) NOT NULL,
-    content TEXT NOT NULL DEFAULT '',
-    command TEXT NOT NULL DEFAULT '',
+    content TEXT,
+    command TEXT,
     PRIMARY KEY (id))
     PRIMARY KEY (id))
     ENGINE=InnoDB
     ENGINE=InnoDB
     DEFAULT CHARSET=utf8;
     DEFAULT CHARSET=utf8;
@@ -354,10 +357,10 @@ ALTER TABLE host_disk_usage_io ADD INDEX (timestamp);
 ALTER TABLE host_disk_usage_io ADD INDEX (node_id, mountpoint, timestamp);
 ALTER TABLE host_disk_usage_io ADD INDEX (node_id, mountpoint, timestamp);
 
 
 
 
-INSERT INTO os_template_initialize_operate_set (label, describe, active) VALUES ('CentOS-Systemd', '用作 Redhat Systemd 系列的系统初始化。初始化操作依据 CentOS 7 来实现。', 1);
-INSERT INTO os_template_initialize_operate_set (label, describe, active) VALUES ('CentOS-SysV', '用作 Redhat SysV 系列的系统初始化。初始化操作依据 CentOS 6.8 来实现。', 1);
-INSERT INTO os_template_initialize_operate_set (label, describe, active) VALUES ('Gentoo-OpenRC', '用作 Gentoo OpenRC 系列的系统初始化。', 1);
-INSERT INTO os_template_initialize_operate_set (label, describe, active) VALUES ('Windows', '用作 MS-Windows 系列的系统初始化。初始化操作依据 Windows 2012 来实现。', 1);
+INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('CentOS-Systemd', '用作 Redhat Systemd 系列的系统初始化。初始化操作依据 CentOS 7 来实现。', 1);
+INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('CentOS-SysV', '用作 Redhat SysV 系列的系统初始化。初始化操作依据 CentOS 6.8 来实现。', 1);
+INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('Gentoo-OpenRC', '用作 Gentoo OpenRC 系列的系统初始化。', 1);
+INSERT INTO os_template_initialize_operate_set (label, descriptione, active) VALUES ('Windows', '用作 MS-Windows 系列的系统初始化。初始化操作依据 Windows 2012 来实现。', 1);
 
 
 -- For CentOS-Systemd
 -- For CentOS-Systemd
 INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (1, 1, 0, '/etc/resolv.conf', 'nameserver {DNS1}
 INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (1, 1, 0, '/etc/resolv.conf', 'nameserver {DNS1}
@@ -413,11 +416,11 @@ timeout 2 > NUL
 shutdown -r -t 0', '');
 shutdown -r -t 0', '');
 
 
 
 
-INSERT INTO os_template_profile (label, describe, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
+INSERT INTO os_template_profile (label, description, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
 VALUES ('CentOS-7.4', 'CentOS 7.4。', 'linux', 'centos', 7, 4, 'x86_64', 'CentOS Linux release 7.4.1708 (Core)', 1, 'icon-os icon-os-centos', 1);
 VALUES ('CentOS-7.4', 'CentOS 7.4。', 'linux', 'centos', 7, 4, 'x86_64', 'CentOS Linux release 7.4.1708 (Core)', 1, 'icon-os icon-os-centos', 1);
-INSERT INTO os_template_profile (label, describe, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
+INSERT INTO os_template_profile (label, description, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
 VALUES ('CentOS-6.8', 'CentOS 6.8。', 'linux', 'centos', 6, 8, 'x86_64', 'CentOS release 6.8 (Final)', 1, 'icon-os icon-os-centos', 2);
 VALUES ('CentOS-6.8', 'CentOS 6.8。', 'linux', 'centos', 6, 8, 'x86_64', 'CentOS release 6.8 (Final)', 1, 'icon-os icon-os-centos', 2);
-INSERT INTO os_template_profile (label, describe, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
+INSERT INTO os_template_profile (label, description, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
 VALUES ('Gentoo-2.2', 'Gentoo 2.2。', 'linux', 'gentoo', 2, 2, 'x86_64', 'Gentoo Base System release 2.2', 1, 'icon-os icon-os-gentoo', 3);
 VALUES ('Gentoo-2.2', 'Gentoo 2.2。', 'linux', 'gentoo', 2, 2, 'x86_64', 'Gentoo Base System release 2.2', 1, 'icon-os icon-os-gentoo', 3);
-INSERT INTO os_template_profile (label, describe, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
-VALUES ('Windows-2012-R2-Standard', 'Windows 2012 R2 Standard。', 'windows', 'windows', 6, 3, 'x86_64', 'Windows Server 2012 R2 Standard', 1, 'icon-os icon-os-gentoo', 4);
+INSERT INTO os_template_profile (label, description, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
+VALUES ('Windows-2012-R2-Standard', 'Windows 2012 R2 Standard。', 'windows', 'windows', 6, 3, 'x86_64', 'Windows Server 2012 R2 Standard', 1, 'icon-os icon-os-windows', 4);

+ 139 - 0
misc/v0.2_to_v0.3/update.sql

@@ -0,0 +1,139 @@
+
+
+-- 操作系统模板镜像
+CREATE TABLE IF NOT EXISTS os_template_image(
+    label VARCHAR(255) NOT NULL,
+    description TEXT,
+    path VARCHAR(255) NOT NULL,
+    active BOOLEAN NOT NULL DEFAULT TRUE,
+    icon VARCHAR(255) NOT NULL,
+    os_template_profile_id BIGINT UNSIGNED NOT NULL,
+    PRIMARY KEY (id))
+    ENGINE=InnoDB
+    DEFAULT CHARSET=utf8;
+
+
+-- 操作系统模板描述文件
+CREATE TABLE IF NOT EXISTS os_template_profile(
+    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+    label VARCHAR(255) NOT NULL,
+    description TEXT,
+    -- http://libguestfs.org/guestfish.1.html#inspect-get-type
+    os_type VARCHAR(10) NOT NULL,
+    -- http://libguestfs.org/guestfish.1.html#inspect-get-distro
+    os_distro VARCHAR(20) NOT NULL,
+    os_major TINYINT UNSIGNED NOT NULL,
+    os_minor TINYINT UNSIGNED NOT NULL,
+    -- http://libguestfs.org/guestfish.1.html#inspect-get-arch  http://libguestfs.org/guestfish.1.html#file-architecture
+    os_arch VARCHAR(10) NOT NULL,
+    os_product_name VARCHAR(255) NOT NULL,
+    active BOOLEAN NOT NULL DEFAULT TRUE,
+    icon VARCHAR(255) NOT NULL,
+    os_template_initialize_operate_set_id BIGINT UNSIGNED NOT NULL,
+    PRIMARY KEY (id))
+    ENGINE=InnoDB
+    DEFAULT CHARSET=utf8;
+
+ALTER TABLE os_template_profile ADD INDEX (label);
+ALTER TABLE os_template_profile ADD INDEX (os_type);
+ALTER TABLE os_template_profile ADD INDEX (os_distro);
+ALTER TABLE os_template_profile ADD INDEX (os_product_name);
+
+
+-- 操作系统模板初始化操作集
+CREATE TABLE IF NOT EXISTS os_template_initialize_operate_set(
+    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+    label VARCHAR(255) NOT NULL,
+    description TEXT,
+    active BOOLEAN NOT NULL DEFAULT TRUE,
+    PRIMARY KEY (id))
+    ENGINE=InnoDB
+    DEFAULT CHARSET=utf8;
+
+ALTER TABLE os_template_initialize_operate_set ADD INDEX (label);
+
+
+-- 操作系统模板初始化操作细则
+CREATE TABLE IF NOT EXISTS os_template_initialize_operate(
+    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+    os_template_initialize_operate_set_id BIGINT UNSIGNED NOT NULL,
+    kind TINYINT UNSIGNED NOT NULL DEFAULT 0,
+    sequence TINYINT UNSIGNED NOT NULL DEFAULT 0,
+    path VARCHAR(255) NOT NULL,
+    content TEXT,
+    command TEXT,
+    PRIMARY KEY (id))
+    ENGINE=InnoDB
+    DEFAULT CHARSET=utf8;
+
+ALTER TABLE os_template_initialize_operate ADD INDEX (os_template_initialize_operate_set_id);
+
+
+
+INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('CentOS-Systemd', '用作 Redhat Systemd 系列的系统初始化。初始化操作依据 CentOS 7 来实现。', 1);
+INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('CentOS-SysV', '用作 Redhat SysV 系列的系统初始化。初始化操作依据 CentOS 6.8 来实现。', 1);
+INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('Gentoo-OpenRC', '用作 Gentoo OpenRC 系列的系统初始化。', 1);
+INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('Windows', '用作 MS-Windows 系列的系统初始化。初始化操作依据 Windows 2012 来实现。', 1);
+
+-- For CentOS-Systemd
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (1, 1, 0, '/etc/resolv.conf', 'nameserver {DNS1}
+nameserver {DNS2}', '');
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (1, 1, 1, '/etc/sysconfig/network-scripts/ifcfg-eth0', 'DEVICE=eth0
+TYPE=Ethernet
+ONBOOT=yes
+BOOTPROTO="static"
+IPADDR={IP}
+NETMASK={NETMASK}
+GATEWAY={GATEWAY}
+DNS1={DNS1}
+DNS2={DNS2}
+IPV6INIT=no
+NAME=eth0', '');
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (1, 1, 2, '/etc/hostname', '{HOSTNAME}', '');
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (1, 0, 3, '', '', 'echo "root:{PASSWORD}" | chpasswd');
+
+-- For CentOS-SysV
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (2, 1, 0, '/etc/resolv.conf', 'nameserver {DNS1}
+nameserver {DNS2}', '');
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (2, 1, 1, '/etc/sysconfig/network-scripts/ifcfg-eth0', 'DEVICE=eth0
+TYPE=Ethernet
+ONBOOT=yes
+BOOTPROTO="static"
+IPADDR={IP}
+NETMASK={NETMASK}
+GATEWAY={GATEWAY}
+IPV6INIT=no
+NAME=eth0', '');
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (2, 1, 2, '/etc/sysconfig/network', 'NETWORKING=yes
+HOSTNAME="{HOSTNAME}"', '');
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (2, 0, 3, '', '', 'echo "root:{PASSWORD}" | chpasswd');
+
+-- For Gentoo-OpenRC
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (3, 1, 0, '/etc/resolv.conf', 'nameserver {DNS1}
+nameserver {DNS2}', '');
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (3, 1, 1, '/etc/conf.d/net', 'config_eth0="{IP}/{NETMASK}"
+routes_eth0="default via {GATEWAY}"', '');
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (3, 1, 2, '/etc/conf.d/hostname', 'hostname="{HOSTNAME}"', '');
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (3, 0, 3, '', '', 'echo "root:{PASSWORD}" | chpasswd');
+
+-- For Windows
+INSERT INTO os_template_initialize_operate (os_template_initialize_operate_set_id, kind, sequence, path, content, command) VALUES (4, 1, 0, '/Windows/jimv_init.bat', 'netsh interface ip set address name="Ethernet" source=static {IP} {NETMASK} {GATEWAY}
+netsh interface ip set dns "Ethernet" static {DNS1} primary
+netsh interface ip add dns "Ethernet" {DNS2}
+wmic computersystem where name="%COMPUTERNAME%" call rename name="{HOSTNAME}"
+net user Administrator {PASSWORD}
+timeout 3 > NUL
+sc delete JimVInit
+del C:\\Windows\\jimv_init.bat
+timeout 2 > NUL
+shutdown -r -t 0', '');
+
+
+INSERT INTO os_template_profile (label, description, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
+VALUES ('CentOS-7.4', 'CentOS 7.4。', 'linux', 'centos', 7, 4, 'x86_64', 'CentOS Linux release 7.4.1708 (Core)', 1, 'icon-os icon-os-centos', 1);
+INSERT INTO os_template_profile (label, description, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
+VALUES ('CentOS-6.8', 'CentOS 6.8。', 'linux', 'centos', 6, 8, 'x86_64', 'CentOS release 6.8 (Final)', 1, 'icon-os icon-os-centos', 2);
+INSERT INTO os_template_profile (label, description, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
+VALUES ('Gentoo-2.2', 'Gentoo 2.2。', 'linux', 'gentoo', 2, 2, 'x86_64', 'Gentoo Base System release 2.2', 1, 'icon-os icon-os-gentoo', 3);
+INSERT INTO os_template_profile (label, description, os_type, os_distro, os_major, os_minor, os_arch, os_product_name, active, icon, os_template_initialize_operate_set_id)
+VALUES ('Windows-2012-R2-Standard', 'Windows 2012 R2 Standard。', 'windows', 'windows', 6, 3, 'x86_64', 'Windows Server 2012 R2 Standard', 1, 'icon-os icon-os-windows', 4);

+ 7 - 2
models/os_template_image.py

@@ -20,6 +20,9 @@ class OSTemplateImage(ORM):
     def __init__(self):
     def __init__(self):
         super(OSTemplateImage, self).__init__()
         super(OSTemplateImage, self).__init__()
         self.id = 0
         self.id = 0
+        self.label = None
+        self.description = None
+        self.icon = None
         self.os_template_profile_id = None
         self.os_template_profile_id = None
         self.path = None
         self.path = None
         self.active = True
         self.active = True
@@ -28,8 +31,9 @@ class OSTemplateImage(ORM):
     def get_filter_keywords():
     def get_filter_keywords():
         return {
         return {
             'id': FilterFieldType.INT.value,
             'id': FilterFieldType.INT.value,
-            'os_template_profile_id': FilterFieldType.INT.value,
+            'label': FilterFieldType.STR.value,
             'path': FilterFieldType.STR.value,
             'path': FilterFieldType.STR.value,
+            'os_template_profile_id': FilterFieldType.INT.value,
             'active': FilterFieldType.INT.value
             'active': FilterFieldType.INT.value
         }
         }
 
 
@@ -39,4 +43,5 @@ class OSTemplateImage(ORM):
 
 
     @staticmethod
     @staticmethod
     def get_allow_content_search_keywords():
     def get_allow_content_search_keywords():
-        return ['path']
+        return ['label', 'path']
+

+ 1 - 1
models/os_template_initialize_operate_set.py

@@ -21,7 +21,7 @@ class OSTemplateInitializeOperateSet(ORM):
         super(OSTemplateInitializeOperateSet, self).__init__()
         super(OSTemplateInitializeOperateSet, self).__init__()
         self.id = 0
         self.id = 0
         self.label = None
         self.label = None
-        self.describe = ''
+        self.description = ''
         self.active = True
         self.active = True
 
 
     @staticmethod
     @staticmethod

+ 1 - 1
models/os_template_profile.py

@@ -21,7 +21,7 @@ class OSTemplateProfile(ORM):
         super(OSTemplateProfile, self).__init__()
         super(OSTemplateProfile, self).__init__()
         self.id = 0
         self.id = 0
         self.label = None
         self.label = None
-        self.describe = ''
+        self.description = ''
         self.os_type = None
         self.os_type = None
         self.os_distro = None
         self.os_distro = None
         self.os_major = None
         self.os_major = None

+ 1 - 1
models/rules.py

@@ -86,7 +86,7 @@ class Rules(Enum):
     REMARK = (basestring, 'remark')
     REMARK = (basestring, 'remark')
     USE_FOR = (int, 'use_for')
     USE_FOR = (int, 'use_for')
     LABEL = (basestring, 'label')
     LABEL = (basestring, 'label')
-    DESCRIBE = (basestring, 'describe')
+    DESCRIPTION = (basestring, 'description')
     OS_TYPE = (basestring, 'os_type')
     OS_TYPE = (basestring, 'os_type')
     OS_DISTRO = (basestring, 'os_distro')
     OS_DISTRO = (basestring, 'os_distro')
     OS_MAJOR = (int, 'os_major')
     OS_MAJOR = (int, 'os_major')

+ 1 - 25
models/utils.py

@@ -276,31 +276,7 @@ def utility_processor():
         return '<span class="{icon}" style="color: #{color};">&nbsp;&nbsp;{desc}</span>'.format(
         return '<span class="{icon}" style="color: #{color};">&nbsp;&nbsp;{desc}</span>'.format(
             icon=icon, color=color, desc=desc)
             icon=icon, color=color, desc=desc)
 
 
-    def format_os_type_id_to_name(os_type):
-        from models import OSType
-        if os_type == OSType.linux.value:
-            return u'Linux'
-
-        elif os_type == OSType.windows.value:
-            return u'Windows'
-
-        elif os_type == OSType.bsd.value:
-            return u'BSD'
-
-        elif os_type == OSType.aix.value:
-            return u'AIX'
-
-        elif os_type == OSType.hp_unix.value:
-            return u'HP-UNIX'
-
-        elif os_type == OSType.unknown.value:
-            return u'Unknown'
-
-        else:
-            return u'Unknown'
-
     return dict(format_price=format_price, format_datetime_by_tus=format_datetime_by_tus,
     return dict(format_price=format_price, format_datetime_by_tus=format_datetime_by_tus,
                 format_datetime_by_ts=format_datetime_by_ts, format_guest_status=format_guest_status,
                 format_datetime_by_ts=format_datetime_by_ts, format_guest_status=format_guest_status,
-                format_sequence_to_device_name=format_sequence_to_device_name, format_disk_state=format_disk_state,
-                format_os_type_id_to_name=format_os_type_id_to_name)
+                format_sequence_to_device_name=format_sequence_to_device_name, format_disk_state=format_disk_state)
 
 

+ 10 - 0
templates/layout.html

@@ -39,6 +39,10 @@
             filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
             filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
         }
         }
 
 
+        .dropdown-menu {
+            min-width: 100% !important;
+        }
+
         @font-face {
         @font-face {
             font-family: 'Baskerville';
             font-family: 'Baskerville';
             src: url('/static/fonts/Baskerville/Baskerville-Italic.eot');
             src: url('/static/fonts/Baskerville/Baskerville-Italic.eot');
@@ -363,6 +367,12 @@
                                 <span>平台日志</span>
                                 <span>平台日志</span>
                             </a>
                             </a>
                         </li>
                         </li>
+                        <li>
+                            <a href="{{ url_for('v_os_templates_image.show') }}" title="虚拟机模板镜像列表">
+                                <i class="glyph-icon icon-linecons-inbox"></i>
+                                <span>虚拟机模板镜像</span>
+                            </a>
+                        </li>
                         <li>
                         <li>
                             <a href="{{ url_for('v_os_templates.show') }}" title="虚拟机模板列表">
                             <a href="{{ url_for('v_os_templates.show') }}" title="虚拟机模板列表">
                                 <i class="glyph-icon icon-linecons-inbox"></i>
                                 <i class="glyph-icon icon-linecons-inbox"></i>

+ 724 - 0
templates/os_templates_image_show.html

@@ -0,0 +1,724 @@
+{% extends "layout.html" %}
+{% block head %}
+    {{ super() }}
+    <style type="text/css">
+
+        @media (min-width: 768px) {
+            .form-horizontal .control-label {
+                text-align: left;
+            }
+        }
+
+        label>span {
+            color: deepskyblue;
+        }
+
+        .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;
+        }
+
+        .btn-shortcut {
+            font-size: 12px !important;
+            padding: 0 26px;
+        }
+
+        .show {
+            display: inline-block !important;
+        }
+
+        .tr-selected td,
+        .tr-selected {
+            color: #000 !important;
+            background: #fafaff !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();
+        });
+
+        $('#edit_label_modal').on('show.bs.modal', function (me) {
+            $('#os_template_image_id').val($(me.relatedTarget).parent().parent().prev().prev().text());
+            $('#edit_label').val($(me.relatedTarget).prev().text());
+        });
+
+        $('#edit_path_modal').on('show.bs.modal', function (me) {
+            $('#os_template_image_id').val($(me.relatedTarget).parent().parent().prev().prev().prev().prev().text());
+            $('#edit_path').val($(me.relatedTarget).prev().text());
+        });
+
+        $('#add_os_template_image_modal').on('show.bs.modal', function (me) {
+            refresh_os_template_boot_job_selectpicker($('#boot_job_id'));
+        });
+
+        $('#update_boot_job_modal').on('show.bs.modal', function (me) {
+            refresh_os_template_boot_job_selectpicker($('#update_boot_job_id'));
+        });
+
+        $('#update_os_type_modal').on('show.bs.modal', function (me) {
+            $('#os_template_image_id').val($(me.relatedTarget).parent().prev().prev().prev().prev().prev().text());
+            $('#update_os_type_instance_desc').text($(me.relatedTarget).parent().prev().prev().prev().text());
+        });
+
+        $('#update_icon_modal').on('show.bs.modal', function (me) {
+            $('#os_template_image_id').val($(me.relatedTarget).parent().prev().prev().prev().prev().prev().prev().text());
+            $('#update_icon_instance_desc').text($(me.relatedTarget).parent().prev().prev().prev().prev().text());
+        });
+
+        $('#add_os_template_image_form').formValidation({
+            framework: 'bootstrap4',
+            icon: {
+                valid: 'fa fa-check',
+                invalid: 'fa fa-times',
+                validating: 'fa fa-refresh'
+            },
+            // Since the Bootstrap Button hides the radio and checkbox
+            // We exclude the disabled elements only
+            excluded: ':disabled',
+            locale: 'zh_CN',
+            fields: {
+                label: {
+                    validators: {
+                        notEmpty: {},
+                        stringLength: {
+                            min: 2,
+                            max: 255
+                        }
+                    }
+                },
+                icon: {
+                    validators: {
+                        notEmpty: {
+                        }
+                    }
+                },
+                path: {
+                    validators: {
+                        notEmpty: {},
+                        stringLength: {
+                            min: 2,
+                            max: 255
+                        }
+                    }
+                },
+                os_type: {
+                    validators: {
+                        notEmpty: {
+                        }
+                    }
+                },
+                boot_job_id: {
+                    validators: {
+                        notEmpty: {
+                        }
+                    }
+                }
+            }
+        })
+        .on('success.field.fv', function(e, data) {
+            if (data.fv.getInvalidFields().length > 0) {    // There is invalid field
+                data.fv.disableSubmitButtons(true);
+            }
+        })
+    });
+
+    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;
+    }
+
+    function row_onmouseover(me) {
+        $(me).find(".edit_label_trigger, .edit_path_trigger").css('display','inline-flex');
+    }
+
+    function row_onmouseout(me) {
+        $(me).find(".edit_label_trigger, .edit_path_trigger").css('display','none');
+    }
+
+    function refresh_os_template_boot_job_selectpicker(selectpicker) {
+        $.ajax({
+            url : '/api/boot_jobs?filter=use_for:eq:0',
+            type : 'GET',
+            contentType: "application/json; charset=utf-8",
+            dataType: 'json',
+            error : function() {
+            },
+            success : function(data, textStatus, xhr) {
+                selectpicker.empty();
+                $.each(data.data, function(k, v) {
+                    selectpicker.append(
+                        $('<option>', {value: v['id'], text: v['name'], 'data-subtext': v['remark']})
+                    );
+                });
+                selectpicker.selectpicker('refresh');
+            }
+        });
+    }
+
+    function label_update(me) {
+        var os_template_image_id = $('#os_template_image_id').val();
+        var label = $('#edit_label').val();
+        $('#edit_label_modal').modal('hide');
+        $.ajax({
+            url : '/api/os_template_image/' + os_template_image_id,
+            type : 'PATCH',
+            contentType: "application/json; charset=utf-8",
+            data : JSON.stringify({
+                label: label
+            }),
+            error : function() {
+                alter_danger('模板镜像名称更新失败!');
+            },
+            success : function() {
+                alter_success('模板镜像名称更新成功!');
+                refresh()
+            }
+        });
+    }
+
+    function path_update(me) {
+        var os_template_image_id = $('#os_template_image_id').val();
+        var path = $('#edit_path').val();
+        $('#edit_path_modal').modal('hide');
+        $.ajax({
+            url : '/api/os_template_image/' + os_template_image_id,
+            type : 'PATCH',
+            contentType: "application/json; charset=utf-8",
+            data : JSON.stringify({
+                path: path
+            }),
+            error : function() {
+                alter_danger('模板镜像路径更新失败!');
+            },
+            success : function() {
+                alter_success('模板镜像路径更新成功!');
+                refresh()
+            }
+        });
+    }
+
+    function get_selected_element(checked) {
+        if (checked === null) {
+            checked = true;
+        }
+
+        if (checked) {
+            return $('tbody :checked');
+        } else {
+
+            return $('tbody input:not(:checked)');
+        }
+    }
+    
+    function highlight_selected_element() {
+        var selected_element = get_selected_element(true);
+        var no_selected_element = get_selected_element(false);
+
+        selected_element.each(function(i, e) {
+            $(e).parent().parent().toggleClass('tr-selected', true);
+        });
+
+        no_selected_element.each(function(i, e) {
+            $(e).parent().parent().toggleClass('tr-selected', false);
+        });
+    }
+    
+    function shortcut_bar_enable() {
+        var selected_element = get_selected_element(true);
+
+        if (selected_element.length > 0) {
+            $('.btn-shortcut').toggleClass('disabled', false);
+        } else {
+            $('.btn-shortcut').toggleClass('disabled', true);
+        }
+    }
+
+    function select_item_action() {
+        highlight_selected_element();
+        shortcut_bar_enable();
+    }
+
+    function enable(id) {
+        $.ajax({
+            url : '/api/os_template_image/' + id,
+            type : 'PATCH',
+            contentType: "application/json; charset=utf-8",
+            data : JSON.stringify({
+                active: true
+            }),
+            error : function() {
+                alter_danger('启用指令发送失败!');
+            },
+            success : function() {
+                alter_success('启用指令发送成功!');
+                refresh();
+            }
+        });
+    }
+
+    function disable(id) {
+        $.ajax({
+            url : '/api/os_template_image/' + id,
+            type : 'PATCH',
+            contentType: "application/json; charset=utf-8",
+            data : JSON.stringify({
+                active: false
+            }),
+            error : function() {
+                alter_danger('禁用指令发送失败!');
+            },
+            success : function() {
+                alter_success('禁用指令发送成功!');
+                refresh();
+            }
+        });
+    }
+
+    function remove(id) {
+        $.ajax({
+            url : '/api/os_templates/' + id,
+            type : 'DELETE',
+            contentType: "application/json; charset=utf-8",
+            error : function() {
+                alter_danger('模板删除指令发送失败!');
+            },
+            success : function() {
+                alter_success('模板删除指令发送成功!');
+                refresh();
+            }
+        });
+    }
+
+    function enable_at(me) {
+        var id = $(me).parent().parent().parent().parent().parent().children()[0].textContent;
+        enable(id);
+    }
+
+    function disable_at(me) {
+        var id = $(me).parent().parent().parent().parent().parent().children()[0].textContent;
+        disable(id);
+    }
+
+    function delete_at(me) {
+        var id = $('#os_template_image_id').val();
+        remove(id);
+        $('#delete_modal').modal('hide');
+    }
+</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>
+                            <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>
+                <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="180px;">名称</th>
+                    <th>状态</th>
+                    <th width="500px;">路径</th>
+                    <th>发行版本</th>
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <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>
+                        {% else %}
+                        <span style="color: #00BB00;">启用</span>
+                        {% 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>
+                            <span class="{{ os_templates_profile_mapping_by_id[item.id].icon }}"></span>
+                            <p style="display: inline-block;">{{ os_templates_profile_mapping_by_id[item.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_boot_job_modal"
+                                        onclick="$('#os_template_image_id').val($(this).parent().parent().parent().parent().parent().children()[0].textContent);
+                                        $('#update_boot_job_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 %}
+                </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>
+                        </th>
+                    </tr>
+                    </tfoot>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>
+
+<input id="os_template_image_id" title="模板 ID" class="form-control" name="os_template_image_id" hidden>
+
+<div class="modal" id="edit_label_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">
+                <input id="edit_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="label_update();">确定</button>
+                <button type="button" class="btn btn-sm btn-default" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="modal" id="edit_path_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title">编辑模板镜像路径:</h4>
+            </div>
+            <div class="modal-body">
+                <input id="edit_path" title="系统模板镜像路径" class="form-control" name="os_template_image_path">
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-primary" onclick="path_update();">确定</button>
+                <button type="button" class="btn btn-sm btn-default" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="modal" id="delete_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title">删除模板:</h4>
+            </div>
+            <div class="modal-body">
+                <p>你确定要删除该模板镜像吗?</p>
+                <h3 style="color: orangered;" id="delete_instance_desc"></h3>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-primary" onclick="delete_at();">确定</button>
+                <button type="button" class="btn btn-sm btn-default" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="modal" id="add_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" style="padding-top: 0; padding-bottom: 0;">
+                <div class="example-box-wrapper">
+                    <form id="add_os_template_image_form" class="form-horizontal bordered-row" action="/os_template_image" method="post">
+                        <div class="form-group">
+                            <div class="col-sm-2"></div>
+                            <label class="col-sm-2 control-label"><span class="glyph-icon icon-elusive-compass-circled"></span>&nbsp;&nbsp;模板镜像名称</label>
+                            <div class="col-sm-6">
+                                <input id="label" name="label" type="text" title="模板镜像名称" class="form-control">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-2"></div>
+                            <label class="col-sm-2 control-label"><span class="glyph-icon icon-bookmark-o"></span>&nbsp;&nbsp;ICON</label>
+                            <div class="col-sm-6">
+                                <select id="icon" name="icon" title="模板 ICON" class="selectpicker">
+                                    <option value="icon-os icon-os-centos" data-icon="icon-os icon-os-centos" selected>CentOS</option>
+                                    <option value="icon-os icon-os-linux" data-icon="icon-os icon-os-linux">Linux</option>
+                                    <option value="icon-os icon-os-ubuntu" data-icon="icon-os icon-os-ubuntu">Ubuntu</option>
+                                    <option value="icon-os icon-os-suse" data-icon="icon-os icon-os-suse">Suse</option>
+                                    <option value="icon-os icon-os-gentoo" data-icon="icon-os icon-os-gentoo">Gentoo</option>
+                                    <option value="icon-os icon-os-debian" data-icon="icon-os icon-os-debian">Debian</option>
+                                    <option value="icon-os icon-os-redhat" data-icon="icon-os icon-os-redhat">Redhat</option>
+                                    <option value="icon-os icon-os-bsd" data-icon="icon-os icon-os-bsd">BSD</option>
+                                    <option value="icon-os icon-os-windows" data-icon="icon-os icon-os-windows">Windows</option>
+                                </select>
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-2"></div>
+                            <label class="col-sm-2 control-label"><span class="glyph-icon icon-elusive-stumbleupon"></span>&nbsp;&nbsp;模板路径</label>
+                            <div class="col-sm-6">
+                                <input id="path" name="path" type="text" title="模板路径" class="form-control">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-2"></div>
+                            <label class="col-sm-2 control-label"><span class="glyph-icon icon-bookmark-o"></span>&nbsp;&nbsp;模板系统类型</label>
+                            <div class="col-sm-6">
+                                <select id="os_type" name="os_type" title="请选择模板系统类型" class="selectpicker">
+                                    <option value="0">Linux</option>
+                                    <option value="1">Windows</option>
+                                    <option value="2">BSD</option>
+                                    <option value="3">AIX</option>
+                                    <option value="4">HP-UNIX</option>
+                                </select>
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-2"></div>
+                            <label class="col-sm-2 control-label"><span class="glyph-icon icon-file-code-o"></span>&nbsp;&nbsp;初始化作业</label>
+                            <div class="col-sm-6">
+                                <select id="boot_job_id" name="boot_job_id" title="实例的启动作业" class="selectpicker">
+                                </select>
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4"></div>
+                            <div class="col-sm-6 pull-right">
+                                <button type="submit" class="btn btn-blue-alt" style="width: 180px; height: 40px; font-size: 16px;" disabled>创建</button>
+                                <button class="btn btn-default" style="width: 64px; height: 40px; font-size: 16px;" data-dismiss="modal">取消</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="modal" id="update_os_type_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title">变更操作系统类型:</h4>
+            </div>
+            <div class="modal-body" style="padding-top: 0;">
+                <form class="form-horizontal bordered-row">
+                    <div class="form-group">
+                        <div class="col-sm-1"></div>
+                        <label class="col-sm-3 control-label"><span class="glyph-icon icon-elusive-compass-circled"></span>&nbsp;&nbsp;操作系统类型</label>
+                        <div class="col-sm-8">
+                            <h3 style="color: orangered;" id="update_os_type_instance_desc"></h3>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <div class="col-sm-1"></div>
+                        <div class="col-sm-8">
+                            <select id="update_os_type" name="os_type" title="操作系统类型" class="selectpicker">
+                                <option value="0" selected>Linux</option>
+                                <option value="1">Windows</option>
+                                <option value="2">BSD</option>
+                                <option value="3">AIX</option>
+                                <option value="4">HP-UNIX</option>
+                            </select>
+                        </div>
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-primary" onclick="update_os_type_at();">确定</button>
+                <button type="button" class="btn btn-sm btn-default" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+{% endblock content %}

+ 1 - 1
templates/os_templates_show.html

@@ -542,7 +542,7 @@
                     </td>
                     </td>
                     <td>
                     <td>
                         <a href="javascript:;" data-toggle="modal" data-target="#update_os_type_modal">
                         <a href="javascript:;" data-toggle="modal" data-target="#update_os_type_modal">
-                            {{ format_os_type_id_to_name(item.os_type) }}
+                            {{ item.os_type }}
                         </a>
                         </a>
                     </td>
                     </td>
                     <td>
                     <td>

+ 133 - 0
views/os_template_image.py

@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+import json
+from flask import Blueprint, render_template, url_for, request, redirect
+import requests
+from math import ceil
+
+
+__author__ = 'James Iter'
+__date__ = '2018/2/5'
+__contact__ = 'james.iter.cn@gmail.com'
+__copyright__ = '(c) 2018 by James Iter.'
+
+
+blueprint = Blueprint(
+    'v_os_template_image',
+    __name__,
+    url_prefix='/os_template_image'
+)
+
+blueprints = Blueprint(
+    'v_os_templates_image',
+    __name__,
+    url_prefix='/os_templates_image'
+)
+
+
+def show():
+    args = list()
+    page = int(request.args.get('page', 1))
+    page_size = int(request.args.get('page_size', 10))
+    keyword = request.args.get('keyword', None)
+    order_by = request.args.get('order_by', None)
+    order = request.args.get('order', None)
+    filters = list()
+
+    if page is not None:
+        args.append('page=' + page.__str__())
+
+    if page_size is not None:
+        args.append('page_size=' + page_size.__str__())
+
+    if keyword is not None:
+        args.append('keyword=' + keyword.__str__())
+
+    if order_by is not None:
+        args.append('order_by=' + order_by)
+
+    if order is not None:
+        args.append('order=' + order)
+
+    if filters.__len__() > 0:
+        args.append('filter=' + ','.join(filters))
+
+    host_url = request.host_url.rstrip('/')
+
+    os_templates_image_url = host_url + url_for('api_os_templates_image.r_get_by_filter')
+    if keyword is not None:
+        os_templates_image_url = host_url + url_for('api_os_templates_image.r_content_search')
+
+    os_templates_profile_url = host_url + url_for('api_os_templates_profile.r_get_by_filter')
+
+    if args.__len__() > 0:
+        os_templates_image_url = os_templates_image_url + '?' + '&'.join(args)
+
+    os_templates_image_ret = requests.get(url=os_templates_image_url, cookies=request.cookies)
+    os_templates_image_ret = json.loads(os_templates_image_ret.content)
+
+    os_templates_profile_ret = requests.get(url=os_templates_profile_url, cookies=request.cookies)
+    os_templates_profile_ret = json.loads(os_templates_profile_ret.content)
+    os_templates_profile_mapping_by_id = dict()
+    for os_template_profile in os_templates_profile_ret['data']:
+        os_templates_profile_mapping_by_id[os_template_profile['id']] = os_template_profile
+
+    last_page = int(ceil(os_templates_image_ret['paging']['total'] / float(page_size)))
+    page_length = 5
+    pages = list()
+    if page < int(ceil(page_length / 2.0)):
+        for i in range(1, page_length + 1):
+            pages.append(i)
+            if i == last_page or last_page == 0:
+                break
+
+    elif last_page - page < page_length / 2:
+        for i in range(last_page - page_length + 1, last_page + 1):
+            if i < 1:
+                continue
+            pages.append(i)
+
+    else:
+        for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
+            pages.append(i)
+            if i == last_page or last_page == 0:
+                break
+
+    return render_template('os_templates_image_show.html', os_templates_image_ret=os_templates_image_ret,
+                           os_templates_profile_mapping_by_id=os_templates_profile_mapping_by_id,
+                           page=page, page_size=page_size, keyword=keyword, pages=pages, order_by=order_by, order=order,
+                           last_page=last_page)
+
+
+def create():
+    host_url = request.host_url.rstrip('/')
+
+    if request.method == 'POST':
+        label = request.form.get('label')
+        description = request.form.get('description')
+        path = request.form.get('path')
+        icon = request.form.get('icon')
+        active = request.form.get('active')
+        os_template_profile_id = request.form.get('os_template_profile_id')
+
+        payload = {
+            "label": label,
+            "description": description,
+            "path": path,
+            "icon": icon,
+            "active": active,
+            "os_template_profile_id": int(os_template_profile_id)
+        }
+
+        url = host_url + '/api/os_template_image'
+        headers = {'content-type': 'application/json'}
+        r = requests.post(url, data=json.dumps(payload), headers=headers, cookies=request.cookies)
+        j_r = json.loads(r.content)
+        return render_template('success.html', go_back_url='/os_templates_image', timeout=10000, title='提交成功',
+                               message_title='添加模板镜像的请求已被接受',
+                               message='您所提交的模板镜像已创建。页面将在10秒钟后自动跳转到模板列表页面!')
+
+    else:
+        return redirect(url_for('v_os_templates_image.show'))

+ 12 - 1
views_route_table.py

@@ -3,7 +3,15 @@
 
 
 
 
 from models.utils import add_rule_views
 from models.utils import add_rule_views
-from views import guest, disk, log, os_template, boot_job, operate_rule, host, dashboard, config, misc
+from views import guest
+from views import disk
+from views import log
+from views import host
+from views import dashboard
+from views import config
+from views import misc
+from views import os_template_image
+from views import os_template, boot_job, operate_rule
 
 
 
 
 __author__ = 'James Iter'
 __author__ = 'James Iter'
@@ -36,6 +44,9 @@ add_rule_views(disk.blueprint, '/detail/<uuid>', views_func='disk.detail', metho
 
 
 add_rule_views(log.blueprints, '', views_func='log.show', methods=['GET'])
 add_rule_views(log.blueprints, '', views_func='log.show', methods=['GET'])
 
 
+add_rule_views(os_template_image.blueprints, '', views_func='os_template_image.show', methods=['GET'])
+add_rule_views(os_template_image.blueprint, '', views_func='os_template_image.create', methods=['POST'])
+
 add_rule_views(os_template.blueprints, '', views_func='os_template.show', methods=['GET'])
 add_rule_views(os_template.blueprints, '', views_func='os_template.show', methods=['GET'])
 add_rule_views(os_template.blueprint, '', views_func='os_template.create', methods=['POST'])
 add_rule_views(os_template.blueprint, '', views_func='os_template.create', methods=['POST'])