<html>
  <head>
    <meta content="text/html; charset=ISO-8859-1"
      http-equiv="Content-Type">
  </head>
  <body bgcolor="#FFFFFF" text="#000000">
    <div class="moz-cite-prefix">On 03/22/2014 03:39 AM, Aline Manera
      wrote:<br>
    </div>
    <blockquote cite="mid:532C955F.6000401@linux.vnet.ibm.com"
      type="cite">
      <meta content="text/html; charset=ISO-8859-1"
        http-equiv="Content-Type">
      <div class="moz-cite-prefix"><br>
        Some comments:<br>
        <br>
        <img src="cid:part1.03080502.07000408@linux.vnet.ibm.com" alt=""><br>
        <br>
        Which operation failed?<br>
        I think we need to provide a detailed message<br>
        <br>
        For example:<br>
        <br>
        "An error occurs while checking for packages update."<br>
      </div>
    </blockquote>
    ACK. "Operation failed." is confused because user doesn't know what
    operation means. Will replace it with your suggestion text.<br>
    <blockquote cite="mid:532C955F.6000401@linux.vnet.ibm.com"
      type="cite">
      <div class="moz-cite-prefix"> <br>
        The "Retry" button should be in the footer or be a link instead
        a button<br>
      </div>
    </blockquote>
    &nbsp;&nbsp; For pop-up window or dialog, we put the button in the footer,
    just as what we do in create VM window, edit VM window, etc. Though
    in this case, I still suggest keep the button be here. It's
    user-friendly to put the button after the message so the user will
    be able to take action immediately. It's consistent with grid
    design: toolbar (which contains action buttons) is above data or
    message.<br>
    <br>
    And the button is intentionally here, rather than a link.<br>
    Be aware of that: button is for "<b>action</b>", while link is for "<b>redirection</b>",
    and sometimes we can choose either of them for button link or link
    button cases.<br>
    &nbsp; 1) For example, when we want to switch tabs, the tab pages are
    listed as links like "Guests", "Templates', "Network"; we can't use
    buttons here.<br>
    &nbsp; 2) For another example, when we create a VM with some properties
    (which are general organized as &lt;form&gt; &lt;field&gt;s and put
    in a &lt;form&gt; element), we should use a &lt;button&gt; to
    trigger the "create" action instead of a link (&lt;a href=""&gt;).<br>
    &nbsp; 3) And the 3rd example, log-in/log-out buttons. Because log in or
    log out action often goes with a redirection: log-in will cause a
    redirection to main page, and log-out will cause a redirection to
    log-in page. So in the case, either button or link is OK.<br>
    <br>
    &nbsp; For our case, it's for "list software" so no redirection is here.
    Only button can be used here.<br>
    <blockquote cite="mid:532C955F.6000401@linux.vnet.ibm.com"
      type="cite">
      <div class="moz-cite-prefix"> <br>
        <br>
        On 03/21/2014 06:48 AM, Hongliang Wang wrote:<br>
      </div>
      <blockquote
        cite="mid:1395395321-15918-1-git-send-email-hlwang@linux.vnet.ibm.com"
        type="cite">
        <pre wrap="">Software update grid keeps loading on UI when server returns 500 error.
Instead, we shall remove the loading UI and add a message UI to let the
user know something is wrong, as well as add a button to allow the user
retry.

Signed-off-by: Hongliang Wang <a moz-do-not-send="true" class="moz-txt-link-rfc2396E" href="mailto:hlwang@linux.vnet.ibm.com">&lt;hlwang@linux.vnet.ibm.com&gt;</a>
---
 ui/css/theme-default/grid.css | 20 +++++++++++++
 ui/js/src/kimchi.api.js       |  4 +--
 ui/js/src/kimchi.grid.js      | 66 +++++++++++++++++++++++++++++++++----------
 ui/js/src/kimchi.host.js      | 11 +++++++-
 ui/pages/i18n.html.tmpl       |  5 ++++
 5 files changed, 87 insertions(+), 19 deletions(-)

diff --git a/ui/css/theme-default/grid.css b/ui/css/theme-default/grid.css
index 44ae614..684dd7b 100644
--- a/ui/css/theme-default/grid.css
+++ b/ui/css/theme-default/grid.css
@@ -239,3 +239,23 @@
     height: 48px;
     width: 49px;
 }
+
+.grid-message {
+    background: white;
+    box-sizing: border-box;
+    bottom: 0;
+    left: 0;
+    overflow: auto;
+    padding: .2em .5em;
+    position: absolute;
+    right: 0;
+    z-index: 5;
+}
+
+.grid-message-text {
+    line-height: 25px;
+}
+
+.retry-button {
+    margin: 0 1em;
+}
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index 4310435..11f83df 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -838,10 +838,8 @@ var kimchi = {
                 }, 200);
                 break;
             case 'finished':
-                suc(result);
-                break;
             case 'failed':
-                err(result);
+                suc(result);
                 break;
             default:
                 break;
