[Kimchi-devel] [PATCH v4] [Kimchi] Issue# 979 - Change boot order UI

Aline Manera alinefm at linux.vnet.ibm.com
Thu Jan 19 12:43:35 UTC 2017


Hi Bianca,

I am not able to Add/Remove multiple entries in the boot order list at 
once. Every time I click on "Save" or "Delete" the dialog is closed.

Also the "Remove" buttons are not aligned with each other.

As we are restricting the boot order to have only 3 options (hdd, cdrom 
and network), I'd suggest to show all those options at once and mark 
those already set and let user select more options to "add" in addition 
to grab the elements.

I am OK if you want to keep that design, but it would look better if it 
is similar to what we have on Network tab from Edit guest dialog.

Some more comments below:

On 01/17/2017 05:32 PM, bianca at linux.vnet.ibm.com wrote:
> From: Bianca Carvalho <bianca at linux.vnet.ibm.com>
>
> This patch adds the UI portion for supporting changing the guest boot order
> via edit guest panel.  This was based off of what Samuel had prototyped.
>
> Issue found in backend during test:
> When updating the VM even with just changing the name, the bootorder gets reset to 'hd' only.
> I tested this using the curl command and confirmed that indeed it does get reset to one entry only.
> Issue written to address this:  https://github.com/kimchi-project/kimchi/issues/1012
>
> Signed-off-by: Bianca Carvalho <bianca at linux.vnet.ibm.com>
> ---
>   ui/css/kimchi.css                    |  57 ++++++++++++++-
>   ui/css/src/modules/_edit-guests.scss |  60 +++++++++++++++-
>   ui/js/src/kimchi.guest_edit_main.js  | 133 ++++++++++++++++++++++++++++++++++-
>   ui/pages/guest-edit.html.tmpl        |  37 ++++++++--
>   4 files changed, 280 insertions(+), 7 deletions(-)
>
> diff --git a/ui/css/kimchi.css b/ui/css/kimchi.css
> index fff3279..16cc161 100644
> --- a/ui/css/kimchi.css
> +++ b/ui/css/kimchi.css
> @@ -1,7 +1,7 @@
>   /*
>    * Project Kimchi
>    *
> - * Copyright IBM Corp, 2015-2016
> + * Copyright IBM Corp, 2015-2017
>    *
>    * Licensed under the Apache License, Version 2.0 (the "License");
>    * you may not use this file except in compliance with the License.
> @@ -1557,6 +1557,61 @@ body.wok-gallery {
>     overflow: visible;
>   }
>
> +ul {
> +  cursor: default;
> +}
> +
> +.boot-order {
> +  display: block;
> +  width: 85%;
> +  font-size: 14px;
> +  line-height: 1.42857;
> +  color: #444;
> +  overflow: hidden;
> +  background-color: #fff;
> +  background-image: none;
> +  border: 1px solid #ccc;
> +  border-radius: 3px;
> +  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
> +  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
> +  -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
> +  -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
> +  transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
> +}
> +
> +.boot-order:focus,
> +.boot-order.focus {
> +  border-color: #66afe9;
> +  outline: 0;
> +  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
> +  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
> +}
> +
> +.boot-order > li {
> +  cursor: move;
> +  /* fallback if grab cursor is unsupported */
> +  cursor: grab;
> +  cursor: -moz-grab;
> +  cursor: -webkit-grab;
> +  border-left: 0;
> +  border-right: 0;
> +}
> +
> +.boot-order > li:first-child {
> +  border-top: 0;
> +}
> +
> +.boot-order > li:last-child {
> +  border-bottom: 0;
> +}
> +
> +.boot-order > li.ui-sortable-helper {
> +  cursor: grabbing;
> +  cursor: -moz-grabbing;
> +  cursor: -webkit-grabbing;
> +  border: 1px solid #ccc !important;
> +}
> +
>   /* Add Template Modal Window */
>   .templates-modal .modal-dialog {
>     width: 1100px;
> diff --git a/ui/css/src/modules/_edit-guests.scss b/ui/css/src/modules/_edit-guests.scss
> index 25d4d65..c8cc122 100644
> --- a/ui/css/src/modules/_edit-guests.scss
> +++ b/ui/css/src/modules/_edit-guests.scss
> @@ -1,7 +1,7 @@
>   //
>   // Project Kimchi
>   //
> -// Copyright IBM Corp, 2015-2016
> +// Copyright IBM Corp, 2015-2017
>   //
>   // Licensed under the Apache License, Version 2.0 (the "License");
>   // you may not use this file except in compliance with the License.
> @@ -429,3 +429,61 @@
>   #form-guest-storage-add .form-section .field {
>       overflow: visible;
>   }
> +
> +ul {
> +  cursor: default;
> +}
> +
> +.boot-order {
> +  display: block;
> +  width: 85%;
> +  font-size: 14px;
> +  line-height: 1.42857;
> +  color: #444;
> +  overflow: hidden;
> +  background-color: #fff;
> +  background-image: none;
> +  border: 1px solid #ccc;
> +  border-radius: 3px;
> +  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
> +  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
> +  -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
> +  -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
> +  transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
> +}
> +
> +.boot-order:focus,
> +.boot-order.focus {
> +  border-color: #66afe9;
> +  outline: 0;
> +  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
> +  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
> +}
> +
> +.boot-order > li {
> +  cursor: move; /* fallback if grab cursor is unsupported */
> +  cursor: grab;
> +  cursor: -moz-grab;
> +  cursor: -webkit-grab;
> +  border-left: 0;
> +  border-right: 0;
> +}
> +
> +.boot-order > li:first-child {
> +  border-top: 0;
> +}
> +
> +.boot-order > li:last-child {
> +  border-bottom: 0;
> +}
> +
> +.boot-order > li.ui-sortable-helper {
> +  cursor: grabbing;
> +  cursor: -moz-grabbing;
> +  cursor: -webkit-grabbing;
> +  border: 1px solid #ccc !important;
> +}
> +
> +.boot-order  li i {
> +  text-align: right;
> +}
> diff --git a/ui/js/src/kimchi.guest_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js
> index 1682a58..2f15729 100644
> --- a/ui/js/src/kimchi.guest_edit_main.js
> +++ b/ui/js/src/kimchi.guest_edit_main.js
> @@ -15,6 +15,7 @@
>    * See the License for the specific language governing permissions and
>    * limitations under the License.
>    */
> +
>   kimchi.guest_edit_main = function() {
>       var authType;
>       var formTargetId;
> @@ -26,6 +27,7 @@ kimchi.guest_edit_main = function() {
>       var networkOptions = "";
>
>       clearTimeout(kimchi.vmTimeout);
> +    var bootOrderOptions = [];
>
>       $('#modalWindow').on('hidden.bs.modal', function() {
>           kimchi.setListVMAutoTimeout();
> @@ -45,7 +47,7 @@ kimchi.guest_edit_main = function() {
>
>       var submitForm = function(event) {

>
> -        // tap map, "general": 0, "storage": 1, "interface": 2, "permission": 3, "password": 4
> +        // tap map, "general": 0, "storage": 1, "interface": 2, "permission": 3, "pci": 4, "snapshot": 5, "processor": 6

That does not make sense anymore

>           var submit_map = {
>               0: generalSubmit,
>               3: permissionSubmit,
> @@ -966,6 +968,113 @@ kimchi.guest_edit_main = function() {
>           }
>       };
>
> +    var setupBootOrder = function(guest) {
> +        var guestBootOrder = guest['bootorder'];
> +        $("#myList").empty();
> +        $.each(guestBootOrder, function(index, value) {
> +           var itemNode = $.parseHTML("<li class='list-group-item' " + "data-value=" + value + ">" + value + "<button class='btn btn-link deleteDev'><i class='fa fa-minus-circle'></i> Remove</button></li>");
> +           $("#myList").append(itemNode);
> +        });
> +
> +        $('.boot-order').sortable({
> +            items: 'li',
> +            cursor: 'move',
> +            opacity: 0.6,
> +            containment: "parent",
> +            start: function(event, ui) {
> +                $(this).addClass('focus');
> +            },
> +            stop: function(event, ui) {
> +                $(this).removeClass('focus');
> +            },
> +            change: function(event, ui) {
> +                // callback once started changing order
> +            },
> +            update: function(event, ui) {
> +                // callback once finished order
> +                $(saveButton).prop('disabled', false);
> +                bootOrderOptions = [];
> +                $("#myList li").each(function() {
> +                    bootOrderOptions.push($(this).attr("data-value"))
> +                });
> +                bootOrderOptions.forEach(function(entry) {
> +                    console.log(entry);
> +                });
> +            }
> +        });
> +

> +        $(".deleteDev").on('click', function(evt) {

'deleteBootOrderElem' would be more informative.

> +            evt.preventDefault();
> +            var item = $(this).parent().attr("data-value");
> +            var index = guestBootOrder.indexOf(item);
> +            if (index !== -1) {
> +              guestBootOrder.splice(index, 1);
> +            }
> +            var data = {
> +                bootorder: guestBootOrder
> +            };
> +            kimchi.updateVM(kimchi.selectedGuest, data, function() {
> +                wok.window.close();
> +            });
> +        });
> +
> +        $("#guest-edit-add-boot-button").on('click', function(evt) {
> +            evt.preventDefault();
> +            var html1 = "<div class='item' id='bootDevOrder'><span class='cell column-device'><select id='bootDev' data-none-selected-text='Nothing selected'>"
> +            var html2 = "</select></span><span class='cell column-actions action-area'><button class='btn btn-primary save'>Save</button> <button class='btn btn-primary cancel'>Remove</button></span><div>"
> +            var selectOptions = []
> +            var dev = ["cdrom", "hd", "network"];
> +            $.each(dev, function(index, value) {
> +              if (!($.inArray(value, guestBootOrder) > -1)) {
> +                var option = "<option id='"+ value +"-check'>" + value + "</option>"
> +                selectOptions.push(option);
> +              }
> +            });
> +            var addBootorder = html1 + selectOptions.toString().replace(",", "") + html2;
> +            var selectionVal = $("#add-boot-order").html(addBootorder);
> +            addItem({ bootorder: selectionVal });
> +
> +            if ($('.body:not(:empty)')) {
> +              $("#guest-edit-add-boot-button").prop('disabled', true);
> +            }
> +        });
> +
> +        if (guestBootOrder.length == 3) {
> +          $("#guest-edit-add-boot-button").prop('disabled', true);
> +        }
> +
> +        var addItem = function(data) {
> +            var itemNode = $.parseHTML(wok.substitute($('#add-boot-order').html(), data));
> +            $(".body", "#form-guest-edit-bootorder").append(itemNode);
> +
> +            $('#bootDev').selectpicker({
> +              size: 4,
> +              width: '150px'
> +            });
> +
> +            $(".save", itemNode).on('click', function(evt) {
> +                evt.preventDefault();
> +                guestBootOrder.push($("select", "#bootDevOrder").val());
> +                var data = {
> +                    bootorder: guestBootOrder
> +                };
> +
> +                kimchi.updateVM(kimchi.selectedGuest, data, function() {
> +                    wok.window.close();
> +                });
> +            });
> +            $(".cancel", itemNode).on('click', function(evt) {
> +                evt.preventDefault();
> +                var item = $(this).parent().parent();
> +                $("label", item).text() === "" ? item.remove() : toggleEdit(item, false, data.id);
> +
> +                $("#guest-edit-add-boot-button").prop('disabled', false);
> +            });
> +
> +        };
> +
> +    };
> +
>       var initContent = function(guest) {
>           guest['icon'] = guest['icon'] || 'plugins/kimchi/images/icon-vm.png';
>           $('#form-guest-edit-general').fillWithObject(guest);
> @@ -1031,6 +1140,7 @@ kimchi.guest_edit_main = function() {
>           setupPermission();
>           setupPCIDevice();
>           setupSnapshot();
> +        setupBootOrder(guest);
>
>           kimchi.init_processor_tab(guest.cpu_info, $(saveButton));
>           if ((kimchi.thisVMState === "running") || (kimchi.thisVMState === "paused")) {
> @@ -1128,6 +1238,27 @@ kimchi.guest_edit_main = function() {
>               });
>
>           });

> +        //Format the strings to go in the array before passing in to the API
> +        //"hd", "network", "cdrom"
> +        var formattedBootOrderOptions = [];
> +        for (var i=0; i<bootOrderOptions.length; i++) {
> +            var str = bootOrderOptions[i].trim();
> +            str = str.toLowerCase();
> +            if (str === "hdd") {
> +                str = "hd";
> +            } else if (str === "cd-rom") {
> +                str = "cdrom";
> +            }
> +            formattedBootOrderOptions.push(str);
> +        }
> +        var data = {
> +            bootorder: formattedBootOrderOptions
> +        };

Why is that needed?

I mean, the input tag has a 'value' parameter to handle the API value 
and the content to be the displayed value so if you are using those 
parameters correct, that conversion is not needed.

> +        kimchi.updateVM(kimchi.selectedGuest, data, function() {
> +            wok.window.close();
> +        }, function(err) {
> +            wok.message.error(err.responseJSON.reason,'#alert-modal-container');
> +        });
>       };
>
>       var permissionSubmit = function(event) {
> diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl
> index d8a482c..df35b8e 100644
> --- a/ui/pages/guest-edit.html.tmpl
> +++ b/ui/pages/guest-edit.html.tmpl
> @@ -1,7 +1,7 @@
>   #*
>    * Project Kimchi
>    *
> - * Copyright IBM Corp, 2013-2016
> + * Copyright IBM Corp, 2013-2017
>    *
>    * Licensed under the Apache License, Version 2.0 (the "License");
>    * you may not use this file except in compliance with the License.
> @@ -30,7 +30,7 @@
>       </div>
>       <div class="modal-body">
>       <span id="alert-modal-container"></span>
> -<ul class="nav nav-tabs" role="tablist">
> +  <ul class="nav nav-tabs" role="tablist">
>       <li role="presentation" class="active"><a href="#form-guest-edit-general" aria-controls="form-guest-edit-general" role="tab" data-id="form-guest-edit-general" data-toggle="tab">$_("General")</a></li>
>       <li role="presentation"><a href="#form-guest-edit-storage" aria-controls="form-guest-edit-storage" role="tab" data-id="form-guest-edit-storage" data-toggle="tab">$_("Storage")</a></li>
>       <li role="presentation"><a href="#form-guest-edit-interface" aria-controls="form-guest-edit-interface" role="tab" data-id="form-guest-edit-interface" data-toggle="tab">$_("Interface")</a></li>
> @@ -39,7 +39,7 @@
>       <li role="presentation"><a href="#form-guest-edit-snapshot" aria-controls="form-guest-edit-snapshot" role="tab" data-id="form-guest-edit-snapshot" data-toggle="tab">$_("Snapshot")</a></li>
>       <li role="presentation"><a href="#form-edit-processor" aria-controls="form-edit-processor" role="tab" data-id="form-edit-processor" data-toggle="tab">$_("Processor")</a></li>
>     </ul>
> -        <div class="tab-content" id="guest-edit-tabs">
> +        <div class="tab-content" id="guest-edit-tabs" style="height: 720px;">
>               <form role="tabpanel" class="tab-pane active" id="form-guest-edit-general">
>                   <div class="form-group">
>                       <label for="guest-edit-id-textbox">$_("Name")</label>
> @@ -68,6 +68,21 @@
>                           <option value="virtio">$_("virtio")</option>
>                       </select>
>                   </div>
> +                <div class="guest-edit-bootorder tab-pane" id="form-guest-edit-bootorder">
> +                    <div id="bootOrder">
> +                        <label for="guest-edit-boot-order-textbox">Boot Order</label>
> +                            <ul id="myList" class="list-group boot-order">

> +                                <li class="list-group-item" data-value="CD-ROM">CD-ROM</li>
> +                                <li class="list-group-item" data-value="HDD">HDD</li>
> +                                <li class="list-group-item" data-value="Network">Network</li>

Seems the problem I mentioned above is here. The data-value should be 
the value to be sent to backend.

> +                            </ul>
> +                            <p class="help-block">
> +                                <i class="fa fa-info-circle"></i> $_("Change the boot order by dragging the items on the list.")</p>
> +                            </p>
> +                            <p><button id="guest-edit-add-boot-button" class="add btn btn-primary"><i class="fa fa-plus-circle"></i> $_("Add")</button></p>
> +                            <div class="body"></div>
> +                    </div>
> +                </div>
>               </form>
>               <form role="tabpanel" class="tab-pane" id="form-guest-edit-storage">
>                   <div class="btn-group action-area">
> @@ -197,7 +212,6 @@
>                               $_("Current CPU must be equal or lower than the Maximum CPU value. If a topology is set, it must be also be a multiple of the 'threads' value.")
>                           </p>
>                       </div>
> -
>                       <div id="guest-max-processor-panel" class="form-group">
>                           <label for="guest-edit-max-processor-textbox">$_("Max CPU")</label>
>                           <p id="settings-readonly-help" class="hidden">$_("Unable to edit maximum CPU or CPU topology when editing a running or paused virtual machine.")</p>
> @@ -238,6 +252,21 @@
>           <button id="guest-edit-button-cancel" class="btn btn-default" data-dismiss="modal">$_("Cancel")</button>
>       </div>
>   </div>
> +<script id="add-boot-order" type="text/html">
> +    <div class="item" id="bootDevOrder">
> +        <span class="cell column-device">
> +            <select id="bootDev" data-none-selected-text="$_("Nothing selected")">
> +              <option id="cdrom-check">cdrom</option>
> +              <option id="hdd-check">hd</option>
> +              <option id="network-check">network</option>
> +            </select>
> +        </span>
> +        <span class="cell column-actions action-area">
> +                <button class="btn btn-primary save">$_("Save")</button>
> +                <button class="btn btn-primary cancel">$_("Remove")</button>
> +        </span>
> +    <div>
> +</script>
>   <script id="cdrom-row-tmpl" type="text/html">
>       <div class="item view" id="cdrom-{dev}">
>           <span class="cell dev column-device">



More information about the Kimchi-devel mailing list