James Iter пре 8 година
родитељ
комит
193abed70d

+ 25 - 1
api/os_template_image.py

@@ -41,12 +41,18 @@ def r_create():
     os_template_image = OSTemplateImage()
 
     args_rules = [
-        Rules.OS_TEMPLATE_PROFILE_ID_EXT.value,
+        Rules.LABEL.value,
+        Rules.DESCRIPTION.value,
         Rules.PATH.value,
+        Rules.ICON.value,
+        Rules.OS_TEMPLATE_PROFILE_ID_EXT.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.icon = request.json.get('icon')
     os_template_image.active = request.json.get('active')
     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
     ]
 
+    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:
         args_rules.append(
             Rules.PATH.value,
@@ -96,6 +112,11 @@ def r_update(_id):
             Rules.ACTIVE.value,
         )
 
+    if 'icon' in request.json:
+        args_rules.append(
+            Rules.ICON.value,
+        )
+
     if 'os_template_profile_id' in request.json:
         args_rules.append(
             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.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.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 = \
             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 = [
         Rules.LABEL.value,
-        Rules.DESCRIBE.value,
+        Rules.DESCRIPTION.value,
         Rules.ACTIVE.value
     ]
 
     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')
 
     try:
@@ -85,9 +85,9 @@ def r_update(_id):
             Rules.LABEL.value,
         )
 
-    if 'describe' in request.json:
+    if 'description' in request.json:
         args_rules.append(
-            Rules.DESCRIBE.value,
+            Rules.DESCRIPTION.value,
         )
 
     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.get()
         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 = \
             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 = [
         Rules.LABEL.value,
-        Rules.DESCRIBE.value,
+        Rules.DESCRIPTION.value,
         Rules.OS_TYPE.value,
         Rules.OS_DISTRO.value,
         Rules.OS_MAJOR.value,
@@ -55,7 +55,7 @@ def r_create():
     ]
 
     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_distro = request.json.get('os_distro')
     os_template_profile.os_major = request.json.get('os_major')
@@ -101,9 +101,9 @@ def r_update(_id):
             Rules.LABEL.value,
         )
 
-    if 'describe' in request.json:
+    if 'description' in request.json:
         args_rules.append(
-            Rules.DESCRIBE.value,
+            Rules.DESCRIPTION.value,
         )
 
     if 'os_type' in request.json:
@@ -164,7 +164,7 @@ def r_update(_id):
 
         os_template_profile.get()
         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_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)

+ 28 - 0
main.py

@@ -25,12 +25,22 @@ from models import Database as db
 from models import Config
 from models import User
 from api.user import blueprint as user_blueprint
+
 from api.boot_job import blueprint as boot_job_blueprint
 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 blueprints as operate_rule_blueprints
 from api.os_template import blueprint as os_template_blueprint
 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 blueprints as guest_blueprints
 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.log import blueprint as view_log_blueprint
 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 blueprints as view_os_template_blueprints
 from views.boot_job import blueprint as view_boot_job_blueprint
 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 blueprints as view_operate_rule_blueprints
+
 from views.host import blueprint as view_host_blueprint
 from views.host import blueprints as view_host_blueprints
 
@@ -211,12 +225,22 @@ try:
     db.init_conn_redis()
 
     app.register_blueprint(user_blueprint)
+
     app.register_blueprint(boot_job_blueprint)
     app.register_blueprint(boot_job_blueprints)
     app.register_blueprint(operate_rule_blueprint)
     app.register_blueprint(operate_rule_blueprints)
     app.register_blueprint(os_template_blueprint)
     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_blueprints)
     app.register_blueprint(disk_blueprint)
@@ -240,12 +264,16 @@ try:
     app.register_blueprint(view_disk_blueprints)
     app.register_blueprint(view_log_blueprint)
     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_blueprints)
     app.register_blueprint(view_boot_job_blueprint)
     app.register_blueprint(view_boot_job_blueprints)
     app.register_blueprint(view_operate_rule_blueprint)
     app.register_blueprint(view_operate_rule_blueprints)
+
     app.register_blueprint(view_host_blueprint)
     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(
     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,
     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;
@@ -127,7 +130,7 @@ CREATE TABLE IF NOT EXISTS os_template_image(
 CREATE TABLE IF NOT EXISTS os_template_profile(
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
     label VARCHAR(255) NOT NULL,
-    describe TEXT NOT NULL DEFAULT '',
+    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
@@ -154,7 +157,7 @@ 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,
-    describe TEXT NOT NULL DEFAULT '',
+    description TEXT,
     active BOOLEAN NOT NULL DEFAULT TRUE,
     PRIMARY KEY (id))
     ENGINE=InnoDB
@@ -170,8 +173,8 @@ CREATE TABLE IF NOT EXISTS os_template_initialize_operate(
     kind TINYINT UNSIGNED NOT NULL DEFAULT 0,
     sequence TINYINT UNSIGNED NOT NULL DEFAULT 0,
     path VARCHAR(255) NOT NULL,
-    content TEXT NOT NULL DEFAULT '',
-    command TEXT NOT NULL DEFAULT '',
+    content TEXT,
+    command TEXT,
     PRIMARY KEY (id))
     ENGINE=InnoDB
     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);
 
 
-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
 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', '');
 
 
-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);
-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);
-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);
-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):
         super(OSTemplateImage, self).__init__()
         self.id = 0
+        self.label = None
+        self.description = None
+        self.icon = None
         self.os_template_profile_id = None
         self.path = None
         self.active = True
@@ -28,8 +31,9 @@ class OSTemplateImage(ORM):
     def get_filter_keywords():
         return {
             'id': FilterFieldType.INT.value,
-            'os_template_profile_id': FilterFieldType.INT.value,
+            'label': FilterFieldType.STR.value,
             'path': FilterFieldType.STR.value,
+            'os_template_profile_id': FilterFieldType.INT.value,
             'active': FilterFieldType.INT.value
         }
 
@@ -39,4 +43,5 @@ class OSTemplateImage(ORM):
 
     @staticmethod
     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__()
         self.id = 0
         self.label = None
-        self.describe = ''
+        self.description = ''
         self.active = True
 
     @staticmethod

+ 1 - 1
models/os_template_profile.py

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

+ 1 - 1
models/rules.py

@@ -86,7 +86,7 @@ class Rules(Enum):
     REMARK = (basestring, 'remark')
     USE_FOR = (int, 'use_for')
     LABEL = (basestring, 'label')
-    DESCRIBE = (basestring, 'describe')
+    DESCRIPTION = (basestring, 'description')
     OS_TYPE = (basestring, 'os_type')
     OS_DISTRO = (basestring, 'os_distro')
     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(
             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,
                 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);
         }
 
+        .dropdown-menu {
+            min-width: 100% !important;
+        }
+
         @font-face {
             font-family: 'Baskerville';
             src: url('/static/fonts/Baskerville/Baskerville-Italic.eot');
@@ -363,6 +367,12 @@
                                 <span>平台日志</span>
                             </a>
                         </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>
                             <a href="{{ url_for('v_os_templates.show') }}" title="虚拟机模板列表">
                                 <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>
                         <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>
                     </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 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'
@@ -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(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.blueprint, '', views_func='os_template.create', methods=['POST'])