diff --git a/ui/js/src/kimchi.grid.js b/ui/js/src/kimchi.grid.js
index 540f1ba..f35228d 100644
--- a/ui/js/src/kimchi.grid.js
+++ b/ui/js/src/kimchi.grid.js
@@ -62,6 +62,18 @@ kimchi.widget.Grid = function(params) {
                 '&lt;/div&gt;',
             '&lt;/div&gt;',
         '&lt;/div&gt;',
+        '&lt;div class="grid-message hidden"&gt;',
+          '&lt;div class="grid-message-text"&gt;',
+            i18n['KCHGRD6002M'],
+            '&lt;button class="retry-button btn-small"&gt;',
+              i18n['KCHGRD6003M'],
+            '&lt;/button&gt;',
+          '&lt;/div&gt;',
+          '&lt;div class="detailed-title"&gt;',
+            i18n['KCHGRD6004M'],
+          '&lt;/div&gt;',
+          '&lt;div class="detailed-text"&gt;&lt;/div&gt;',
+        '&lt;/div&gt;',
       '&lt;/div&gt;'
     ];

@@ -161,6 +173,9 @@ kimchi.widget.Grid = function(params) {
     var maskNode = $('.grid-mask', gridNode);
     maskNode.css('top', captionHeight + 'px');

+    var messageNode = $('.grid-message', gridNode);
+    messageNode.css('top', captionHeight + 'px');
+
     var fillBody = function(container, fields, data) {
         var tbody = ($('tbody', container).length &amp;&amp; $('tbody', container))
             || $('&lt;tbody&gt;&lt;/tbody&gt;').appendTo(container);
@@ -384,28 +399,49 @@ kimchi.widget.Grid = function(params) {
     $('body').on('mousemove', positionResizer);
     $('body').on('mouseup', endResizing);

+    this.showMessage = function(msg) {
+        $('.detailed-text', messageNode).text(msg);
+        $(messageNode).removeClass('hidden');
+    };
+
+    this.hideMessage = function() {
+        $(messageNode).addClass('hidden');
+    };
+
     this.destroy = function() {
         $('body').off('mousemove', positionResizer);
         $('body').off('mouseup', endResizing);
     };

     var data = params['data'];
-    if(!data) {
-        return;
-    }
+    var self = this;
+    var reload = function() {
+        if(!data) {
+            return;
+        }

-    if($.isArray(data)) {
-        this.setData(data);
-        return;
-    }
+        $(messageNode).addClass('hidden');

-    if($.isFunction(data)) {
-        var self = this;
-        var loadData = data;
-        maskNode.removeClass('hidden');
-        loadData(function(data) {
+        if($.isArray(data)) {
             self.setData(data);
-            maskNode.addClass('hidden');
-        });
-    }
+            return;
+        }
+
+        if($.isFunction(data)) {
+            var loadData = data;
+            maskNode.removeClass('hidden');
+            loadData(function(data) {
+                self.setData(data);
+                maskNode.addClass('hidden');
+            });
+        }
+    };
+
+    var reloadButton = $('.retry-button', gridNode);
+    $(reloadButton).on('click', function(event) {
+        reload();
+    });
+
+    this.reload = reload;
+    reload();
 };
diff --git a/ui/js/src/kimchi.host.js b/ui/js/src/kimchi.host.js
index 6e4678f..2990cdd 100644
--- a/ui/js/src/kimchi.host.js
+++ b/ui/js/src/kimchi.host.js
@@ -57,7 +57,9 @@ kimchi.host_main = function() {
                         kimchi.topic('kimchi/softwareUpdated').publish({
                             result: result
                         });
-                    }, function(result) {
+                    }, function(error) {
+                        var message = error &amp;&amp; error['responseJSON'] &amp;&amp; error['responseJSON']['reason'];
+                        kimchi.message.error(message || i18n['KCHUPD6009M']);
                         $(updateButton).text(i18n['KCHUPD6006M']).prop('disabled', false);
                     }, reloadProgressArea);
                 }
@@ -100,6 +102,13 @@ kimchi.host_main = function() {

             var updateButton = $('#' + softwareUpdatesGridID + '-update-button');
             $(updateButton).prop('disabled', softwareUpdates.length === 0);
+        }, function(error) {
+            var message = error &amp;&amp; error['responseJSON'] &amp;&amp; error['responseJSON']['reason'];
+            if($.isFunction(gridCallback)) {
+                gridCallback([]);
+            }
+            softwareUpdatesGrid &amp;&amp;
+                softwareUpdatesGrid.showMessage(message || i18n['KCHUPD6008M']);
         });
     };

diff --git a/ui/pages/i18n.html.tmpl b/ui/pages/i18n.html.tmpl
index 2f47e50..362cde2 100644
--- a/ui/pages/i18n.html.tmpl
+++ b/ui/pages/i18n.html.tmpl
@@ -60,6 +60,9 @@ var i18n = {
     'KCHAPI6006M': "$_("Warning")",

     'KCHGRD6001M': "$_("Loading...")",
+    'KCHGRD6002M': "$_("Operation failed.")",
+    'KCHGRD6003M': "$_("Retry")",
+    'KCHGRD6004M': "$_("Detailed message:")",

     'KCHTMPL6001W': "$_("No iso found")",

@@ -87,6 +90,8 @@ var i18n = {
     'KCHUPD6005M': "$_("Repository")",
     'KCHUPD6006M': "$_("Update All")",
     'KCHUPD6007M': "$_("Updating...")",
+    'KCHUPD6008M': "$_("Failed to retrieve updates.")",
+    'KCHUPD6009M': "$_("Failed to update package(s).")",


     'KCHDR6001M': "$_("Debug report will be removed permanently and can't be recovered. Do you want to continue?")",
</pre>
      </blockquote>
      <br>
    </blockquote>
    <br>
  </body>
</html>