[Kimchi-devel] [PATCH] Adding ability to switch graphics devices on a cold vm, adds serial console option as well

Aline Manera alinefm at linux.vnet.ibm.com
Thu Sep 11 01:57:01 UTC 2014


Some overview:

1. We use 4 spaces for indentation in all files.
     I didn't apply the patch but it seems there are some tabs in it.

2. We use 80 characters limit
     You can run "make check-local" to make sure your code is respecting 
it in other pep8 rules.

3. You need to update docs/API.md to document this new behavior

4. You need to update mockmodel.py
     As its name says it is a mock model used when running Kimchi with 
--test. It used by user to check Kimchi feature without affecting the 
system.

5. Add tests for it.
     Under /tests we have unit tests that are ran by "make check"
     test_rest.py uses mockmodel and test the REST API
     test_model.py test the model itself and so on

6. I suggest spitting this patch in (at least) 2 patches: one for 
backend and other one for UI
     That way the patch is not blocked by some comments on UI and it is 
easy to review small patch =)

7. More comments inline below.


On 09/09/2014 11:14 AM, bbaude at redhat.com wrote:
> From: Brent Baude <bbaude at redhat.com>
>
> This patch allows you to change the graphics type for a cold guest.  It will also allow
> a serial choice for ppc64 machines where a user can then run virsh console to deal with
> low-bandwidth issues.
>
> ---
>   src/kimchi/API.json                    |   5 +-
>   src/kimchi/model/config.py             |   2 +
>   src/kimchi/model/vms.py                | 177 +++++++++++++++++++++++++++------
>   src/kimchi/vmtemplate.py               |   9 ++
>   ui/css/theme-default/guest-edit.css    |   6 ++
>   ui/css/theme-default/storage.css       |  58 -----------
>   ui/js/src/kimchi.guest_edit_main.js    |  30 +++++-
>   ui/js/src/kimchi.guest_main.js         |  23 ++++-
>   ui/js/src/kimchi.template_edit_main.js |   4 +
>   ui/pages/guest-edit.html.tmpl          |  21 ++++
>   ui/pages/guest.html.tmpl               |   5 +-
>   ui/pages/i18n.json.tmpl                |   1 +
>   ui/pages/tabs/storage.html.tmpl        |   4 +-
>   13 files changed, 246 insertions(+), 99 deletions(-)
>
> diff --git a/src/kimchi/API.json b/src/kimchi/API.json
> index 8a95804..335f1fd 100644
> --- a/src/kimchi/API.json
> +++ b/src/kimchi/API.json
> @@ -9,7 +9,7 @@
>               "type": "object",
>               "properties": {
>                   "type": {
> -                    "enum": ["spice", "vnc"],
> +                    "enum": ["spice", "vnc", "serial"],
>                       "error": "KCHVM0014E"

You need to update the message "KCHVM0014E" in the i18n.py file to add 
the new graphics type there.

>                   },
>                   "listen": {
> @@ -274,7 +274,8 @@
>                       "type": "integer",
>                       "minimum": 512,
>                       "error": "KCHTMPL0013E"
> -                }
> +                },
> +		"graphics": { "$ref": "#/kimchitype/graphics" }
>               }
>           },
>           "networks_create": {
> diff --git a/src/kimchi/model/config.py b/src/kimchi/model/config.py
> index 1c00cfe..0b7ecc3 100644
> --- a/src/kimchi/model/config.py
> +++ b/src/kimchi/model/config.py
> @@ -20,6 +20,7 @@
>   from multiprocessing.pool import ThreadPool
>
>   import cherrypy
> +import platform
>
>   from kimchi.basemodel import Singleton
>   from kimchi.config import config as kconfig
> @@ -106,6 +107,7 @@ class CapabilitiesModel(object):
>           return {'libvirt_stream_protocols': self.libvirt_stream_protocols,
>                   'qemu_spice': self._qemu_support_spice(),
>                   'qemu_stream': self.qemu_stream,
> +		'hostarch': platform.machine(),
>                   'screenshot': VMScreenshot.get_stream_test_result(),
>                   'system_report_tool': bool(report_tool),
>                   'update_tool': update_tool,
> diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
> index 58686cd..8f69cef 100644
> --- a/src/kimchi/model/vms.py
> +++ b/src/kimchi/model/vms.py
> @@ -20,10 +20,12 @@
>   from lxml.builder import E
>   import lxml.etree as ET
>   from lxml import etree, objectify
> +import platform
>   import os
>   import random
>   import string
>   import time
> +from datetime import datetime,timedelta
>   import uuid
>   from xml.etree import ElementTree
>
> @@ -247,8 +249,13 @@ class VMModel(object):
>           self.groups = import_class('kimchi.model.host.GroupsModel')(**kargs)
>
>       def update(self, name, params):
> +        if 'ticket' in params:
> +            password = params['ticket'].get("passwd")

It was updated to "graphics" instead of "ticket"

> +            password = password if password is not None else "".join(
> +                random.sample(string.ascii_letters + string.digits, 8))
> +            params['ticket']['passwd'] = password

This was also changed recently. The password will be automatically 
generated if the passed value is an empty string,

>           dom = self.get_vm(name, self.conn)
> -        dom = self._static_vm_update(dom, params)
> +        dom = self._static_vm_update(dom, params, name)
>           self._live_vm_update(dom, params)
>           return dom.name().decode('utf-8')
>
> @@ -305,41 +312,107 @@ class VMModel(object):
>           os_elem = E.os({"distro": distro, "version": version})
>           set_metadata_node(dom, os_elem)
>
> -    def _update_graphics(self, dom, xml, params):
> -        root = objectify.fromstring(xml)
> -        graphics = root.devices.find("graphics")
> -        if graphics is None:
> +    def _update_graphics(self, dom, xml, params, name):

> +##        root = objectify.fromstring(xml)
> +##        graphics = root.devices.find("graphics")
> +

If the above code is not needed anymore remove the lines instead of 
comment them

> +        curgraphics = self._vm_get_graphics(name)
> +        graphics_type, graphics_listen, graphics_port, graphics_password, graphics_validto = curgraphics
> +
> +        if graphics_type is None :
>               return xml
> +
> +        if graphics_password != None : params['graphics']['passwd'] = graphics_password

Split it in 2 lines. It is easy to read.

> +        if graphics_validto != None :
> +            params['graphics']['passwdValidTo'] = graphics_validto

> -        password = params['graphics'].get("passwd")
> -        if password is not None and len(password.strip()) == 0:
> -            password = "".join(random.sample(string.ascii_letters +


> +        try:
> +            ## Determine if a change in graphics type has been submitted
> +            if ('graphics' in params) and (params['graphics']['type'] != None):
> +                hasGraphicTypeChange = True
> +        except:
> +            hasGraphicTypeChange = False
> +

If the code gets this point, the "graphics" is in params dict.
So you just need to check for type

if params["graphics"].get("type") is not None:

# continue the follow to change the graphics type

> +        if hasGraphicTypeChange:
> +            newGraphicsType=params['graphics']['type']
> +
> +		    ## Remove current graphics implementations by node
> +            newxml=xml
> +            root = etree.fromstring(newxml)
> +            devices = root.find("devices")
> +            if graphics_type == 'vnc':
> +                nodeDelete=[devices.find("graphics"),devices.find("video")]
> +            elif graphics_type == 'spice':
> +                nodeDelete=[devices.find("graphics"),devices.find("channel"), devices.find("video")]
> +            elif graphics_type == 'serial':
> +                nodeDelete=[devices.find("serial"),devices.find("console")]
> +            else:
> +                nodeDelete=''
> +            if nodeDelete:
> +                for i in nodeDelete:
> +                    devices.remove(i)
> +		## Add new graphics information back in by node
> +            if newGraphicsType == 'spice':
> +                newchannel=etree.SubElement(devices,'channel',type='spicevmc')
> +                newtarget=etree.SubElement(newchannel,'target',type='virtio',name='com.redhat.spice.0')
> +                newgraphics=etree.SubElement(devices,'graphics',type='spice', listen='127.0.0.1', autoport='yes')
> +                if 'passwd' in params['graphics']: newgraphics.attrib['passwd'] = graphics_password
> +                if 'passwdValidTo' in params['graphics']: newgraphics.attrib['passwdValidTo'] = self._convert_datetime(str(graphics_validto))
> +                newlisten=etree.SubElement(newgraphics,'listen',type='address',address='127.0.0.1')
> +            elif newGraphicsType == 'vnc':
> +                newgraphics=etree.SubElement(devices,'graphics',type='vnc', listen='127.0.0.1', autoport='yes', port='-1')
> +                newlisten=etree.SubElement(newgraphics,'listen',type='address',address='127.0.0.1')
> +                if 'passwd' in params['graphics']: newgraphics.attrib['passwd'] = graphics_password
> +                if 'passwdValidTo' in params['graphics']: newgraphics.attrib['passwdValidTo'] = self._convert_datetime(str(graphics_validto))
> +		
> +            elif newGraphicsType == 'serial':
> +                newconsole=etree.SubElement(devices,'console',type='pty')
> +                newtarget=etree.SubElement(newconsole,'target',type='serial', port='0')
> +                newaddress=etree.SubElement(newconsole,'address',type='spapr-vio', reg='0x30000000')
> +            xml = etree.tostring(root)

This is really a big piece of code. I suggest move it to a function.

And thanks for using etree to generate the XML.
I'd say to you create a new module to input it. As we can reuse it while 
creating the VM too.

src/kimchi/xmlutils/graphics.py

And then we can insert more and more modules there.

> +        if ('passwd' in params['graphics']) or ('passwdValidTo' in params['graphics']):
> +            root = objectify.fromstring(xml)
> +            graphics = root.devices.find("graphics")
> +
> +            password = params['graphics'].get("passwd")
> +            if password is not None and len(password.strip()) == 0:
> +             password = "".join(random.sample(string.ascii_letters +
>                                                string.digits, 8))
>
> -        if password is not None:
> -            graphics.attrib['passwd'] = password
> +            if password is not None:
> +                graphics.attrib['passwd'] = password
>
> -        expire = params['graphics'].get("passwdValidTo")
> -        to = graphics.attrib.get('passwdValidTo')
> -        if to is not None:
> -            if (time.mktime(time.strptime(to, '%Y-%m-%dT%H:%M:%S'))
> -               - time.time() <= 0):
> -                expire = expire if expire is not None else 30
> +            expire = params['graphics'].get("passwdValidTo")
> +            to = graphics.attrib.get('passwdValidTo')
> +            if to is not None:
> +             if (time.mktime(time.strptime(to, '%Y-%m-%dT%H:%M:%S'))
> +                   - time.time() <= 0):
> +                    expire = expire if expire is not None else 30
>
> -        if expire is not None:
> -            expire_time = time.gmtime(time.time() + float(expire))
> -            valid_to = time.strftime('%Y-%m-%dT%H:%M:%S', expire_time)
> -            graphics.attrib['passwdValidTo'] = valid_to
> +            if expire is not None:
> +                expire_time = time.gmtime(time.time() + float(expire))
> +                valid_to = time.strftime('%Y-%m-%dT%H:%M:%S', expire_time)
> +                graphics.attrib['passwdValidTo'] = valid_to
>
> -        if not dom.isActive():
> -            return ET.tostring(root, encoding="utf-8")
> +            if not dom.isActive():
> +                return ET.tostring(root, encoding="utf-8")
>
> -        xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
> -        dom.updateDeviceFlags(etree.tostring(graphics),
> +            xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
> +            dom.updateDeviceFlags(etree.tostring(graphics),
>                                 libvirt.VIR_DOMAIN_AFFECT_LIVE)
>           return xml
>
> -    def _static_vm_update(self, dom, params):
> +    def _convert_datetime(self,deltaseconds):
> +        deltaseconds = int(deltaseconds)
> +        # takes the delta (in seconds) between the current time
> +        # and the expiration time for the vnc password
> +        mycur = datetime.strptime((time.strftime('%Y-%m-%dT%H:%M:%S',time.gmtime())),'%Y-%m-%dT%H:%M:%S')
> +        expiredate = time.strptime((str(mycur + timedelta(seconds=deltaseconds))),'%Y-%m-%d %H:%M:%S')
> +
> +        return time.strftime('%Y-%m-%dT%H:%M:%S',expiredate)
> +
> +
> +    def _static_vm_update(self, dom, params, name):
>           state = DOM_STATE_MAP[dom.info()[0]]
>           old_xml = new_xml = dom.XMLDesc(0)
>
> @@ -355,7 +428,7 @@ class VMModel(object):
>                   new_xml = xmlutils.xml_item_update(new_xml, xpath, val)
>
>           if 'graphics' in params:
> -            new_xml = self._update_graphics(dom, new_xml, params)
> +            new_xml = self._update_graphics(dom, new_xml, params, name)
>
>           conn = self.conn.get()
>           try:
> @@ -383,15 +456,35 @@ class VMModel(object):
>           dom = ElementTree.fromstring(dom.XMLDesc(0))
>           return dom.find('devices/video') is not None


> +    def _get_ticket(self, dom):
> +        xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
> +        root = objectify.fromstring(xml)
> +        graphic = root.devices.find("graphics")
> +        if graphic is None:
> +            return {"passwd": None, "expire": None}
> +        passwd = graphic.attrib.get('passwd')
> +        ticket = {"passwd": passwd}
> +        valid_to = graphic.attrib.get('passwdValidTo')
> +        ticket['expire'] = None
> +        if valid_to is not None:
> +            to = time.mktime(time.strptime(valid_to, '%Y-%m-%dT%H:%M:%S'))
> +            ticket['expire'] = to - time.mktime(time.gmtime())
> +
> +        return ticket
> +

It was removed recently

>       def lookup(self, name):
>           dom = self.get_vm(name, self.conn)
>           info = dom.info()
>           state = DOM_STATE_MAP[info[0]]
>           screenshot = None
> -        # (type, listen, port, passwd, passwdValidTo)
>           graphics = self._vm_get_graphics(name)
> +        graphics_type = graphics[0]
>           graphics_port = graphics[2]
>           graphics_port = graphics_port if state == 'running' else None

> +        if graphics_type is None and platform.machine() == "ppc64":
> +            ## Checking for ppc64 serial console
> +            graphics_type = "serial"
> +

If the host is a ppc64 macihne it does not mean the VM has the serial 
console, right?
So you should respect the value returned by _vm_get_graphics(name)

>           try:
>               if state == 'running' and self._has_video(dom):
>                   screenshot = self.vmscreenshot.lookup(name)
> @@ -539,8 +632,21 @@ class VMModel(object):
>
>           expr = "/domain/devices/graphics/@type"
>           res = xmlutils.xpath_get_text(xml, expr)
> -        graphics_type = res[0] if res else None
> -
> +        if res:
> +               graphics_type = res[0]
> +        else:
> +			## Checking for serial console, if not
> +			## returning None
> +               expr = "/domain/devices/console/@type"
> +               res = xmlutils.xpath_get_text(xml, expr)
> +               if res:
> +                       if res[0] == "pty":
> +                               graphics_type = "serial"
> +                       else:
> +                               graphics_type = None
> +               else:
> +                      graphics_type = None
> +
>           expr = "/domain/devices/graphics/@listen"
>           res = xmlutils.xpath_get_text(xml, expr)
>           graphics_listen = res[0] if res else None
> @@ -558,8 +664,15 @@ class VMModel(object):
>               expr = "/domain/devices/graphics[@type='%s']/@passwdValidTo"
>               res = xmlutils.xpath_get_text(xml, expr % graphics_type)
>               if res:
> -                to = time.mktime(time.strptime(res[0], '%Y-%m-%dT%H:%M:%S'))
> -                graphics_passwdValidTo = to - time.mktime(time.gmtime())
> +                currdate = datetime.strptime((time.strftime('%Y-%m-%dT%H:%M:%S',time.gmtime())),'%Y-%m-%dT%H:%M:%S')
> +                graphics_expiredate = (datetime.strptime(res[0],'%Y-%m-%dT%H:%M:%S'))
> +                mymax = max((currdate, graphics_expiredate))
> +                expirediff = currdate - graphics_expiredate
> +                if mymax == currdate:
> +                    ## time has expired
> +                    graphics_passwdValidTo = abs(expirediff).seconds * -1
> +                else:
> +                    graphics_passwdValidTo = abs(expirediff).seconds
>
>           return (graphics_type, graphics_listen, graphics_port,
>                   graphics_passwd, graphics_passwdValidTo)
> @@ -567,6 +680,8 @@ class VMModel(object):
>       def connect(self, name):
>           # (type, listen, port, passwd, passwdValidTo)
>           graphics_port = self._vm_get_graphics(name)[2]
> +        graphics = self._vm_get_graphics(name)
> +        graphics_type, graphics_listen, graphics_port = graphics
>           if graphics_port is not None:
>               vnc.add_proxy_token(name, graphics_port)
>           else:
> diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py
> index c5bb7b3..f735d14 100644
> --- a/src/kimchi/vmtemplate.py
> +++ b/src/kimchi/vmtemplate.py
> @@ -217,12 +217,21 @@ drive=drive-%(bus)s0-1-0,id=%(bus)s0-1-0'/>
>                 <target type='virtio' name='com.redhat.spice.0'/>
>               </channel>
>           """
> +

> +	serial_xml = """
> +  	    <console type='pty'>
> +  	     <target type='serial' port='0'/>
> +  	     <address type='spapr-vio' reg='0x30000000'/>
> +	    </console>
> +	"""

By doing the XML module I suggested before you can reuse the XML 
generated by etree here.

>           graphics = dict(self.info['graphics'])
>           if params:
>               graphics.update(params)
>           graphics_xml = graphics_xml % graphics
>           if graphics['type'] == 'spice':
>               graphics_xml = graphics_xml + spicevmc_xml
> +	elif graphics['type'] == 'serial' :
> +	    graphics_xml = serial_xml
>           return graphics_xml
>
>       def _get_scsi_disks_xml(self):
> diff --git a/ui/css/theme-default/guest-edit.css b/ui/css/theme-default/guest-edit.css
> index 74c2237..8515cda 100644
> --- a/ui/css/theme-default/guest-edit.css
> +++ b/ui/css/theme-default/guest-edit.css
> @@ -63,6 +63,12 @@
>       width: 470px;
>   }
>
> +.guest-edit-wrapper-controls > .dropdown {
> +    margin: 5px 0 0 1px;
> +    width: 440px;
> +}
> +
> +
>   #form-guest-edit-storage .guest-edit-wrapper-controls {
>       width: 486px;
>   }
> diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css
> index f635c2f..eeb783c 100644
> --- a/ui/css/theme-default/storage.css
> +++ b/ui/css/theme-default/storage.css
> @@ -222,64 +222,6 @@
>       width: 13px;
>   }

> -.toolable {
> -    position: relative;
> -}
> -
> -.toolable .tooltip {
> -    display: none;
> -    border: 2px solid #0B6BAD;
> -    background: #fff;
> -    padding: 6px;
> -    position: absolute;
> -    color: #666666;
> -    font-weight: bold;
> -    font-size: 11px;
> -    -webkit-border-radius: 5px;
> -    -moz-border-radius: 5px;
> -    border-radius: 5px;
> -    z-index: 100;
> -    top: -300%;
> -    left: -140%;
> -    white-space: nowrap;
> -}
> -
> -.toolable:hover .tooltip {
> -    display: block;
> -}
> -
> -.toolable .tooltip:after {
> -    -moz-border-bottom-colors: none;
> -    -moz-border-left-colors: none;
> -    -moz-border-right-colors: none;
> -    -moz-border-top-colors: none;
> -    border-color: #fff transparent transparent;
> -    border-image: none;
> -    border-style: solid;
> -    border-width: 7px;
> -    content: "";
> -    display: block;
> -    left: 15px;
> -    position: absolute;
> -    bottom: -14px;
> -}
> -
> -.toolable .tooltip:before {
> -    -moz-border-bottom-colors: none;
> -    -moz-border-left-colors: none;
> -    -moz-border-right-colors: none;
> -    -moz-border-top-colors: none;
> -    border-color: #0B6BAD transparent transparent;
> -    border-image: none;
> -    border-style: solid;
> -    border-width: 8px;
> -    content: "";
> -    display: block;
> -    left: 14px;
> -    position: absolute;
> -    bottom: -18px;
> -}
> -

To where the above content went ?

>   .inactive {
>       background: #E80501;
>       background: linear-gradient(to bottom, #E88692 0%, #E84845 50%,
> diff --git a/ui/js/src/kimchi.guest_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js
> index 938dfd9..2e71736 100644
> --- a/ui/js/src/kimchi.guest_edit_main.js
> +++ b/ui/js/src/kimchi.guest_edit_main.js
> @@ -386,6 +386,7 @@ kimchi.guest_edit_main = function() {
>           };
>
>           initStorageListeners();
> +
>           setupInterface();
>           setupPermission();
>
> @@ -398,6 +399,31 @@ kimchi.guest_edit_main = function() {
>               kimchi.topic('kimchi/vmCDROMReplaced').unsubscribe(onReplaced);
>               kimchi.topic('kimchi/vmCDROMDetached').unsubscribe(onDetached);
>           };
> +
> +	graphics_type = guest.graphics.type
> +	
> +	var graphicsForm = $('#form-guest-edit-general');
> +	$('input[name="graphics"]', graphicsForm).val(graphics_type);
> +	
> +
> +	var vncOpt = [{label: 'VNC', value: 'vnc'}];
> +        kimchi.select('guest-edit-general-graphics-list', vncOpt);
> +
> +        var enableSpice = function() {
> +            if (kimchi.capabilities == undefined) {
> +                setTimeout(enableSpice, 2000);
> +                return;
> +            }
> +            if (kimchi.capabilities.qemu_spice == true) {
> +                spiceOpt = [{label: 'Spice', value: 'spice'}]
> +                kimchi.select('guest-edit-general-graphics-list', spiceOpt);
> +            }
> +       	    if (kimchi.capabilities.hostarch == 'ppc64') {
> +		serialOpt = [{label: 'Serial', value: 'serial'}]
> +		kimchi.select('guest-edit-general-graphics-list', serialOpt);
> +	    }
> +        };
> +	enableSpice();
>       };
>
>       kimchi.retrieveVM(kimchi.selectedGuest, initContent);
> @@ -405,13 +431,15 @@ kimchi.guest_edit_main = function() {
>       var generalSubmit = function(event) {
>           $(saveButton).prop('disabled', true);
>           var data=$('#form-guest-edit-general').serializeObject();
> +	graphics_type=data.graphics
>           if(data['memory']!=undefined) {
>               data['memory'] = Number(data['memory']);
>           }
>           if(data['cpus']!=undefined) {
>               data['cpus']   = Number(data['cpus']);
>           }
> -
> +	data['graphics'] = {}
> +	data['graphics']['type'] = graphics_type
>           kimchi.updateVM(kimchi.selectedGuest, data, function() {
>               kimchi.listVmsAuto();
>               kimchi.window.close();
> diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js
> index dbe8753..5731634 100644
> --- a/ui/js/src/kimchi.guest_main.js
> +++ b/ui/js/src/kimchi.guest_main.js
> @@ -266,11 +266,26 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) {
>       }
>
>       var consoleActions=guestActions.find("[name=vm-console]");
> +    if ((vmObject.graphics['type'] == 'vnc') || (vmObject.graphics['type'] == 'spice'))  {
> +	if (vmRunningBool) {
> +	        consoleActions.on("click", kimchi.openVmConsole);
> +        	consoleActions.show();
> +	}
> +	else {
> +	        consoleActions.hide();
> +       		consoleActions.off("click",kimchi.openVmConsole);
>
> -    if ((vmObject.graphics['type'] == 'vnc') || (vmObject.graphics['type'] == 'spice')) {
> -        consoleActions.on("click", kimchi.openVmConsole);
> -        consoleActions.show();
> -    } else {         //we don't recognize the VMs supported graphics, so hide the menu choice
> +	}
> +
> +    }
> +    else if ((vmObject.graphics['type'] == 'serial') && (vmRunningBool)) {
> +	consoleActions.prop('disabled', true);
> +	tooltip = $("#connectserial label")
> +	tooltip.show()
> +	tooltip.addClass("tooltip")
> +	consoleActions.show();
> +    }
> +    else {         //we don't recognize the VMs supported graphics, so hide the menu choice
>           consoleActions.hide();
>           consoleActions.off("click",kimchi.openVmConsole);
>       }
> diff --git a/ui/js/src/kimchi.template_edit_main.js b/ui/js/src/kimchi.template_edit_main.js
> index cb43091..68d1a07 100644
> --- a/ui/js/src/kimchi.template_edit_main.js
> +++ b/ui/js/src/kimchi.template_edit_main.js
> @@ -48,6 +48,10 @@ kimchi.template_edit_main = function() {
>
>           var vncOpt = [{label: 'VNC', value: 'vnc'}];
>           kimchi.select('template-edit-graphics-list', vncOpt);
> +	if (kimchi.capabilities.hostarch == 'ppc64') {
> +		serialOpt = [{label: 'Serial', value: 'serial'}]
> +		kimchi.select('template-edit-graphics-list', serialOpt);
> +	}
>           var enableSpice = function() {
>               if (kimchi.capabilities == undefined) {
>                   setTimeout(enableSpice, 2000);
> diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl
> index 0b7dad3..1e5e2b4 100644
> --- a/ui/pages/guest-edit.html.tmpl
> +++ b/ui/pages/guest-edit.html.tmpl
> @@ -41,6 +41,7 @@
>                   <li>
>                       <a href="#form-guest-edit-permission">$_("Permission")</a>
>                   </li>
> +		 </li>
>               </ul>
>               <form id="form-guest-edit-general">
>                   <fieldset class="guest-edit-fieldset">
> @@ -93,6 +94,26 @@
>                                   type="text"
>                                   disabled="disabled" />
>                           </div>
> +
> +
> +                       <div>
> +                            <div class="guest-edit-wrapper-label">
> +                                <label for="guest-edit-graphics-textbox">
> +                                    $_("Graphics")
> +                                </label>
> +                            </div>
> +
> +                            <div class="guest-edit-wrapper-controls">
> +                                    <div class="btn dropdown popable">
> +                                    <input id="guest-edit-general-graphics" name="graphics" type="hidden"/>
> +
> +                                    <span class="text" id="guest-edit-general-graphics-label"></span><span class="arrow"></span>
> +                                    <div class="popover" style="width: 100%">
> +					<ul class="select-list" id="guest-edit-general-graphics-list" data-target="guest-edit-general-graphics" data-label="guest-edit-general-graphics-label">
> +                                            </ul>
> +                                    </div>
> +                            </div>
> +                        </div>
>                       </div>
>                   </fieldset>
>               </form>
> diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl
> index 43fb350..de3b6bd 100644
> --- a/ui/pages/guest.html.tmpl
> +++ b/ui/pages/guest.html.tmpl
> @@ -55,7 +55,10 @@
>                       <div name="actionmenu" class="btn dropdown popable vm-action" style="width: 70px">
>                           <span class="text">$_("Actions")</span><span class="arrow"></span>
>                           <div class="popover actionsheet right-side" style="width: 250px">
> -                            <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button>
> +			    <button class="button-big toolable shutoff-disabled" name="vm-console"><span class="text">$_("Connect")</span><label name="connectserial" class="tooltip connectserial" display="hidden">$_("Use virsh console to connect to this guest")</label></button>
> +			    <!-- <button class="button-big toolable shutoff-disabled" name="vm-console"><span class="text">$_("Connect")</span></button> -->
> +
> +                            <button class="button-big shutoff-disabled" name="vm-media"><span class="text">$_("Manage Media")</span></button>
>                               <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button>
>                               <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button>
>                               <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button>
> diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl
> index d920ae2..f25c6bb 100644
> --- a/ui/pages/i18n.json.tmpl
> +++ b/ui/pages/i18n.json.tmpl
> @@ -172,4 +172,5 @@
>
>       "KCHVMSTOR0001E": "$_("CDROM path need to be a valid local path and cannot be blank.")",
>       "KCHVMSTOR0002E": "$_("Disk pool or volume cannot be blank.")"
> +
>   }
> diff --git a/ui/pages/tabs/storage.html.tmpl b/ui/pages/tabs/storage.html.tmpl
> index 87205bd..563504d 100644
> --- a/ui/pages/tabs/storage.html.tmpl
> +++ b/ui/pages/tabs/storage.html.tmpl
> @@ -58,10 +58,10 @@
>               </div>
>               <div class="storage-state">
>                   <div class="status-dot toolable active" data-state="{state}">
> -                    <label class="tooltip">$_("active")</label>
> +                    <label class="tooltip storage">$_("active")</label>
>                   </div>
>                   <div class="status-dot toolable inactive" data-state="{state}">
> -                    <label class="tooltip">$_("inactive")</label>
> +                    <label class="tooltip storage">$_("inactive")</label>
>                   </div>
>               </div>
>               <div class="storage-location">




More information about the Kimchi-devel mailing list