You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2015/11/19 12:23:09 UTC

[1/9] git commit: updated refs/heads/master to fe2917e

Repository: cloudstack
Updated Branches:
  refs/heads/master 219da6402 -> fe2917e91


CLOUDSTACK-9020: Implement collapsible columns and threshold colorings

Implements following in listView that generates tabular views;
- Collapsible columns in case of multi-header groupable columns
- Implements threshold coloring of cells in table
- Implements option to render a table that is scrollable in both x-y directions
- Support to only display status icon instead of label if compact is set to true
- Fixes quick-view alignment issue on Safari
- If a column was previously sorted, sorts after adding new rows
- If a supercolumn was collapsed, hides cell after adding new rows

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/0845edce
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/0845edce
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/0845edce

Branch: refs/heads/master
Commit: 0845edce1af349b2229efa01fdadc47fbc3d1a1a
Parents: 106e910
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Thu Nov 5 12:39:42 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Nov 19 15:17:43 2015 +0530

----------------------------------------------------------------------
 ui/scripts/ui/widgets/listView.js | 204 ++++++++++++++++++++++++++++++---
 1 file changed, 186 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/0845edce/ui/scripts/ui/widgets/listView.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui/widgets/listView.js b/ui/scripts/ui/widgets/listView.js
index c3c6930..c0740aa 100644
--- a/ui/scripts/ui/widgets/listView.js
+++ b/ui/scripts/ui/widgets/listView.js
@@ -765,10 +765,12 @@
     var createHeader = function(preFilter, fields, $table, actions, options) {
         if (!options) options = {};
 
-        var $thead = $('<thead>').prependTo($table).append($('<tr>'));
+        var $tr = $('<tr>');
+        var $thead = $('<thead>').prependTo($table).append($tr);
         var reorder = options.reorder;
         var detailView = options.detailView;
         var multiSelect = options.multiSelect;
+        var groupableColumns = options.groupableColumns;
         var viewArgs = $table.closest('.list-view').data('view-args');
         var uiCustom = viewArgs.uiCustom;
         var hiddenFields = [];
@@ -776,8 +778,110 @@
         if (preFilter != null)
             hiddenFields = preFilter();
 
+        var addColumnToTr = function($tr, key, colspan, label, needsCollapsibleColumn) {
+            var trText = _l(label);
+            var $th = $('<th>').addClass(key).attr('colspan', colspan).appendTo($tr);
+            if ($th.index()) $th.addClass('reduced-hide');
+            $th.css({'border-right': '1px solid #C6C3C3', 'border-left': '1px solid #C6C3C3'});
+            if (needsCollapsibleColumn) {
+                var karetLeft = $('<span>').css({'margin-right': '10px'});
+                karetLeft.attr('title', trText);
+                karetLeft.appendTo($th);
+                $('<span>').html('&laquo').css({'font-size': '15px', 'float': 'right'}).appendTo(karetLeft);
+                $('<span>').html(trText).appendTo(karetLeft);
+
+                $th.click(function(event) {
+                    event.stopPropagation();
+                    var $th = $(this);
+                    var startIndex = 0;
+                    $th.prevAll('th').each(function() {
+                        startIndex += parseInt($(this).attr('colspan'));
+                    });
+                    var endIndex = startIndex + parseInt($th.attr('colspan'));
+                    // Hide Column group
+                    $th.hide();
+                    $th.closest('table').find('tbody td').filter(function() {
+                        return $(this).index() >= startIndex && $(this).index() < endIndex;
+                    }).hide();
+                    $th.closest('table').find('thead tr:last th').filter(function() {
+                        return $(this).index() >= startIndex && $(this).index() < endIndex;
+                    }).hide();
+                    // Show collapsible column with blank cells
+                    $th.next('th').show();
+                    $th.closest('table').find('tbody td').filter(function() {
+                        return $(this).index() == endIndex;
+                    }).show();
+                    $th.closest('table').find('thead tr:last th').filter(function() {
+                        return $(this).index() == endIndex;
+                    }).show();
+                    // Refresh list view
+                    $tr.closest('.list-view').find('.no-split').dataTable('refresh');
+                });
+
+                var karetRight = addColumnToTr($tr, 'collapsible-column', 1, '');
+                $('<span>').html(trText.substring(0,3)).appendTo(karetRight);
+                $('<span>').css({'font-size': '15px'}).html(' &raquo').appendTo(karetRight);
+                karetRight.attr('title', trText);
+                karetRight.css({'border-right': '1px solid #C6C3C3', 'border-left': '1px solid #C6C3C3', 'min-width': '10px', 'width': '10px', 'max-width': '45px', 'padding': '2px'});
+                karetRight.hide();
+                karetRight.click(function(event) {
+                    event.stopPropagation();
+                    var prevTh = $(this).prev('th');
+                    var startIndex = 0;
+                    prevTh.prevAll('th').each(function() {
+                        startIndex += parseInt($(this).attr('colspan'));
+                    });
+                    var endIndex = startIndex + parseInt(prevTh.attr('colspan'));
+
+                    prevTh.show();
+                    prevTh.closest('table').find('tbody td').filter(function() {
+                        return $(this).index() >= startIndex && $(this).index() < endIndex;
+                    }).show();
+                    prevTh.closest('table').find('thead tr:last th').filter(function() {
+                        return $(this).index() >= startIndex && $(this).index() < endIndex;
+                    }).show();
+
+                    prevTh.next('th').hide();
+                    prevTh.closest('table').find('tbody td').filter(function() {
+                        return $(this).index() == endIndex;
+                    }).hide();
+                    prevTh.closest('table').find('thead tr:last th').filter(function() {
+                        return $(this).index() == endIndex;
+                    }).hide();
+
+                    $tr.closest('.list-view').find('.no-split').dataTable('refresh');
+                });
+            } else {
+                $th.html(trText);
+            }
+            return $th;
+        };
+
+        if (groupableColumns) {
+            $tr.addClass('groupable-header-columns').addClass('groupable-header');
+            $.each(fields, function(key) {
+                var field = this;
+                if (field.columns) {
+                    var colspan = Object.keys(field.columns).length;
+                    addColumnToTr($tr, key, colspan, field.label, true);
+                } else {
+                    var label = '';
+                    if (key == 'name') {
+                        label = 'label.resources';
+                    }
+                    addColumnToTr($tr, key, 1, label);
+                }
+                return true;
+            });
+            if (detailView && !$.isFunction(detailView) && !detailView.noCompact && !uiCustom) {
+                addColumnToTr($tr, 'quick-view', 1, '');
+            }
+            $tr = $('<tr>').appendTo($thead);
+            $tr.addClass('groupable-header');
+        }
+
         if (multiSelect) {
-            var $th = $('<th>').addClass('multiselect').appendTo($thead.find('tr'));
+            var $th = $('<th>').addClass('multiselect').appendTo($tr);
             var content = $('<input>')
                 .attr('type', 'checkbox')
                 .addClass('multiSelectMasterCheckbox')
@@ -794,18 +898,24 @@
             if ($.inArray(key, hiddenFields) != -1)
                 return true;
             var field = this;
-            var $th = $('<th>').addClass(key).appendTo($thead.find('tr'));
-
-            if ($th.index()) $th.addClass('reduced-hide');
-
-            $th.html(_l(field.label));
-
+            if (field.columns) {
+                $.each(field.columns, function(idx) {
+                    var subfield = this;
+                    addColumnToTr($tr, key, 1, subfield.label);
+                    return true;
+                });
+                var blankCell = addColumnToTr($tr, 'collapsible-column', 1, '');
+                blankCell.css({'min-width': '10px', 'width': '10px'});
+                blankCell.hide();
+            } else {
+                addColumnToTr($tr, key, 1, field.label);
+            }
             return true;
         });
 
         // Re-order row buttons
         if (reorder) {
-            $thead.find('tr').append(
+            $tr.append(
                 $('<th>').html(_l('label.order')).addClass('reorder-actions reduced-hide')
             );
         }
@@ -826,7 +936,7 @@
         );
 
         if (actions && !options.noActionCol && renderActionCol(actions) && actionsArray.length != headerActionsArray.length) {
-            $thead.find('tr').append(
+            $tr.append(
                 $('<th></th>')
                 .html(_l('label.actions'))
                 .addClass('actions reduced-hide')
@@ -835,7 +945,7 @@
 
         // Quick view
         if (detailView && !$.isFunction(detailView) && !detailView.noCompact && !uiCustom) {
-            $thead.find('tr').append(
+            $tr.append(
                 $('<th></th>')
                 .html(_l('label.quickview'))
                 .addClass('quick-view reduced-hide')
@@ -1033,6 +1143,7 @@
         var listViewArgs = $listView.data('view-args');
         var uiCustom = listViewArgs.uiCustom;
         var subselect = uiCustom ? listViewArgs.listView.subselect : null;
+        var hasCollapsibleColumn = false;
 
         if (!(data && data.length)) {
             $listView.data('end-of-table', true);
@@ -1088,8 +1199,25 @@
                 );
             }
 
-            // Add field data
+            var reducedFields = {};
+            var idx = 0;
             $.each(fields, function(key) {
+                var field = this;
+                if (field.columns) {
+                    $.each(field.columns, function(innerKey) {
+                        reducedFields[innerKey] = this;
+                    });
+                    reducedFields['blank-cell-' + idx] = {blankCell: true};
+                    idx += 1;
+                    hasCollapsibleColumn = true;
+                } else {
+                    reducedFields[key] = this;
+                }
+                return true;
+            });
+
+            // Add field data
+            $.each(reducedFields, function(key) {
                 if ($.inArray(key, hiddenFields) != -1)
                     return true;
                 var field = this;
@@ -1103,6 +1231,11 @@
                     $td.addClass('truncated');
                 }
 
+                if (field.blankCell) {
+                    $td.css({'min-width': '10px', 'width': '10px'});
+                    $td.hide();
+                }
+
                 if (field.indicator) {
                     $td.addClass('state').addClass(field.indicator[content]);
 
@@ -1110,6 +1243,19 @@
                     //$tr.find('td:first').addClass('item-state-' + field.indicator[content]);
                 }
 
+                if (field.thresholdcolor && field.thresholds) {
+                    if ((field.thresholds.disable in dataItem) && (field.thresholds.notification in dataItem)) {
+                        var disableThreshold = parseFloat(dataItem[field.thresholds.disable]);
+                        var notificationThreshold = parseFloat(dataItem[field.thresholds.notification]);
+                        var value = parseFloat(content);
+                        if (value >= disableThreshold) {
+                            $td.addClass('alert-disable-threshold');
+                        } else if (value >= notificationThreshold) {
+                            $td.addClass('alert-notification-threshold');
+                        }
+                    }
+                }
+
                 if (field.id == true) id = field.id;
                 if ($td.index()) $td.addClass('reduced-hide');
                 if (field.action) {
@@ -1140,9 +1286,12 @@
                             $('<pre>').html(_s(content))
                         );
                     } else {
-                        $td.append(
-                            $('<span>').html(_s(content))
-                        );
+                        var span = $('<span>').html(_s(content));
+                        if (field.compact) {
+                            span.addClass('compact');
+                            span.html('');
+                        }
+                        $td.append(span);
                     }
                 }
 
@@ -1380,8 +1529,8 @@
                     .appendTo($tr);
                 $quickView.mouseover(
                     // Show quick view
-
                     function() {
+                        var $quickView = $(this);
                         var $quickViewTooltip = $('<div>').addClass('quick-view-tooltip hovered-elem');
                         var $tr = $quickView.closest('tr');
                         var $listView = $tr.closest('.list-view');
@@ -1465,7 +1614,7 @@
                         });
                         $quickViewTooltip.css({
                             position: 'absolute',
-                            left: $tr.offset().left + $tr.width() - $quickViewTooltip.width(),
+                            left: $quickView.offset().left + $quickView.outerWidth() - $quickViewTooltip.width() - 2*(parseInt($quickView.css('border-left-width')) + parseInt($quickView.css('border-right-width'))),
                             top: $quickView.offset().top,
                             zIndex: $tr.closest('.panel').zIndex() + 1
                         });
@@ -1480,6 +1629,14 @@
             }
         });
 
+        // Toggle collapsible column to fix alignment of hidden/shown cells
+        if (hasCollapsibleColumn) {
+            $tbody.closest('table').find('tr:first th.collapsible-column:visible').prev('th').click();
+        }
+
+        // Re-sort table if a column was previously sorted
+        $listView.find('thead tr:last th.sorted').click().click();
+
         return rows;
     };
 
@@ -1798,8 +1955,19 @@
                 reorder: reorder,
                 detailView: listViewData.detailView,
                 'multiSelect': multiSelect,
-                noActionCol: listViewData.noActionCol
+                noActionCol: listViewData.noActionCol,
+                groupableColumns: listViewData.groupableColumns
             });
+
+        if (listViewData.noSplit) {
+            $table.addClass('no-split');
+        }
+
+        if (listViewData.horizontalOverflow) {
+            $table.addClass('horizontal-overflow');
+            $table.parent().css({'overflow-x': 'auto'});
+        }
+
         createFilters($toolbar, listViewData.filters);
 
         if (listViewData.hideSearchBar != true) {


[4/9] git commit: updated refs/heads/master to fe2917e

Posted by bh...@apache.org.
CLOUDSTACK-9020: Add new status icons and css rules

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/77b01fa7
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/77b01fa7
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/77b01fa7

Branch: refs/heads/master
Commit: 77b01fa7a5f2a3fbf5ad2e820c78c2f86283a8af
Parents: 219da64
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Thu Nov 5 12:33:43 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Nov 19 15:17:43 2015 +0530

----------------------------------------------------------------------
 ui/css/cloudstack3.css |  68 ++++++++++++++++++++++++++++++++++++++++++--
 ui/images/sprites.png  | Bin 194105 -> 198421 bytes
 2 files changed, 65 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/77b01fa7/ui/css/cloudstack3.css
----------------------------------------------------------------------
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 2ea8537..2a71ee8 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -146,7 +146,7 @@ table thead th.sorted.asc {
 
 table tbody td,
 table th {
-  padding: 10px 5px 8px;
+  padding: 10px 5px 6px;
   border-right: 1px solid #BFBFBF;
   color: #282828;
   clear: none;
@@ -1393,7 +1393,7 @@ div.list-view td.state span {
   -webkit-text-shadow: 0px 1px 1px #FFFFFF;
   -o-text-shadow: 0px 1px 1px #FFFFFF;
   text-shadow: 0px 1px 1px #FFFFFF;
-  background: url(../images/sprites.png) 1px -536px;
+  background: url(../images/sprites.png) 1px -526px;
 }
 
 div.list-view td.state.on span {
@@ -1407,7 +1407,69 @@ div.list-view td.state.off span {
   background-image: url(../images/sprites.png);
   background-repeat: no-repeat;
   color: #B90606;
-  background-position: 1px -496px;
+  background-position: 1px -492px;
+}
+
+div.list-view td.state.warning span {
+  background-image: url(../images/sprites.png);
+  background-repeat: no-repeat;
+  color: #B90606;
+  background-position: 1px -558px;
+}
+
+div.list-view td.state.transition span {
+  background-image: url(../images/sprites.png);
+  background-repeat: no-repeat;
+  color: #B90606;
+  background-position: 1px -432px;
+}
+
+.horizontal-overflow tbody td, .horizontal-overflow thead th {
+  min-width: 40px;
+  padding: 10px 10px 5px 0px;
+}
+
+.horizontal-overflow th.quick-view {
+  padding-left: 5px;
+}
+
+.groupable-header {
+  background: url(../images/bg-table-head.png);
+  border-left: 1px solid #C6C3C3;
+  border-right: 1px solid #C6C3C3;
+}
+
+.groupable-header-columns th {
+  border: none;
+}
+
+table.horizontal-overflow td.state {
+  width: 55px;
+  min-width: 55px;
+  max-width: 55px;
+}
+
+table.no-split td.first {
+  min-width: 150px;
+}
+
+.groupable-header-border {
+  border-left: 1px solid #C6C3C3;
+  border-right: 1px solid #C6C3C3;
+}
+
+td.alert-notification-threshold {
+  color: #E87900;
+  background-color: rgba(255, 231, 175, 0.75);
+}
+
+td.alert-disable-threshold {
+  color: #F50000;
+  background-color: rgba(255, 190, 190, 0.75);
+}
+
+span.compact {
+  height: 16px;
 }
 
 /** Quick view tooltip*/

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/77b01fa7/ui/images/sprites.png
----------------------------------------------------------------------
diff --git a/ui/images/sprites.png b/ui/images/sprites.png
index 1a6eaa5..0ddafaf 100755
Binary files a/ui/images/sprites.png and b/ui/images/sprites.png differ


[2/9] git commit: updated refs/heads/master to fe2917e

Posted by bh...@apache.org.
CLOUDSTACK-9020: Implement sorting for tables

Implements sorting for tables across CloudStack UI;
- General alphabetic/string based sorting
- Numeric sorting for columns if data appears numeric
- Special sorting comparator for state columns
- Avoids sorting quick view columns and other specific columns

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/106e9106
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/106e9106
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/106e9106

Branch: refs/heads/master
Commit: 106e9106c94ca9dc1e048c5d98b1ffa7b041c807
Parents: a48a224
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Thu Nov 5 12:36:42 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Nov 19 15:17:43 2015 +0530

----------------------------------------------------------------------
 ui/scripts/ui/widgets/dataTable.js | 91 ++++++++++++++++++++++++++-------
 1 file changed, 72 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/106e9106/ui/scripts/ui/widgets/dataTable.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui/widgets/dataTable.js b/ui/scripts/ui/widgets/dataTable.js
index 4c02531..22ddda6 100644
--- a/ui/scripts/ui/widgets/dataTable.js
+++ b/ui/scripts/ui/widgets/dataTable.js
@@ -141,49 +141,95 @@
          * @param columnIndex Index of column (starting at 0) to sort by
          */
         var sortTable = function(columnIndex) {
-            return false;
             var direction = 'asc';
 
-            if ($table.find('thead th').hasClass('sorted ' + direction)) {
+            if ($table.find('thead tr:last th').hasClass('sorted ' + direction)) {
                 direction = 'desc';
             }
 
-            $table.find('thead th').removeClass('sorted desc asc');
-            $($table.find('thead th')[columnIndex]).addClass('sorted').addClass(direction);
+            $table.find('thead tr:last th').removeClass('sorted desc asc');
+            $($table.find('thead tr:last th')[columnIndex]).addClass('sorted').addClass(direction);
 
             var $elems = $table.find('tbody td').filter(function() {
                 return $(this).index() == columnIndex;
             });
 
+            if ($elems.length < 2) {
+                return;
+            }
+
+            var stringComparator = function(a,b) {
+                return a.html().localeCompare(b.html());
+            };
+            var numericComparator = function(a,b) {
+                return parseFloat(a.children().html()) < parseFloat(b.children().html()) ? 1 : -1;
+            };
+            var stateComparator = function(a,b) {
+                return a.attr('title').localeCompare(b.attr('title'));
+            };
+            var isNumeric = function(obj) {
+                return !$.isArray(obj) && !isNaN(parseFloat(obj)) && isFinite(parseFloat(obj));
+            }
+
+            var comparator = stringComparator;
+            var hasAllRowsSameValue = true;
+            var firstElem = $($elems[0]).html();
             var sortData = [];
+            var numericDataCount = 0;
             $elems.each(function() {
-                sortData.push($(this).html());
-                sortData.sort();
-
-                if (direction == 'asc') {
-                    sortData.reverse();
+                var text = $(this).html();
+                if (hasAllRowsSameValue) {
+                    if (firstElem !== text) {
+                        hasAllRowsSameValue = false;
+                    }
+                }
+                if (isNumeric(text) || !text) {
+                    numericDataCount++;
                 }
+                sortData.push($(this));
             });
 
+            if ($($elems[0]).hasClass('state')) {
+                comparator = stateComparator;
+            } else {
+                if (hasAllRowsSameValue) {
+                    return;
+                }
+                if (columnIndex != 0 && numericDataCount > ($elems.length / 4)) {
+                    comparator = numericComparator;
+                }
+            }
+
+            sortData.sort(comparator);
+
+            if (direction == 'asc') {
+                sortData.reverse();
+            }
+
+            var elements = [];
             $(sortData).each(function() {
-                var sortKey = this;
-                var $targetCell = $elems.filter(function() {
-                    return $(this).html() == sortKey;
-                });
-                var $targetContainer = $targetCell.parent();
+                elements.push($(this).parent().clone(true));
+            });
 
-                $targetContainer.remove().appendTo($table.find('tbody'));
+            var $tbody = $table.find('tbody');
+            $tbody.empty();
+            $(elements).each(function() {
+                $(this).appendTo($tbody);
             });
 
             computeEvenOddRows();
         };
 
         var resizeHeaders = function() {
-            var $thead = $table.closest('div.data-table').find('thead');
+            var $thead = $table.hasClass('no-split') ? $table.find('thead') : $table.closest('div.data-table').find('thead');
             var $tbody = $table.find('tbody');
             var $ths = $thead.find('th');
             var $tds = $tbody.find('tr:first td');
 
+            if ($table.hasClass('no-split')) {
+                $tbody.width($thead.width());
+            }
+
             if ($ths.size() > $tds.size()) {
                 $ths.width(
                     $table.width() / $ths.size()
@@ -194,6 +240,10 @@
             $ths.each(function() {
                 var $th = $(this);
 
+                if ($th.hasClass('collapsible-column')) {
+                    return true;
+                }
+
                 var $td = $tds.filter(function() {
                     return $(this).index() == $th.index();
                 });
@@ -238,9 +288,12 @@
                 $table.find('tbody').closest('table').addClass('body');
             }
 
-            $table.find('th:not(:has(input))').bind('mousemove mouseout', hoverResizableEvent);
-            $table.find('th:not(:has(input))').bind('mousedown mousemove mouseup mouseout', resizeDragEvent);
-            $table.find('th:not(:has(input))').bind('click', function(event) {
+            if (!$table.hasClass('horizontal-overflow')) {
+                $table.find('th:not(:has(input))').bind('mousemove mouseout', hoverResizableEvent);
+                $table.find('th:not(:has(input))').bind('mousedown mousemove mouseup mouseout', resizeDragEvent);
+            }
+
+            $table.find('thead tr:last th:not(:has(input)):not(.collapsible-column):not(.quick-view)').unbind('click').bind('click', function(event) {
                 if ($(this).hasClass('resizable')) {
                     return false;
                 }


[3/9] git commit: updated refs/heads/master to fe2917e

Posted by bh...@apache.org.
CLOUDSTACK-9020: Method to remove last panel from the breadcrumb

Adds a new method to cloudBrowser that can remove the last panel and link/ref
from the breadcrumb

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/a48a224e
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/a48a224e
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/a48a224e

Branch: refs/heads/master
Commit: a48a224eaef1f13ac06addf85963f9f6e0f3b279
Parents: 77b01fa
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Thu Nov 5 12:35:25 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Nov 19 15:17:43 2015 +0530

----------------------------------------------------------------------
 ui/scripts/ui/widgets/cloudBrowser.js | 8 ++++++++
 1 file changed, 8 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a48a224e/ui/scripts/ui/widgets/cloudBrowser.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui/widgets/cloudBrowser.js b/ui/scripts/ui/widgets/cloudBrowser.js
index 007025b..b7a5c38 100644
--- a/ui/scripts/ui/widgets/cloudBrowser.js
+++ b/ui/scripts/ui/widgets/cloudBrowser.js
@@ -321,6 +321,14 @@
             return $panel;
         },
 
+        removeLastPanel: function(args) {
+            $('div.panel:last').stop(); // Prevent destroyed panels from animating
+            this.element.find('div.panel:last').remove();
+            this.element.find('div.panel:last').removeClass('reduced');
+            $('#breadcrumbs').find('ul li:last').remove();
+            $('#breadcrumbs').find('ul div.end').remove();
+        },
+
         /**
          * Clear all panels
          */


[6/9] git commit: updated refs/heads/master to fe2917e

Posted by bh...@apache.org.
CLOUDSTACK-9020: add ipaddress in instances view

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/459d6380
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/459d6380
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/459d6380

Branch: refs/heads/master
Commit: 459d6380b8b0fe7a8437df3c137b7ae5db4cddd3
Parents: 4830334
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Sat Nov 7 16:50:08 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Nov 19 15:17:44 2015 +0530

----------------------------------------------------------------------
 ui/scripts/instances.js | 8 ++++++++
 1 file changed, 8 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/459d6380/ui/scripts/instances.js
----------------------------------------------------------------------
diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js
index 8e21c81..2b7576c 100644
--- a/ui/scripts/instances.js
+++ b/ui/scripts/instances.js
@@ -170,6 +170,9 @@
                     label: 'label.display.name',
                     truncate: true
                 },
+                ipaddress: {
+                    label: 'label.ip.address'
+                },
                 zonename: {
                     label: 'label.zone.name'
                 },
@@ -394,6 +397,11 @@
                     data: data,
                     success: function(json) {
                         var items = json.listvirtualmachinesresponse.virtualmachine;
+                        $.each(items, function(idx, vm) {
+                            if (vm.nic && vm.nic.length > 0 && vm.nic[0].ipaddress) {
+                                items[idx].ipaddress = vm.nic[0].ipaddress;
+                            }
+                        });
                         args.response.success({
                             data: items
                         });


[5/9] git commit: updated refs/heads/master to fe2917e

Posted by bh...@apache.org.
CLOUDSTACK-9020: Increase UI container width by 200px

Based on suggestion from Lucian (Nux), this patch increases the UI's container
width by 200px as most modern resolutions on desktop/laptops/workstations are
at least 1400px wide. By increasing the width and adjusting css properties
throughout the UI, we get more space to show information. This also gets
rid of horizontal scrollbar in case of metrics views. This also, fixes the UI
logos to include our mascot 'cloudmonkey'.

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/aa609951
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/aa609951
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/aa609951

Branch: refs/heads/master
Commit: aa6099515d3efc9b885d69c47333cf25502cb2de
Parents: 459d638
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Sat Nov 7 16:51:09 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Nov 19 15:17:44 2015 +0530

----------------------------------------------------------------------
 ui/css/cloudstack3.css       |  91 +++++++++++++++++++-------------------
 ui/images/logo-login-oss.png | Bin 22165 -> 10864 bytes
 ui/images/logo.png           | Bin 21781 -> 9257 bytes
 3 files changed, 46 insertions(+), 45 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/aa609951/ui/css/cloudstack3.css
----------------------------------------------------------------------
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 1a69edb..22f7409 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -46,7 +46,7 @@ div.toolbar,
 
 /*+}*/
 body {
-  min-width: 1024px;
+  min-width: 1224px;
   font-family: sans-serif;
   overflow: auto;
   background: #EDE8E8;
@@ -61,7 +61,7 @@ body.install-wizard {
 }
 
 #main-area {
-  width: 1024px;
+  width: 1224px;
   height: 729px;
   margin: auto;
   border: 1px solid #D4D4D4;
@@ -96,8 +96,8 @@ a:hover {
 
 /*Table*/
 table {
-  width: 740px;
-  max-width: 777px;
+  width: 940px;
+  max-width: 977px;
   margin: 15px 15px 12px 12px;
   font-size: 13px;
   text-align: left;
@@ -480,8 +480,8 @@ body.login {
 }
 
 .login .logo {
-  width: 250px;
-  height: 31px;
+  width: 290px;
+  height: 40px;
   float: left;
   margin: 72px 0 0 209px;
   background: url(../images/logo-login.png) no-repeat 0 0;
@@ -1297,7 +1297,7 @@ div.panel div.list-view {
 }
 
 .detail-view div.list-view {
-  width: 730px;
+  width: 930px;
   border: 1px solid #DAD4D4;
   margin: 41px auto auto !important;
   height: 536px !important;
@@ -1305,12 +1305,12 @@ div.panel div.list-view {
 }
 
 div.panel div.list-view div.data-table table {
-  width: 755px;
+  width: 955px;
   margin-top: 44px;
 }
 
 .detail-view div.list-view div.data-table table {
-  width: 703px !important;
+  width: 903px !important;
 }
 
 .detail-view div.list-view div.data-table table td {
@@ -1321,7 +1321,7 @@ div.panel div.list-view div.fixed-header {
   position: absolute;
   top: 29px;
   left: 12px;
-  width: 760px;
+  width: 960px;
   height: 47px;
   display: table;
   background-color: #F7F7F7;
@@ -1330,9 +1330,9 @@ div.panel div.list-view div.fixed-header {
 }
 
 .detail-view div.list-view div.fixed-header {
-  width: 703px !important;
+  width: 903px !important;
   top: 49px !important;
-  left: 32px !important;
+  left: 29px !important;
   background: #FFFFFF;
 }
 
@@ -1354,7 +1354,7 @@ div.panel div.list-view div.fixed-header table {
   position: relative;
   left: 0px;
   top: 18px;
-  width: 755px;
+  width: 955px;
   /*+box-shadow:0px 4px 10px #DFE1E3;*/
   -moz-box-shadow: 0px 4px 10px #DFE1E3;
   -webkit-box-shadow: 0px 4px 10px #DFE1E3;
@@ -1890,7 +1890,7 @@ span.compact {
 }
 
 .detail-group table {
-  width: 96%;
+  width: 98%;
   font-size: 12px;
   border-bottom: 1px solid #DFDFDF;
   filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f7f7f7', endColorstr='#eaeaea',GradientType=0 );
@@ -2435,7 +2435,7 @@ div.detail-group.actions td {
 }
 
 #header.nologo div.logo {
-  width: 1024px;
+  width: 1224px;
   height: 47px;
   margin: auto;
   background: url(../images/logo.png) no-repeat 0 center;
@@ -2446,7 +2446,7 @@ div.detail-group.actions td {
 }
 
 #header div.controls {
-  width: 1026px;
+  width: 1226px;
   height: 48px;
   position: relative;
   margin: 27px auto 0;
@@ -2554,7 +2554,7 @@ div.detail-group.actions td {
   margin: 0;
   position: absolute;
   top: -47px;
-  left: 890px;
+  left: 1090px;
   cursor: default !important;
   display: inline-block;
   float: left;
@@ -2944,9 +2944,9 @@ div.detail-group.actions td {
 
 /*Browser*/
 #browser {
-  width: 794px;
+  width: 994px;
   height: 100%;
-  max-width: 794px;
+  max-width: 994px;
   position: relative;
   float: left;
   overflow: hidden;
@@ -3068,7 +3068,7 @@ div.detail-group.actions td {
 }
 
 .detail-view .ui-tabs-panel div.toolbar {
-  width: 768px;
+  width: 968px;
   background: transparent;
   border: none;
   margin-top: 8px;
@@ -3234,7 +3234,7 @@ div.toolbar div.button.main-action span.icon {
 
 div.toolbar div.button.refresh {
   float: right;
-  margin: 0 15px 0 0;
+  margin: 0 20px 0 0;
 }
 
 div.toolbar div.button.refresh span {
@@ -4357,7 +4357,7 @@ textarea {
 }
 
 .dashboard.admin .dashboard-container.sub {
-  width: 368px;
+  width: 468px;
 }
 
 .dashboard.admin .dashboard-container.sub .button.view-all,
@@ -4415,7 +4415,7 @@ textarea {
 
 /**** Head*/
 .dashboard.admin .dashboard-container.head {
-  width: 766px;
+  width: 966px;
   height: 331px;
   margin: 9px 0 0;
   float: left;
@@ -4475,7 +4475,7 @@ textarea {
 
 /**** Charts / stats*/
 .dashboard.admin .zone-stats {
-  width: 774px;
+  width: 974px;
   height: 316px;
   overflow: auto;
   overflow-x: hidden;
@@ -4486,7 +4486,7 @@ textarea {
 }
 
 .dashboard.admin .zone-stats ul {
-  width: 796px;
+  width: 996px;
   /*+placement:shift -2px 11px;*/
   position: relative;
   left: -2px;
@@ -4494,7 +4494,7 @@ textarea {
 }
 
 .dashboard.admin .zone-stats ul li {
-  width: 388px;
+  width: 488px;
   font-size: 14px;
   height: 79px;
   float: left;
@@ -4514,7 +4514,7 @@ textarea {
 }
 
 .dashboard.admin .zone-stats ul li .label {
-  width: 111px;
+  width: 161px;
   float: left;
   font-weight: 100;
   border-bottom: 1px solid #E2E2E2;
@@ -4685,7 +4685,7 @@ textarea {
 }
 
 .dashboard.admin .dashboard-container.sub.alerts ul {
-  width: 368px;
+  width: 468px;
   height: 234px;
   overflow: auto;
   overflow-x: hidden;
@@ -5145,7 +5145,7 @@ textarea {
 }
 
 .system-chart.dashboard.admin .dashboard-container {
-  width: 740px;
+  width: 930px;
   border: none;
 }
 
@@ -5166,7 +5166,7 @@ textarea {
 }
 
 .system-chart.dashboard.admin .dashboard-container .stats .chart {
-  width: 136px;
+  width: 300px;
 }
 
 /** Compute*/
@@ -5183,7 +5183,7 @@ textarea {
 }
 
 .system-chart.compute ul.resources li.zone {
-  left: 96px;
+  left: 196px;
 }
 
 .system-chart.compute ul.resources li.zone .label {
@@ -5195,32 +5195,32 @@ textarea {
 }
 
 .system-chart.compute ul.resources li.pods {
-  left: 199px;
+  left: 299px;
   top: 112px;
 }
 
 .system-chart.compute ul.resources li.clusters {
-  left: 296px;
+  left: 396px;
   top: 189px;
 }
 
 .system-chart.compute ul.resources li.hosts {
-  left: 407px;
+  left: 507px;
   top: 265px;
 }
 
 .system-chart.compute ul.resources li.primaryStorage {
-  left: 407px;
+  left: 507px;
   top: 375px;
 }
 
 .system-chart.compute ul.resources li.secondaryStorage {
-  left: 199px;
+  left: 299px;
   top: 497px;
 }
 
 .system-chart.compute ul.resources li.ucs {
-  left: 199px;
+  left: 299px;
   top: 406px;
 }
 
@@ -8141,6 +8141,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal
 .detail-view .multi-edit select {
   width: 93%;
   font-size: 10px;
+  min-width: 80px;
 }
 
 .multi-edit input {
@@ -9079,7 +9080,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal
 .network-chart li.firewall {
   /*+placement:shift 282px 188px;*/
   position: relative;
-  left: 282px;
+  left: 356px;
   top: 188px;
   position: absolute;
 }
@@ -9087,7 +9088,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal
 .network-chart li.loadBalancing {
   /*+placement:shift 167px 342px;*/
   position: relative;
-  left: 167px;
+  left: 237px;
   top: 342px;
   position: absolute;
 }
@@ -9095,7 +9096,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal
 .network-chart li.portForwarding {
   /*+placement:shift 401px 342px;*/
   position: relative;
-  left: 401px;
+  left: 480px;
   top: 342px;
   position: absolute;
 }
@@ -9172,7 +9173,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal
 /*System Dashboard*/
 .system-dashboard {
   height: 258px;
-  width: 762px;
+  width: 962px;
   display: block;
   /*+border-radius:3px;*/
   -moz-border-radius: 3px;
@@ -9270,7 +9271,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal
   position: relative;
   left: 18px;
   top: 110px;
-  width: 78%;
+  width: 83%;
   position: absolute;
   text-align: center;
   padding: 8px 0;
@@ -9285,7 +9286,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal
 
 .system-dashboard .status_box li {
   height: 178px;
-  width: 178px;
+  width: 228px;
   padding: 0;
   margin: 0 0 0 8px;
   /*+border-radius:3px;*/
@@ -9309,7 +9310,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal
   padding: 65px 80px 5px;
   /*+placement:shift 31px 19px;*/
   position: relative;
-  left: 31px;
+  left: 51px;
   top: 19px;
   position: absolute;
   /*+opacity:56%;*/
@@ -9346,7 +9347,7 @@ div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBal
   /*+placement:shift 13px 5px;*/
   position: relative;
   left: 13px;
-  top: 5px;
+  top: 13px;
   font-weight: 100;
 }
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/aa609951/ui/images/logo-login-oss.png
----------------------------------------------------------------------
diff --git a/ui/images/logo-login-oss.png b/ui/images/logo-login-oss.png
index e0f3767..92fc81c 100644
Binary files a/ui/images/logo-login-oss.png and b/ui/images/logo-login-oss.png differ

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/aa609951/ui/images/logo.png
----------------------------------------------------------------------
diff --git a/ui/images/logo.png b/ui/images/logo.png
index f36a9cb..2e3aae9 100644
Binary files a/ui/images/logo.png and b/ui/images/logo.png differ


[9/9] git commit: updated refs/heads/master to fe2917e

Posted by bh...@apache.org.
Merge pull request #1038 from shapeblue/metrics-master

Metrics views for CloudStack UIFS: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Metrics+Views+for+CloudStack+UI
JIRA: https://issues.apache.org/jira/browse/CLOUDSTACK-9020

* pr/1038:
  CLOUDSTACK-9020: Increase UI container width by 200px
  CLOUDSTACK-9020: add ipaddress in instances view
  CLOUDSTACK-9020: Make UI pagesize configurable
  CLOUDSTACK-9020: Metrics views for CloudStack UI
  CLOUDSTACK-9020: Implement collapsible columns and threshold colorings
  CLOUDSTACK-9020: Implement sorting for tables
  CLOUDSTACK-9020: Method to remove last panel from the breadcrumb
  CLOUDSTACK-9020: Add new status icons and css rules

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/fe2917e9
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/fe2917e9
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/fe2917e9

Branch: refs/heads/master
Commit: fe2917e91bc065b742f90cbdddbf631ac9dd9111
Parents: 219da64 aa60995
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Thu Nov 19 16:52:42 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Nov 19 16:52:42 2015 +0530

----------------------------------------------------------------------
 .../config/ApiServiceConfiguration.java         |    4 +-
 .../classes/resources/messages.properties       |   35 +
 ui/css/cloudstack3.css                          |  175 ++-
 ui/dictionary.jsp                               |   35 +
 ui/images/logo-login-oss.png                    |  Bin 22165 -> 10864 bytes
 ui/images/logo.png                              |  Bin 21781 -> 9257 bytes
 ui/images/sprites.png                           |  Bin 194105 -> 198421 bytes
 ui/index.jsp                                    |    2 +
 ui/scripts/cloudStack.js                        |   20 +
 ui/scripts/instances.js                         |   23 +-
 ui/scripts/metrics.js                           | 1121 ++++++++++++++++++
 ui/scripts/storage.js                           |   14 +
 ui/scripts/system.js                            |   68 +-
 ui/scripts/ui-custom/metricsView.js             |  140 +++
 ui/scripts/ui/widgets/cloudBrowser.js           |    8 +
 ui/scripts/ui/widgets/dataTable.js              |   96 +-
 ui/scripts/ui/widgets/listView.js               |  204 +++-
 17 files changed, 1852 insertions(+), 93 deletions(-)
----------------------------------------------------------------------



[7/9] git commit: updated refs/heads/master to fe2917e

Posted by bh...@apache.org.
CLOUDSTACK-9020: Make UI pagesize configurable

Add global setting that can be consumed by UI to make its pagesize for list API
calls dynamic with default to 100.

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/4830334e
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/4830334e
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/4830334e

Branch: refs/heads/master
Commit: 4830334e79171d03705eb9c2867176e30f16bf43
Parents: ad59283
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Thu Nov 5 12:34:26 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Nov 19 15:17:44 2015 +0530

----------------------------------------------------------------------
 .../config/ApiServiceConfiguration.java         |  4 +++-
 ui/scripts/cloudStack.js                        | 20 ++++++++++++++++++++
 2 files changed, 23 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4830334e/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java b/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java
index 94c0a55..701af62 100644
--- a/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java
+++ b/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java
@@ -26,6 +26,8 @@ public class ApiServiceConfiguration implements Configurable {
     public static final ConfigKey<String> ManagementHostIPAdr = new ConfigKey<String>("Advanced", String.class, "host", "localhost", "The ip address of management server", true);
     public static final ConfigKey<String> ApiServletPath = new ConfigKey<String>("Advanced", String.class, "endpointe.url", "http://localhost:8080/client/api",
             "API end point. Can be used by CS components/services deployed remotely, for sending CS API requests", true);
+    public static final ConfigKey<Long> DefaultUIPageSize = new ConfigKey<Long>("Advanced", Long.class, "default.ui.page.size", "20",
+            "The default pagesize to be used by UI and other clients when making list* API calls", true, ConfigKey.Scope.Global);
 
     @Override
     public String getConfigComponentName() {
@@ -34,7 +36,7 @@ public class ApiServiceConfiguration implements Configurable {
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] {ManagementHostIPAdr, ApiServletPath};
+        return new ConfigKey<?>[] {ManagementHostIPAdr, ApiServletPath, DefaultUIPageSize};
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4830334e/ui/scripts/cloudStack.js
----------------------------------------------------------------------
diff --git a/ui/scripts/cloudStack.js b/ui/scripts/cloudStack.js
index 8137043..b5aa94a 100644
--- a/ui/scripts/cloudStack.js
+++ b/ui/scripts/cloudStack.js
@@ -165,6 +165,26 @@
                     }
                 });
 
+                // Update global pagesize for list APIs in UI
+                $.ajax({
+                    type: 'GET',
+                    url: createURL('listConfigurations'),
+                    data: {name: 'default.ui.page.size'},
+                    dataType: 'json',
+                    async: false,
+                    success: function(data, textStatus, xhr) {
+                        if (data && data.listconfigurationsresponse && data.listconfigurationsresponse.configuration) {
+                            var config = data.listconfigurationsresponse.configuration[0];
+                            if (config && config.name == 'default.ui.page.size') {
+                                pageSize = parseInt(config.value);
+                            }
+                        }
+                    },
+                    error: function(xhr) { // ignore any errors, fallback to the default
+                    },
+                });
+
+
                 // Populate IDP list
                 $.ajax({
                     type: 'GET',


[8/9] git commit: updated refs/heads/master to fe2917e

Posted by bh...@apache.org.
CLOUDSTACK-9020: Metrics views for CloudStack UI

Implements various metrics views based on a listView based widget that has following
properties:
  - vertically and horizontally scrollable with pagination/infinite scrolling
  - sortable columns (client side)
  - groupable/collapsible columns
  - alternate row coloring
  - refresh button to refresh views
  - threshold table cell coloring
  - panel/breadcrumb navigation
  - quick view action column
  - translatable labels
  - sorts after metrics is refreshed, if a column was previously sorted
  - sorts after adding rows on infinite scrolling if a column was pre-sorted
- Metrics views: Zones, Clusters, Hosts, Instances, Storage pools, Volumes
- Resource filtering/navigation: Zones->Clusters->Hosts->Instances->Volumes,
                                 Storage Pool->Volumes

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/ad592835
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/ad592835
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/ad592835

Branch: refs/heads/master
Commit: ad592835c8ef73ba92e7bea33dcd15181e99900f
Parents: 0845edc
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Thu Nov 5 12:44:14 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Nov 19 15:17:44 2015 +0530

----------------------------------------------------------------------
 .../classes/resources/messages.properties       |   35 +
 ui/css/cloudstack3.css                          |   16 +
 ui/dictionary.jsp                               |   35 +
 ui/index.jsp                                    |    2 +
 ui/scripts/instances.js                         |   15 +-
 ui/scripts/metrics.js                           | 1121 ++++++++++++++++++
 ui/scripts/storage.js                           |   14 +
 ui/scripts/system.js                            |   68 +-
 ui/scripts/ui-custom/metricsView.js             |  140 +++
 ui/scripts/ui/widgets/dataTable.js              |   11 +-
 10 files changed, 1447 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/client/WEB-INF/classes/resources/messages.properties
----------------------------------------------------------------------
diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties
index 6cafdb0..d3cc4d0 100644
--- a/client/WEB-INF/classes/resources/messages.properties
+++ b/client/WEB-INF/classes/resources/messages.properties
@@ -842,6 +842,41 @@ label.menu.virtual.appliances=Virtual Appliances
 label.menu.virtual.resources=Virtual Resources
 label.menu.volumes=Volumes
 label.menu.sshkeypair=SSH KeyPair
+label.metrics=Metrics
+label.metrics.allocated=Allocated
+label.metrics.clusters=Clusters
+label.metrics.cpu.allocated=CPU Allocation
+label.metrics.cpu.max.dev=Deviation
+label.metrics.cpu.total=Total
+label.metrics.cpu.usage=CPU Usage
+label.metrics.cpu.used.avg=Used
+label.metrics.disk=Disk
+label.metrics.disk.iops.total=IOPS
+label.metrics.disk.read=Read
+label.metrics.disk.size=Size
+label.metrics.disk.storagetype=Type
+label.metrics.disk.usage=Disk Usage
+label.metrics.disk.used=Used
+label.metrics.disk.total=Total
+label.metrics.disk.allocated=Allocated
+label.metrics.disk.unallocated=Unallocated
+label.metrics.disk.write=Write
+label.metrics.hosts=Hosts
+label.metrics.memory.allocated=Mem Allocation
+label.metrics.memory.max.dev=Deviation
+label.metrics.memory.total=Total
+label.metrics.memory.usage=Mem Usage
+label.metrics.memory.used.avg=Used
+label.metrics.name=Name
+label.metrics.network.usage=Network Usage
+label.metrics.network.read=Read
+label.metrics.network.write=Write
+label.metrics.num.cpu.cores=Cores
+label.metrics.property=Property
+label.metrics.scope=Scope
+label.metrics.state=State
+label.metrics.storagepool=Storage Pool
+label.metrics.vm.name=VM Name
 label.migrate.instance.to.host=Migrate instance to another host
 label.migrate.instance.to.ps=Migrate instance to another primary storage
 label.migrate.instance.to=Migrate instance to

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/ui/css/cloudstack3.css
----------------------------------------------------------------------
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 2a71ee8..1a69edb 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -12495,6 +12495,22 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it
   background-position: 0px -707px;
 }
 
+.viewMetrics .icon {
+  background-position: -40px -32px;
+}
+
+.viewMetrics:hover .icon {
+  background-position: -40px -32px;
+}
+
+.refreshMetrics .icon {
+  background-position: 0px -62px;
+}
+
+.refreshMetrics:hover .icon {
+  background-position: 0px -62px;
+}
+
 .attach .icon,
 .attachISO .icon,
 .attachDisk .icon,

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/ui/dictionary.jsp
----------------------------------------------------------------------
diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp
index ed280de..977cd7a 100644
--- a/ui/dictionary.jsp
+++ b/ui/dictionary.jsp
@@ -837,6 +837,41 @@ dictionary = {
 'label.menu.virtual.resources': '<fmt:message key="label.menu.virtual.resources" />',
 'label.menu.volumes': '<fmt:message key="label.menu.volumes" />',
 'label.menu.sshkeypair': '<fmt:message key="label.menu.sshkeypair" />',
+'label.metrics': '<fmt:message key="label.metrics" />',
+'label.metrics.allocated': '<fmt:message key="label.metrics.allocated" />',
+'label.metrics.clusters': '<fmt:message key="label.metrics.clusters" />',
+'label.metrics.cpu.allocated': '<fmt:message key="label.metrics.cpu.allocated" />',
+'label.metrics.cpu.max.dev': '<fmt:message key="label.metrics.cpu.max.dev" />',
+'label.metrics.cpu.total': '<fmt:message key="label.metrics.cpu.total" />',
+'label.metrics.cpu.usage': '<fmt:message key="label.metrics.cpu.usage" />',
+'label.metrics.cpu.used.avg': '<fmt:message key="label.metrics.cpu.used.avg" />',
+'label.metrics.disk': '<fmt:message key="label.metrics.disk" />',
+'label.metrics.disk.iops.total': '<fmt:message key="label.metrics.disk.iops.total" />',
+'label.metrics.disk.read': '<fmt:message key="label.metrics.disk.read" />',
+'label.metrics.disk.size': '<fmt:message key="label.metrics.disk.size" />',
+'label.metrics.disk.storagetype': '<fmt:message key="label.metrics.disk.storagetype" />',
+'label.metrics.disk.usage': '<fmt:message key="label.metrics.disk.usage" />',
+'label.metrics.disk.used': '<fmt:message key="label.metrics.disk.used" />',
+'label.metrics.disk.total': '<fmt:message key="label.metrics.disk.total" />',
+'label.metrics.disk.allocated': '<fmt:message key="label.metrics.disk.allocated" />',
+'label.metrics.disk.unallocated': '<fmt:message key="label.metrics.disk.unallocated" />',
+'label.metrics.disk.write': '<fmt:message key="label.metrics.disk.write" />',
+'label.metrics.hosts': '<fmt:message key="label.metrics.hosts" />',
+'label.metrics.memory.allocated': '<fmt:message key="label.metrics.memory.allocated" />',
+'label.metrics.memory.max.dev': '<fmt:message key="label.metrics.memory.max.dev" />',
+'label.metrics.memory.total': '<fmt:message key="label.metrics.memory.total" />',
+'label.metrics.memory.usage': '<fmt:message key="label.metrics.memory.usage" />',
+'label.metrics.memory.used.avg': '<fmt:message key="label.metrics.memory.used.avg" />',
+'label.metrics.name': '<fmt:message key="label.metrics.name" />',
+'label.metrics.network.read': '<fmt:message key="label.metrics.network.read" />',
+'label.metrics.network.usage': '<fmt:message key="label.metrics.network.usage" />',
+'label.metrics.network.write': '<fmt:message key="label.metrics.network.write" />',
+'label.metrics.num.cpu.cores': '<fmt:message key="label.metrics.num.cpu.cores" />',
+'label.metrics.property': '<fmt:message key="label.metrics.property" />',
+'label.metrics.scope': '<fmt:message key="label.metrics.scope" />',
+'label.metrics.state': '<fmt:message key="label.metrics.state" />',
+'label.metrics.storagepool': '<fmt:message key="label.metrics.storagepool" />',
+'label.metrics.vm.name': '<fmt:message key="label.metrics.vm.name" />',
 'label.migrate.instance.to': '<fmt:message key="label.migrate.instance.to" />',
 'label.migrate.instance.to.host': '<fmt:message key="label.migrate.instance.to.host" />',
 'label.migrate.instance.to.ps': '<fmt:message key="label.migrate.instance.to.ps" />',

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/ui/index.jsp
----------------------------------------------------------------------
diff --git a/ui/index.jsp b/ui/index.jsp
index 302e464..703507f 100644
--- a/ui/index.jsp
+++ b/ui/index.jsp
@@ -1800,6 +1800,7 @@
         <script type="text/javascript" src="scripts/ui-custom/granularSettings.js"></script>
         <script type="text/javascript" src="scripts/ui-custom/zoneChart.js"></script>
         <script type="text/javascript" src="scripts/ui-custom/dashboard.js"></script>
+        <script type="text/javascript" src="scripts/ui-custom/metricsView.js"></script>
         <script type="text/javascript" src="scripts/installWizard.js"></script>
         <script type="text/javascript" src="scripts/ui-custom/installWizard.js"></script>
         <script type="text/javascript" src="scripts/projects.js"></script>
@@ -1836,6 +1837,7 @@
         <script type="text/javascript" src="scripts/vm_snapshots.js"></script>
         <script type="text/javascript" src="scripts/ui-custom/projectSelect.js"></script>
         <script type="text/javascript" src="scripts/ui-custom/saml.js"></script>
+        <script type="text/javascript" src="scripts/metrics.js"></script>
 
         <!-- Plugin/module API -->
         <script type="text/javascript" src="scripts/ui-custom/pluginListing.js"></script>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/ui/scripts/instances.js
----------------------------------------------------------------------
diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js
index 5096297..8e21c81 100644
--- a/ui/scripts/instances.js
+++ b/ui/scripts/instances.js
@@ -294,7 +294,20 @@
                         poll: pollAsyncJobResult
                     }
                 },
-                snapshot: vmSnapshotAction({ listView: true })
+                snapshot: vmSnapshotAction({ listView: true }),
+                viewMetrics: {
+                    label: 'label.metrics',
+                    isHeader: true,
+                    addRow: false,
+                    action: {
+                        custom: cloudStack.uiCustom.metricsView({resource: 'vms'})
+                    },
+                    messages: {
+                        notification: function (args) {
+                            return 'label.metrics';
+                        }
+                    }
+                },
             },
 
             dataProvider: function(args) {

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/ui/scripts/metrics.js
----------------------------------------------------------------------
diff --git a/ui/scripts/metrics.js b/ui/scripts/metrics.js
new file mode 100644
index 0000000..609022a
--- /dev/null
+++ b/ui/scripts/metrics.js
@@ -0,0 +1,1121 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+(function(cloudStack) {
+    cloudStack.sections.metrics = {
+        title: 'label.metrics',
+        listView: {
+            id: 'metrics',
+            fields: {
+                name: {
+                    label: 'metrics'
+                }
+            },
+        }
+    };
+
+
+    // Zones Metrics
+    cloudStack.sections.metrics.zones = {
+        title: 'label.metrics',
+        listView: {
+            id: 'physicalResources',
+            fields: {
+                name: {
+                    label: 'label.metrics.name'
+                },
+                state: {
+                    label: 'label.metrics.state',
+                    converter: function (str) {
+                        // For localization
+                        return str;
+                    },
+                    indicator: {
+                        'Enabled': 'on',
+                        'Disabled': 'off'
+                    },
+                    compact: true
+                },
+                clusters : {
+                    label: 'label.metrics.clusters'
+                },
+                cpuused: {
+                    label: 'label.metrics.cpu.usage',
+                    collapsible: true,
+                    columns: {
+                        cpuusedavg: {
+                            label: 'label.metrics.cpu.used.avg',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'cpunotificationthreshold',
+                                disable: 'cpudisablethreshold'
+                            }
+                        },
+                        cpumaxdev: {
+                            label: 'label.metrics.cpu.max.dev'
+                        }
+                    }
+                },
+                cpuallocated: {
+                    label: 'label.metrics.cpu.allocated',
+                    collapsible: true,
+                    columns: {
+                        cpuallocated: {
+                            label: 'label.metrics.allocated',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'cpunotificationthreshold',
+                                disable: 'cpudisablethreshold'
+                            }
+                        },
+                        cputotal: {
+                            label: 'label.metrics.cpu.total'
+                        }
+                    }
+                },
+                memused: {
+                    label: 'label.metrics.memory.usage',
+                    collapsible: true,
+                    columns: {
+                        memusedavg: {
+                            label: 'label.metrics.memory.used.avg',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'memnotificationthreshold',
+                                disable: 'memdisablethreshold'
+                            }
+                        },
+                        memmaxdev: {
+                            label: 'label.metrics.memory.max.dev'
+                        }
+                    }
+                },
+                memallocated: {
+                    label: 'label.metrics.memory.allocated',
+                    collapsible: true,
+                    columns: {
+                        memallocated: {
+                            label: 'label.metrics.allocated',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'memnotificationthreshold',
+                                disable: 'memdisablethreshold'
+                            }
+                        },
+                        memtotal: {
+                            label: 'label.metrics.memory.total'
+                        }
+                    }
+                }
+            },
+            dataProvider: function(args) {
+                var data = {};
+                listViewDataProvider(args, data);
+                $.ajax({
+                    url: createURL('listZones'),
+                    data: data,
+                    success: function(json) {
+                        var items = json.listzonesresponse.zone;
+                        if (items) {
+                            $.each(items, function(idx, zone) {
+                                items[idx].clusters = 0;
+                                items[idx].clustersUp = 0;
+                                items[idx].hosts = 0;
+                                items[idx].cpuusedavg = 0.0;
+                                items[idx].cpumaxdev = 0.0;
+                                items[idx].cpuallocated = 0.0;
+                                items[idx].cputotal = 0.0;
+                                items[idx].maxCpuUsed = 0.0;
+                                items[idx].memusedavg = 0.0;
+                                items[idx].memmaxdev = 0.0;
+                                items[idx].memallocated = 0.0;
+                                items[idx].memtotal = 0.0;
+                                items[idx].maxMemUsed = 0.0;
+
+                                // Threshold color coding
+                                items[idx].cpunotificationthreshold = 75.0;
+                                items[idx].cpudisablethreshold = 95.0;
+                                items[idx].memnotificationthreshold = 75.0;
+                                items[idx].memdisablethreshold = 95.0;
+
+                                $.ajax({
+                                    url: createURL('listClusters'),
+                                    data: {zoneid: zone.id},
+                                    success: function(json) {
+                                        if (json && json.listclustersresponse && json.listclustersresponse.cluster && json.listclustersresponse.count) {
+                                            items[idx].clusters += parseInt(json.listclustersresponse.count);
+                                            $.each(json.listclustersresponse.cluster, function(i, cluster) {
+                                                if (cluster.allocationstate == 'Enabled' && cluster.managedstate == 'Managed') {
+                                                    items[idx].clustersUp += 1;
+                                                }
+                                                $.ajax({
+                                                    url: createURL('listHosts'),
+                                                    data: {clusterid: cluster.id, type: 'routing'},
+                                                    success: function(json) {
+                                                        if (json && json.listhostsresponse && json.listhostsresponse.host && json.listhostsresponse.count) {
+                                                            items[idx].hosts += parseInt(json.listhostsresponse.count);
+                                                            $.each(json.listhostsresponse.host, function(i, host) {
+                                                                if (host.hasOwnProperty('cpuused')) {
+                                                                    var hostCpuUsage = parseFloat(host.cpuused);
+                                                                    items[idx].cpuusedavg += hostCpuUsage;
+                                                                    if (hostCpuUsage > items[idx].maxCpuUsed) {
+                                                                        items[idx].maxCpuUsed = hostCpuUsage;
+                                                                    }
+                                                                }
+
+                                                                if (host.hasOwnProperty('cpuallocated')) {
+                                                                    items[idx].cpuallocated += parseFloat(host.cpuallocated.replace('%', ''));
+                                                                }
+
+                                                                if (host.hasOwnProperty('memoryused')) {
+                                                                    var hostMemoryUsage = 100.0 * parseFloat(host.memoryused) /  parseFloat(host.memorytotal);
+                                                                    items[idx].memusedavg += hostMemoryUsage;
+                                                                    if (hostMemoryUsage > items[idx].maxMemUsed) {
+                                                                        items[idx].maxMemUsed = hostMemoryUsage;
+                                                                    }
+                                                                }
+
+                                                                if (host.hasOwnProperty('memoryallocated')) {
+                                                                    items[idx].memallocated += parseFloat(100.0 * parseFloat(host.memoryallocated)/parseFloat(host.memorytotal));
+                                                                }
+                                                            });
+                                                        }
+                                                    },
+                                                    async: false
+                                                });
+                                            });
+                                        }
+                                    },
+                                    async: false
+                                });
+
+                                $.ajax({
+                                    url: createURL('listCapacity'),
+                                    data: {zoneid: zone.id},
+                                    success: function(json) {
+                                        if (json && json.listcapacityresponse && json.listcapacityresponse.capacity) {
+                                            $.each(json.listcapacityresponse.capacity, function(i, capacity) {
+                                                // CPU
+                                                if (capacity.type == 1) {
+                                                    items[idx].cputotal = parseInt(capacity.capacitytotal)/1000.0;
+                                                }
+                                                // Memory
+                                                if (capacity.type == 0) {
+                                                    items[idx].memtotal = parseInt(capacity.capacitytotal)/(1024.0*1024.0*1024.0);
+                                                }
+                                            });
+                                        }
+                                    },
+                                    async: false
+                                });
+
+                                if (items[idx].hosts != 0) {
+                                    items[idx].cpuusedavg = (items[idx].cpuusedavg / items[idx].hosts);
+                                    items[idx].cpumaxdev = (items[idx].maxCpuUsed - items[idx].cpuusedavg);
+                                    items[idx].cpuallocated = (items[idx].cpuallocated / items[idx].hosts);
+
+                                    items[idx].memusedavg = (items[idx].memusedavg / items[idx].hosts);
+                                    items[idx].memmaxdev = (items[idx].maxMemUsed - items[idx].memusedavg);
+                                    items[idx].memallocated = (items[idx].memallocated / items[idx].hosts);
+                                }
+                                // Format data
+                                items[idx].cpuusedavg = (items[idx].cpuusedavg).toFixed(2) + "%";
+                                items[idx].cpumaxdev = (items[idx].cpumaxdev).toFixed(2) + "%";
+                                items[idx].cpuallocated = (items[idx].cpuallocated).toFixed(2) + "%";
+                                items[idx].cputotal = (items[idx].cputotal).toFixed(2) + " Ghz";
+
+                                items[idx].memusedavg = (items[idx].memusedavg).toFixed(2) + "%";
+                                items[idx].memmaxdev = (items[idx].memmaxdev).toFixed(2) + "%";
+                                items[idx].memallocated = (items[idx].memallocated).toFixed(2) + "%";
+                                items[idx].memtotal = (items[idx].memtotal).toFixed(2) + " GB";
+
+                                items[idx].clusters = items[idx].clustersUp + ' / ' + items[idx].clusters;
+                                items[idx].state = items[idx].allocationstate;
+                            });
+                        }
+                        args.response.success({
+                            data: items
+                        });
+                    }
+                });
+            },
+            browseBy: {
+                filterBy: 'zoneid',
+                resource: 'clusters'
+            },
+            detailView: cloudStack.sections.system.physicalResourceSection.sections.physicalResources.listView.zones.detailView
+        }
+    };
+
+
+    // Clusters Metrics
+    cloudStack.sections.metrics.clusters = {
+        title: 'label.metrics',
+        listView: {
+            id: 'clusters',
+            fields: {
+                name: {
+                    label: 'label.metrics.name'
+                },
+                state: {
+                    label: 'label.metrics.state',
+                    converter: function (str) {
+                        // For localization
+                        return str;
+                    },
+                    indicator: {
+                        'Enabled': 'on',
+                        'Unmanaged': 'warning',
+                        'Disabled': 'off'
+                    },
+                    compact: true
+                },
+                hosts: {
+                    label: 'label.metrics.hosts'
+                },
+                cpuused: {
+                    label: 'label.metrics.cpu.usage',
+                    collapsible: true,
+                    columns: {
+                        cpuusedavg: {
+                            label: 'label.metrics.cpu.used.avg',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'cpunotificationthreshold',
+                                disable: 'cpudisablethreshold'
+                            }
+                        },
+                        cpumaxdev: {
+                            label: 'label.metrics.cpu.max.dev'
+                        }
+                    }
+                },
+                cpuallocated: {
+                    label: 'label.metrics.cpu.allocated',
+                    collapsible: true,
+                    columns: {
+                        cpuallocated: {
+                            label: 'label.metrics.allocated',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'cpunotificationthreshold',
+                                disable: 'cpudisablethreshold'
+                            }
+                        },
+                        cputotal: {
+                            label: 'label.metrics.cpu.total'
+                        }
+                    }
+                },
+                memused: {
+                    label: 'label.metrics.memory.usage',
+                    collapsible: true,
+                    columns: {
+                        memusedavg: {
+                            label: 'label.metrics.memory.used.avg',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'memnotificationthreshold',
+                                disable: 'memdisablethreshold'
+                            }
+                        },
+                        memmaxdev: {
+                            label: 'label.metrics.memory.max.dev'
+                        }
+                    }
+                },
+                memallocated: {
+                    label: 'label.metrics.memory.allocated',
+                    collapsible: true,
+                    columns: {
+                        memallocated: {
+                            label: 'label.metrics.allocated',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'memnotificationthreshold',
+                                disable: 'memdisablethreshold'
+                            }
+                        },
+                        memtotal: {
+                            label: 'label.metrics.memory.total'
+                        }
+                    }
+                }
+            },
+            dataProvider: function(args) {
+                var data = {};
+                listViewDataProvider(args, data);
+                if (args.context.metricsFilterData && args.context.metricsFilterData.key && args.context.metricsFilterData.value) {
+                    data[args.context.metricsFilterData.key] = args.context.metricsFilterData.value;
+                }
+                $.ajax({
+                    url: createURL('listClusters'),
+                    data: data,
+                    success: function(json) {
+                        var items = json.listclustersresponse.cluster;
+                        if (items) {
+                            $.each(items, function(idx, cluster) {
+                                items[idx].hosts = 0;
+                                items[idx].hostsUp = 0;
+                                items[idx].cpuusedavg = 0.0;
+                                items[idx].cpumaxdev = 0.0;
+                                items[idx].cpuallocated = 0.0;
+                                items[idx].cputotal = 0.0;
+                                items[idx].maxCpuUsed = 0;
+                                items[idx].memusedavg = 0.0;
+                                items[idx].memmaxdev = 0.0;
+                                items[idx].memallocated = 0.0;
+                                items[idx].memtotal = 0.0;
+                                items[idx].maxMemUsed = 0.0;
+
+                                // Threshold color coding
+                                items[idx].cpunotificationthreshold = 75.0;
+                                items[idx].cpudisablethreshold = 95.0;
+                                items[idx].memnotificationthreshold = 75.0;
+                                items[idx].memdisablethreshold = 95.0;
+
+                                $.ajax({
+                                    url: createURL('listConfigurations'),
+                                    data: {clusterid: cluster.id, listAll: true},
+                                    success: function(json) {
+                                        if (json.listconfigurationsresponse && json.listconfigurationsresponse.configuration) {
+                                            $.each(json.listconfigurationsresponse.configuration, function(i, config) {
+                                                switch (config.name) {
+                                                    case 'cluster.cpu.allocated.capacity.disablethreshold':
+                                                        items[idx].cpudisablethreshold = 100 * parseFloat(config.value);
+                                                        break;
+                                                    case 'cluster.cpu.allocated.capacity.notificationthreshold':
+                                                        items[idx].cpunotificationthreshold = 100 * parseFloat(config.value);
+                                                        break;
+                                                    case 'cluster.memory.allocated.capacity.disablethreshold':
+                                                        items[idx].memdisablethreshold = 100 * parseFloat(config.value);
+                                                        break;
+                                                    case 'cluster.memory.allocated.capacity.notificationthreshold':
+                                                        items[idx].memnotificationthreshold = 100 * parseFloat(config.value);
+                                                        break;
+                                                }
+                                            });
+                                        }
+                                    },
+                                    async: false
+                                });
+
+                                $.ajax({
+                                    url: createURL('listHosts'),
+                                    data: {clusterid: cluster.id, type: 'routing'},
+                                    success: function(json) {
+                                        if (json && json.listhostsresponse && json.listhostsresponse.host && json.listhostsresponse.count) {
+                                            items[idx].hosts += parseInt(json.listhostsresponse.count);
+                                            $.each(json.listhostsresponse.host, function(i, host) {
+                                                if (host.state == 'Up') {
+                                                    items[idx].hostsUp += 1;
+                                                }
+                                                if (host.hasOwnProperty('cpuused')) {
+                                                    var hostCpuUsage = parseFloat(host.cpuused);
+                                                    items[idx].cpuusedavg += hostCpuUsage;
+                                                    if (hostCpuUsage > items[idx].maxCpuUsed) {
+                                                        items[idx].maxCpuUsed = hostCpuUsage;
+                                                    }
+                                                }
+
+                                                if (host.hasOwnProperty('cpuallocated')) {
+                                                    items[idx].cpuallocated += parseFloat(host.cpuallocated.replace('%', ''));
+                                                }
+
+                                                if (host.hasOwnProperty('memoryused')) {
+                                                    var hostMemoryUsage = 100.0 * parseFloat(host.memoryused) /  parseFloat(host.memorytotal);
+                                                    items[idx].memusedavg += hostMemoryUsage;
+                                                    if (hostMemoryUsage > items[idx].maxMemUsed) {
+                                                        items[idx].maxMemUsed = hostMemoryUsage;
+                                                    }
+                                                }
+
+                                                if (host.hasOwnProperty('memoryallocated')) {
+                                                    items[idx].memallocated += parseFloat(100.0 * parseFloat(host.memoryallocated)/parseFloat(host.memorytotal));
+                                                }
+                                            });
+                                        }
+                                    },
+                                    async: false
+                                });
+
+                                $.ajax({
+                                    url: createURL('listCapacity'),
+                                    data: {clusterid: cluster.id},
+                                    success: function(json) {
+                                        if (json && json.listcapacityresponse && json.listcapacityresponse.capacity) {
+                                            $.each(json.listcapacityresponse.capacity, function(i, capacity) {
+                                                // CPU
+                                                if (capacity.type == 1) {
+                                                    items[idx].cputotal = parseInt(capacity.capacitytotal)/1000.0;
+                                                }
+                                                // Memory
+                                                if (capacity.type == 0) {
+                                                    items[idx].memtotal = parseInt(capacity.capacitytotal)/(1024.0*1024.0*1024.0);
+                                                }
+                                            });
+                                        }
+                                    },
+                                    async: false
+                                });
+
+                                if (items[idx].hosts != 0) {
+                                    items[idx].cpuusedavg = (items[idx].cpuusedavg / items[idx].hosts);
+                                    items[idx].cpumaxdev = (items[idx].maxCpuUsed - items[idx].cpuusedavg);
+                                    items[idx].cpuallocated = (items[idx].cpuallocated / items[idx].hosts);
+
+                                    items[idx].memusedavg = (items[idx].memusedavg / items[idx].hosts);
+                                    items[idx].memmaxdev = (items[idx].maxMemUsed - items[idx].memusedavg);
+                                    items[idx].memallocated = (items[idx].memallocated / items[idx].hosts);
+                                }
+
+                                // Format data
+                                items[idx].cpuusedavg = (items[idx].cpuusedavg).toFixed(2) + "%";
+                                items[idx].cpumaxdev = (items[idx].cpumaxdev).toFixed(2) + "%";
+                                items[idx].cpuallocated = (items[idx].cpuallocated).toFixed(2) + "%";
+                                items[idx].cputotal = (items[idx].cputotal).toFixed(2) + " Ghz";
+
+                                items[idx].memusedavg = (items[idx].memusedavg).toFixed(2) + "%";
+                                items[idx].memmaxdev = (items[idx].memmaxdev).toFixed(2) + "%";
+                                items[idx].memallocated = (items[idx].memallocated).toFixed(2) + "%";
+                                items[idx].memtotal = (items[idx].memtotal).toFixed(2) + " GB";
+                                items[idx].hosts = items[idx].hostsUp + ' / ' + items[idx].hosts;
+
+                                items[idx].state = items[idx].allocationstate;
+                                if (items[idx].managedstate == 'Unmanaged') {
+                                    items[idx].state = 'Unmanaged';
+                                }
+
+                                if (items[idx].managedstate == 'Managed' && items[idx].allocationstate == 'Enabled') {
+                                    items[idx].state = 'Enabled';
+                                }
+
+                                if (items[idx].managedstate == 'Managed' && items[idx].allocationstate == 'Disabled') {
+                                    items[idx].state = 'Disabled';
+                                }
+                            });
+                        }
+                        args.response.success({
+                            data: items
+                        });
+                    }
+                });
+            },
+            browseBy: {
+                filterBy: 'clusterid',
+                resource: 'hosts'
+            },
+            detailView: cloudStack.sections.system.subsections.clusters.listView.detailView
+        }
+    };
+
+
+    // Hosts Metrics
+    cloudStack.sections.metrics.hosts = {
+        title: 'label.metrics',
+        listView: {
+            id: 'hosts',
+            fields: {
+                name: {
+                    label: 'label.metrics.name'
+                },
+                state: {
+                    label: 'label.metrics.state',
+                    converter: function (str) {
+                        // For localization
+                        return str;
+                    },
+                    indicator: {
+                        'Up': 'on',
+                        'Down': 'off',
+                        'Disconnected': 'off',
+                        'Removed': 'off',
+                        'Error': 'off',
+                        'Connecting': 'transition',
+                        'Rebalancing': 'transition',
+                        'Alert': 'warning',
+                    },
+                    compact: true
+                },
+                instances: {
+                    label: 'label.instances'
+                },
+                cpuused: {
+                    label: 'label.metrics.cpu.usage',
+                    collapsible: true,
+                    columns: {
+                        cores: {
+                            label: 'label.metrics.num.cpu.cores',
+                        },
+                        cputotal: {
+                            label: 'label.metrics.cpu.total'
+                        },
+                        cpuusedavg: {
+                            label: 'label.metrics.cpu.used.avg',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'cpunotificationthreshold',
+                                disable: 'cpudisablethreshold'
+                            }
+                        },
+                        cpuallocated: {
+                            label: 'label.metrics.allocated',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'cpunotificationthreshold',
+                                disable: 'cpudisablethreshold'
+                            }
+                        }
+                    }
+                },
+                memused: {
+                    label: 'label.metrics.memory.usage',
+                    collapsible: true,
+                    columns: {
+                        memtotal: {
+                            label: 'label.metrics.memory.total'
+                        },
+                        memusedavg: {
+                            label: 'label.metrics.memory.used.avg',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'memnotificationthreshold',
+                                disable: 'memdisablethreshold'
+                            }
+                        },
+                        memallocated: {
+                            label: 'label.metrics.allocated',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'memnotificationthreshold',
+                                disable: 'memdisablethreshold'
+                            }
+                        }
+                    }
+                },
+                network: {
+                    label: 'label.metrics.network.usage',
+                    collapsible: true,
+                    columns: {
+                        networkread: {
+                            label: 'label.metrics.network.read'
+                        },
+                        networkwrite: {
+                            label: 'label.metrics.network.write'
+                        }
+                    }
+                }
+            },
+            dataProvider: function(args) {
+                var data = {};
+                data.type = 'routing';
+                listViewDataProvider(args, data);
+                if (args.context.metricsFilterData && args.context.metricsFilterData.key && args.context.metricsFilterData.value) {
+                    data[args.context.metricsFilterData.key] = args.context.metricsFilterData.value;
+                }
+                $.ajax({
+                    url: createURL('listHosts'),
+                    data: data,
+                    success: function(json) {
+                        var items = json.listhostsresponse.host;
+                        if (items) {
+                            $.each(items, function(idx, host) {
+                                items[idx].cores = host.cpunumber;
+                                items[idx].cputotal = (parseFloat(host.cpunumber) * parseFloat(host.cpuspeed) / 1000.0).toFixed(2);
+                                if (host.cpuused) {
+                                    items[idx].cpuusedavg = (parseFloat(host.cpuused) * items[idx].cputotal / 100.0).toFixed(2) + ' Ghz';
+                                } else {
+                                    items[idx].cpuusedavg = '';
+                                }
+                                items[idx].cpuallocated = (parseFloat(host.cpuallocated) * items[idx].cputotal / 100.0).toFixed(2) + ' Ghz';
+                                items[idx].memtotal = (parseFloat(host.memorytotal)/(1024.0*1024.0*1024.0)).toFixed(2) + ' GB';
+                                items[idx].memallocated = (parseFloat(host.memoryallocated)/(1024.0*1024.0*1024.0)).toFixed(2) + ' GB';
+                                if (host.memoryused) {
+                                    items[idx].memusedavg = (parseFloat(host.memoryused)/(1024.0*1024.0*1024.0)).toFixed(2) + ' GB';
+                                } else {
+                                    items[idx].memusedavg = '';
+                                }
+                                if (host.networkkbsread && host.networkkbswrite) {
+                                    items[idx].networkread = (parseFloat(host.networkkbsread)/(1024.0*1024.0)).toFixed(2) + ' GB';
+                                    items[idx].networkwrite = (parseFloat(host.networkkbswrite)/(1024.0*1024.0)).toFixed(2) + ' GB';
+                                } else {
+                                    items[idx].networkread = '';
+                                    items[idx].networkwrite = '';
+                                }
+
+                                // Threshold color coding
+                                items[idx].cpunotificationthreshold = 0.75 * parseFloat(items[idx].cputotal);
+                                items[idx].cpudisablethreshold = 0.95 * parseFloat(items[idx].cputotal);
+                                items[idx].memnotificationthreshold = 0.75 * parseFloat(items[idx].memtotal);
+                                items[idx].memdisablethreshold = 0.95 * parseFloat(items[idx].memtotal);
+
+                                $.ajax({
+                                    url: createURL('listConfigurations'),
+                                    data: {clusterid: host.clusterid, listAll: true},
+                                    success: function(json) {
+                                        if (json.listconfigurationsresponse && json.listconfigurationsresponse.configuration) {
+                                            $.each(json.listconfigurationsresponse.configuration, function(i, config) {
+                                                switch (config.name) {
+                                                    case 'cluster.cpu.allocated.capacity.disablethreshold':
+                                                        items[idx].cpudisablethreshold = parseFloat(config.value) * parseFloat(items[idx].cputotal);
+                                                        break;
+                                                    case 'cluster.cpu.allocated.capacity.notificationthreshold':
+                                                        items[idx].cpunotificationthreshold = parseFloat(config.value) * parseFloat(items[idx].cputotal);
+                                                        break;
+                                                    case 'cluster.memory.allocated.capacity.disablethreshold':
+                                                        items[idx].memdisablethreshold = parseFloat(config.value) * parseFloat(items[idx].memtotal);
+                                                        break;
+                                                    case 'cluster.memory.allocated.capacity.notificationthreshold':
+                                                        items[idx].memnotificationthreshold = parseFloat(config.value) * parseFloat(items[idx].memtotal);
+                                                        break;
+                                                }
+                                            });
+                                        }
+                                    },
+                                    async: false
+                                });
+
+                                var cpuOverCommit = 1.0;
+                                var memOverCommit = 1.0;
+                                $.ajax({
+                                    url: createURL('listClusters'),
+                                    data: {clusterid: host.clusterid, listAll: true},
+                                    success: function(json) {
+                                        if (json.listclustersresponse && json.listclustersresponse.cluster) {
+                                            var cluster = json.listclustersresponse.cluster[0];
+                                            cpuOverCommit = cluster.cpuovercommitratio;
+                                            memOverCommit = cluster.memoryovercommitratio;
+                                        }
+                                    },
+                                    async: false
+                                });
+
+                                items[idx].cputotal = items[idx].cputotal + ' Ghz (x' + cpuOverCommit + ')';
+                                items[idx].memtotal = items[idx].memtotal + ' (x' + memOverCommit + ')';
+
+                                items[idx].instances = 0;
+                                items[idx].instancesUp = 0;
+                                $.ajax({
+                                    url: createURL('listVirtualMachines'),
+                                    data: {hostid: host.id, listAll: true},
+                                    success: function(json) {
+                                        if (json && json.listvirtualmachinesresponse && json.listvirtualmachinesresponse.virtualmachine) {
+                                            var vms = json.listvirtualmachinesresponse.virtualmachine;
+                                            if (vms) {
+                                                $.each(vms, function(_, vm) {
+                                                    items[idx].instances += 1;
+                                                    if (vm.state == 'Running') {
+                                                        items[idx].instancesUp += 1;
+                                                    }
+                                                });
+                                            }
+                                        }
+                                    },
+                                    async: false
+                                });
+                                items[idx].instances = items[idx].instancesUp + ' / ' + items[idx].instances;
+                            });
+                        }
+                        args.response.success({
+                            data: items
+                        });
+                    }
+                });
+            },
+            browseBy: {
+                filterBy: 'hostid',
+                resource: 'vms'
+            },
+            detailView: cloudStack.sections.system.subsections.hosts.listView.detailView
+        }
+    };
+
+
+    // VMs Metrics
+    cloudStack.sections.metrics.instances = {
+        title: 'label.metrics',
+        listView: {
+            id: 'instances',
+            fields: {
+                name: {
+                    label: 'label.metrics.name'
+                },
+                state: {
+                    label: 'label.metrics.state',
+                    converter: function (str) {
+                        // For localization
+                        return str;
+                    },
+                    indicator: {
+                        'Running': 'on',
+                        'Stopped': 'off',
+                        'Error': 'off',
+                        'Destroyed': 'off',
+                        'Expunging': 'off',
+                        'Stopping': 'transition',
+                        'Starting': 'transition',
+                        'Migrating': 'transition',
+                        'Shutdowned': 'warning',
+                    },
+                    compact: true
+                },
+                ipaddress: {
+                    label: 'label.ip.address'
+                },
+                zonename: {
+                    label: 'label.zone'
+                },
+                cpuused: {
+                    label: 'label.metrics.cpu.usage',
+                    collapsible: true,
+                    columns: {
+                        cores: {
+                            label: 'label.metrics.num.cpu.cores',
+                        },
+                        cputotal: {
+                            label: 'label.metrics.cpu.total'
+                        },
+                        cpuused: {
+                            label: 'label.metrics.cpu.used.avg',
+                        }
+                    }
+                },
+                memused: {
+                    label: 'label.metrics.memory.usage',
+                    collapsible: true,
+                    columns: {
+                        memallocated: {
+                            label: 'label.metrics.allocated'
+                        }
+                    }
+                },
+                network: {
+                    label: 'label.metrics.network.usage',
+                    collapsible: true,
+                    columns: {
+                        networkread: {
+                            label: 'label.metrics.network.read'
+                        },
+                        networkwrite: {
+                            label: 'label.metrics.network.write'
+                        }
+                    }
+                },
+                disk: {
+                    label: 'label.metrics.disk.usage',
+                    collapsible: true,
+                    columns: {
+                        diskread: {
+                            label: 'label.metrics.disk.read'
+                        },
+                        diskwrite: {
+                            label: 'label.metrics.disk.write'
+                        },
+                        diskiopstotal: {
+                            label: 'label.metrics.disk.iops.total'
+                        }
+                    }
+                }
+            },
+            dataProvider: function(args) {
+                var data = {};
+                listViewDataProvider(args, data);
+                if (args.context.metricsFilterData && args.context.metricsFilterData.key && args.context.metricsFilterData.value) {
+                    data[args.context.metricsFilterData.key] = args.context.metricsFilterData.value;
+                }
+                $.ajax({
+                    url: createURL('listVirtualMachines'),
+                    data: data,
+                    success: function(json) {
+                        var items = [];
+                        if (json && json.listvirtualmachinesresponse && json.listvirtualmachinesresponse.virtualmachine) {
+                            items = json.listvirtualmachinesresponse.virtualmachine;
+                            $.each(items, function(idx, vm) {
+                                items[idx].cores = vm.cpunumber;
+                                items[idx].cputotal = (parseFloat(vm.cpunumber) * parseFloat(vm.cpuspeed) / 1000.0).toFixed(1) + ' Ghz';
+                                items[idx].cpuusedavg = vm.cpuused;
+                                items[idx].cpuallocated = vm.cpuallocated;
+                                items[idx].memallocated = (parseFloat(vm.memory)/1024.0).toFixed(2) + ' GB';
+                                items[idx].networkread = (parseFloat(vm.networkkbsread)/(1024.0)).toFixed(2) + ' MB';
+                                items[idx].networkwrite = (parseFloat(vm.networkkbswrite)/(1024.0)).toFixed(2) + ' MB';
+                                items[idx].diskread = (parseFloat(vm.diskkbsread)/(1024.0)).toFixed(2) + ' MB';
+                                items[idx].diskwrite = (parseFloat(vm.diskkbswrite)/(1024.0)).toFixed(2) + ' MB';
+                                items[idx].diskiopstotal = parseFloat(vm.diskioread) + parseFloat(vm.diskiowrite);
+                                if (vm.nic && vm.nic.length > 0 && vm.nic[0].ipaddress) {
+                                    items[idx].ipaddress = vm.nic[0].ipaddress;
+                                }
+
+                                var keys = [{'cpuused': 'cpuusedavg'},
+                                            {'networkkbsread': 'networkread'},
+                                            {'networkkbswrite': 'networkwrite'},
+                                            {'diskkbsread': 'diskread'},
+                                            {'diskkbswrite': 'diskwrite'},
+                                            {'diskioread': 'diskiopstotal'}];
+                                for (keyIdx in keys) {
+                                    var map = keys[keyIdx];
+                                    var key = Object.keys(map)[0];
+                                    var uiKey = map[key];
+                                    if (!vm.hasOwnProperty(key)) {
+                                        items[idx][uiKey] = '';
+                                    }
+                                }
+                            });
+                        }
+                        args.response.success({
+                            data: items
+                        });
+                    }
+                });
+            },
+            browseBy: {
+                filterBy: 'virtualmachineid',
+                resource: 'volumes'
+            },
+            detailView: cloudStack.sections.instances.listView.detailView
+        }
+    };
+
+
+    // Volumes Metrics
+    cloudStack.sections.metrics.volumes = {
+        title: 'label.metrics',
+        listView: {
+            id: 'volumes',
+            fields: {
+                name: {
+                    label: 'label.metrics.name'
+                },
+                state: {
+                    label: 'label.metrics.state',
+                    converter: function (str) {
+                        // For localization
+                        return str;
+                    },
+                    indicator: {
+                        'Allocated': 'transition',
+                        'Creating': 'transition',
+                        'Ready': 'on',
+                        'Destroy': 'off',
+                        'Expunging': 'off',
+                        'Migrating': 'warning',
+                        'UploadOp': 'transition',
+                        'Snapshotting': 'warning',
+                    },
+                    compact: true
+                },
+                vmname: {
+                    label: 'label.metrics.vm.name'
+                },
+                disksize: {
+                    label: 'label.metrics.disk.size'
+                },
+                storagetype: {
+                    label: 'label.metrics.disk.storagetype'
+                },
+                storagepool: {
+                    label: 'label.metrics.storagepool'
+                },
+            },
+            dataProvider: function(args) {
+                var data = {listAll: true};
+                listViewDataProvider(args, data);
+                if (args.context.metricsFilterData && args.context.metricsFilterData.key && args.context.metricsFilterData.value) {
+                    data[args.context.metricsFilterData.key] = args.context.metricsFilterData.value;
+                }
+                $.ajax({
+                    url: createURL('listVolumes'),
+                    data: data,
+                    success: function(json) {
+                        var items = [];
+                        if (json && json.listvolumesresponse && json.listvolumesresponse.volume) {
+                            items = json.listvolumesresponse.volume;
+                            $.each(items, function(idx, volume) {
+                                items[idx].name = volume.name;
+                                items[idx].state = volume.state;
+                                items[idx].vmname = volume.vmname;
+                                items[idx].disksize = parseFloat(volume.size)/(1024.0*1024.0*1024.0) + ' GB';
+                                items[idx].storagetype = volume.storagetype.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}) + ' (' + volume.type + ')';
+                                if (volume.storage) {
+                                    items[idx].storagepool = volume.storage;
+                                }
+                            });
+                        }
+                        args.response.success({
+                            data: items
+                        });
+                    }
+                });
+            },
+            detailView: cloudStack.sections.storage.sections.volumes.listView.detailView
+        }
+    };
+
+
+    // Storage Pool Metrics
+    cloudStack.sections.metrics.storagepool = {
+        title: 'label.metrics',
+        listView: {
+            id: 'primarystorages',
+            fields: {
+                name: {
+                    label: 'label.metrics.name'
+                },
+                property: {
+                    label: 'label.metrics.property',
+                    collapsible: true,
+                    columns: {
+                        state: {
+                            label: 'label.metrics.state',
+                            converter: function (str) {
+                                // For localization
+                                return str;
+                            },
+                            indicator: {
+                                'Up': 'on',
+                                'Down': 'off',
+                                'Removed': 'off',
+                                'ErrorInMaintenance': 'off',
+                                'PrepareForMaintenance': 'transition',
+                                'CancelMaintenance': 'warning',
+                                'Maintenance': 'warning',
+                            },
+                            compact: true
+                        },
+                        scope: {
+                            label: 'label.metrics.scope'
+                        },
+                        type: {
+                            label: 'label.metrics.disk.storagetype'
+                        },
+                    }
+                },
+                disk: {
+                    label: 'label.metrics.disk',
+                    collapsible: true,
+                    columns: {
+                        disksizeused: {
+                            label: 'label.metrics.disk.used',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'storagenotificationthreshold',
+                                disable: 'storagedisablethreshold'
+                            }
+                        },
+                        disksizetotal: {
+                            label: 'label.metrics.disk.total'
+                        },
+                        disksizeallocated: {
+                            label: 'label.metrics.disk.allocated',
+                            thresholdcolor: true,
+                            thresholds: {
+                                notification: 'storageallocatednotificationthreshold',
+                                disable: 'storageallocateddisablethreshold'
+                            }
+                        },
+                        disksizeunallocated: {
+                            label: 'label.metrics.disk.unallocated'
+                        }
+                    }
+                }
+            },
+            dataProvider: function(args) {
+                var data = {};
+                listViewDataProvider(args, data);
+                if (args.context.metricsFilterData && args.context.metricsFilterData.key && args.context.metricsFilterData.value) {
+                    data[args.context.metricsFilterData.key] = args.context.metricsFilterData.value;
+                }
+                $.ajax({
+                    url: createURL('listStoragePools'),
+                    data: data,
+                    success: function(json) {
+                        var items = [];
+                        if (json && json.liststoragepoolsresponse && json.liststoragepoolsresponse.storagepool) {
+                            items = json.liststoragepoolsresponse.storagepool;
+                            $.each(items, function(idx, pool) {
+                                items[idx].name = pool.name;
+                                items[idx].state = pool.state;
+                                items[idx].scope = pool.scope;
+                                items[idx].type = pool.type;
+                                items[idx].overprovisionfactor = parseFloat(pool.overprovisionfactor);
+                                if (pool.disksizeused) {
+                                    items[idx].disksizeused = (parseFloat(pool.disksizeused)/(1024.0*1024.0*1024.0)).toFixed(2) + ' GB';
+                                } else {
+                                    items[idx].disksizeused = '';
+                                }
+                                items[idx].disksizetotal = parseFloat(pool.disksizetotal);
+                                items[idx].disksizeallocated = parseFloat(pool.disksizeallocated);
+                                items[idx].disksizeunallocated = (items[idx].overprovisionfactor * items[idx].disksizetotal) - items[idx].disksizeallocated;
+
+                                // Format presentation
+                                items[idx].disksizetotal = (items[idx].disksizetotal/(1024.0*1024.0*1024.0)).toFixed(2) + ' GB (x' + items[idx].overprovisionfactor + ')';
+                                items[idx].disksizeallocated = (items[idx].disksizeallocated/(1024.0*1024.0*1024.0)).toFixed(2) + ' GB';
+                                items[idx].disksizeunallocated = (items[idx].disksizeunallocated/(1024.0*1024.0*1024.0)).toFixed(2) + ' GB';
+
+                                // Threshold color coding
+                                items[idx].storagenotificationthreshold = 0.75 * parseFloat(items[idx].disksizetotal);
+                                items[idx].storagedisablethreshold = 0.95 * parseFloat(items[idx].disksizetotal);
+                                items[idx].storageallocatednotificationthreshold = 0.75 * parseFloat(items[idx].disksizetotal) * items[idx].overprovisionfactor;
+                                items[idx].storageallocateddisablethreshold = 0.95 * parseFloat(items[idx].disksizetotal) * items[idx].overprovisionfactor;
+
+
+                                var getThresholds = function(data, items, idx) {
+                                    data.listAll = true;
+                                    $.ajax({
+                                        url: createURL('listConfigurations'),
+                                        data: data,
+                                        success: function(json) {
+                                            if (json.listconfigurationsresponse && json.listconfigurationsresponse.configuration) {
+                                                $.each(json.listconfigurationsresponse.configuration, function(i, config) {
+                                                    switch (config.name) {
+                                                        case 'cluster.storage.allocated.capacity.notificationthreshold':
+                                                            items[idx].storageallocatednotificationthreshold = parseFloat(config.value) * parseFloat(items[idx].disksizetotal);
+                                                            break;
+                                                        case 'cluster.storage.capacity.notificationthreshold':
+                                                            items[idx].storagenotificationthreshold = parseFloat(config.value) * parseFloat(items[idx].disksizetotal);
+                                                            break;
+                                                        case 'pool.storage.allocated.capacity.disablethreshold':
+                                                            items[idx].storageallocateddisablethreshold = parseFloat(config.value) * parseFloat(items[idx].disksizetotal);
+                                                            break;
+                                                        case 'pool.storage.capacity.disablethreshold':
+                                                            items[idx].storagedisablethreshold = parseFloat(config.value) * parseFloat(items[idx].disksizetotal);
+                                                            break;
+                                                    }
+                                                });
+                                            }
+                                        },
+                                        async: false
+                                    });
+                                };
+                                // Update global and cluster level thresholds
+                                getThresholds({}, items, idx);
+                                getThresholds({clusterid: pool.clusterid}, items, idx);
+                            });
+                        }
+                        args.response.success({
+                            data: items
+                        });
+                    }
+                });
+            },
+            browseBy: {
+                filterBy: 'storageid',
+                resource: 'volumes'
+            },
+            detailView: cloudStack.sections.system.subsections['primary-storage'].listView.detailView
+        }
+    };
+
+})(cloudStack);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/ui/scripts/storage.js
----------------------------------------------------------------------
diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js
index 77bd002..09d0052 100644
--- a/ui/scripts/storage.js
+++ b/ui/scripts/storage.js
@@ -245,6 +245,20 @@
                             }
                         },
 
+                        viewMetrics: {
+                            label: 'label.metrics',
+                            isHeader: true,
+                            addRow: false,
+                            action: {
+                                custom: cloudStack.uiCustom.metricsView({resource: 'volumes'})
+                            },
+                            messages: {
+                                notification: function (args) {
+                                    return 'label.metrics';
+                                }
+                            }
+                        },
+
                         uploadVolume: {
                             isHeader: true,
                             label: 'label.upload',

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/ui/scripts/system.js
----------------------------------------------------------------------
diff --git a/ui/scripts/system.js b/ui/scripts/system.js
index c2680b8..eaf2e0d 100644
--- a/ui/scripts/system.js
+++ b/ui/scripts/system.js
@@ -7709,7 +7709,7 @@
                 }
             }
         },
-        show: cloudStack.uiCustom.physicalResources({
+        physicalResourceSection: {
             sections: {
                 physicalResources: {
                     type: 'select',
@@ -7792,7 +7792,20 @@
                                             });
                                         }
                                     }
-                                }
+                                },
+                                viewMetrics: {
+                                    label: 'label.metrics',
+                                    isHeader: true,
+                                    addRow: false,
+                                    action: {
+                                        custom: cloudStack.uiCustom.metricsView({resource: 'zones'})
+                                    },
+                                    messages: {
+                                        notification: function (args) {
+                                            return 'label.metrics';
+                                        }
+                                    }
+                                },
                             },
 
                             detailView: {
@@ -9484,7 +9497,7 @@
                     }
                 }
             }
-        }),
+        },
         subsections: {
             virtualRouters: {
                 sectionSelect: {
@@ -14524,6 +14537,19 @@
                                     }
                                 });
                             }
+                        },
+                        viewMetrics: {
+                            label: 'label.metrics',
+                            isHeader: true,
+                            addRow: false,
+                            action: {
+                                custom: cloudStack.uiCustom.metricsView({resource: 'clusters'})
+                            },
+                            messages: {
+                                notification: function (args) {
+                                    return 'label.metrics';
+                                }
+                            }
                         }
                     },
 
@@ -15226,11 +15252,12 @@
                         }
 
                         if (! args.context.instances) {
-                            array1.push("&zoneid=" + args.context.zones[0].id);
+                            if ("zones" in args.context)
+                                array1.push("&zoneid=" + args.context.zones[0].id);
                             if ("pods" in args.context)
-                            array1.push("&podid=" + args.context.pods[0].id);
+                                array1.push("&podid=" + args.context.pods[0].id);
                             if ("clusters" in args.context)
-                            array1.push("&clusterid=" + args.context.clusters[0].id);
+                               array1.push("&clusterid=" + args.context.clusters[0].id);
                         } else {
                             //Instances menu > Instance detailView > View Hosts
                             array1.push("&id=" + args.context.instances[0].hostid);
@@ -15833,6 +15860,19 @@
                                     return 'label.add.host';
                                 }
                             }
+                        },
+                        viewMetrics: {
+                            label: 'label.metrics',
+                            isHeader: true,
+                            addRow: false,
+                            action: {
+                                custom: cloudStack.uiCustom.metricsView({resource: 'hosts'})
+                            },
+                            messages: {
+                                notification: function (args) {
+                                    return 'label.metrics';
+                                }
+                            }
                         }
                     },
                     detailView: {
@@ -17647,6 +17687,19 @@
                                     return 'label.add.primary.storage';
                                 }
                             }
+                        },
+                        viewMetrics: {
+                            label: 'label.metrics',
+                            isHeader: true,
+                            addRow: false,
+                            action: {
+                                custom: cloudStack.uiCustom.metricsView({resource: 'storagepool'})
+                            },
+                            messages: {
+                                notification: function (args) {
+                                    return 'label.metrics';
+                                }
+                            }
                         }
                     },
 
@@ -19883,6 +19936,9 @@
         }
     }
 
+    // Inject cloudStack infra page
+    cloudStack.sections.system.show = cloudStack.uiCustom.physicalResources(cloudStack.sections.system.physicalResourceSection);
+
     function addExternalLoadBalancer(args, physicalNetworkObj, apiCmd, apiCmdRes, apiCmdObj) {
         var array1 =[];
         array1.push("&physicalnetworkid=" + physicalNetworkObj.id);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/ui/scripts/ui-custom/metricsView.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui-custom/metricsView.js b/ui/scripts/ui-custom/metricsView.js
new file mode 100644
index 0000000..ef5dbba
--- /dev/null
+++ b/ui/scripts/ui-custom/metricsView.js
@@ -0,0 +1,140 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+(function($, cloudStack) {
+
+    cloudStack.uiCustom.metricsView = function(args) {
+        return function() {
+            var metricsListView = cloudStack.sections.metrics.listView;
+            var metricsLabel = _l('label.metrics');
+
+            if (args.resource == 'zones') {
+                metricsListView = cloudStack.sections.metrics.zones.listView;
+                metricsLabel = _l('label.zones') + ' ' + metricsLabel;
+            } else if (args.resource == 'clusters') {
+                metricsListView = cloudStack.sections.metrics.clusters.listView;
+                metricsLabel = _l('label.clusters') + ' ' + metricsLabel;
+            } else if (args.resource == 'hosts') {
+                metricsListView = cloudStack.sections.metrics.hosts.listView;
+                metricsLabel = _l('label.hosts') + ' ' + metricsLabel;
+            } else if (args.resource == 'storagepool') {
+                metricsListView = cloudStack.sections.metrics.storagepool.listView;
+                metricsLabel = _l('label.primary.storage') + ' ' + metricsLabel;
+            } else if (args.resource == 'vms') {
+                metricsListView = cloudStack.sections.metrics.instances.listView;
+                metricsLabel = _l('label.instances') + ' ' + metricsLabel;
+            } else if (args.resource == 'volumes') {
+                metricsListView = cloudStack.sections.metrics.volumes.listView;
+                metricsLabel = _l('label.volumes') + ' ' + metricsLabel;
+            }
+
+            // list view refresh button
+            metricsListView.actions = {
+                refreshMetrics: {
+                    label: 'label.refresh',
+                    isHeader: true,
+                    addRow: true,
+                    action: {
+                        custom: function (args) {
+                            return function() {
+                            };
+                        }
+                    }
+                }
+            };
+
+            metricsListView.hideSearchBar = true;
+            metricsListView.needsRefresh = true;
+            metricsListView.noSplit = true;
+            metricsListView.horizontalOverflow = true;
+            metricsListView.groupableColumns = true;
+
+            if (args.resource == 'volumes') {
+                metricsListView.groupableColumns = false;
+            }
+
+            var metricsContext = cloudStack.context;
+            if (metricsContext.metricsFilterData) {
+                delete metricsContext.metricsFilterData;
+            }
+            if (args.filterBy) {
+                metricsContext.metricsFilterData = {
+                    key: args.filterBy,
+                    value: args.id
+                };
+            }
+
+            var $browser = $('#browser .container');
+            return $browser.cloudBrowser('addPanel', {
+                  title: metricsLabel,
+                  maximizeIfSelected: true,
+                  complete: function($newPanel) {
+                      $newPanel.listView({
+                          $browser: $browser,
+                          context: metricsContext,
+                          listView: metricsListView
+                      });
+                      // Make metrics tables horizontally scrollable
+                      $newPanel.find('.list-view').css({'overflow-x': 'visible'});
+                      // Refresh metrics when refresh button is clicked
+                      $newPanel.find('.refreshMetrics').click(function() {
+                          var sortedTh = $newPanel.find('table thead tr:last th.sorted');
+                          var thIndex = sortedTh.index();
+                          var thClassName = null;
+                          var wasSorted = false;
+                          var sortClassName = 'asc';
+                          if (sortedTh && sortedTh.hasClass('sorted')) {
+                              wasSorted = true;
+                              var classes = sortedTh.attr('class').split(/\s+/);
+                              thClassName = classes[0];
+                              if (classes.indexOf('desc') > -1) {
+                                  sortClassName = 'desc';
+                              }
+                          }
+                          $browser.cloudBrowser('removeLastPanel', {});
+                          var refreshedPanel = cloudStack.uiCustom.metricsView(args)();
+                          if (wasSorted && thClassName) {
+                              refreshedPanel.find('th.' + thClassName).filter(function() {
+                                  return $(this).index() == thIndex;
+                              }).addClass('sorted').addClass(sortClassName);
+                          }
+                      });
+
+                      var filterMetricView = metricsListView.browseBy;
+                      if (filterMetricView) {
+                          $newPanel.bind('click', function(event) {
+                              event.stopPropagation();
+                              var $target = $(event.target);
+                              var id = $target.closest('tr').data('list-view-item-id');
+                              var jsonObj = $target.closest('tr').data('jsonObj');
+                              if (filterMetricView.filterKey && jsonObj) {
+                                  if (jsonObj.hasOwnProperty(filterMetricView.filterKey)) {
+                                  id = jsonObj[filterMetricView.filterKey];
+                                  } else {
+                                      return; // return if provided key is missing
+                                  }
+                              }
+                              if (id && ($target.hasClass('first') || $target.parent().hasClass('first')) && ($target.is('td') || $target.parent().is('td'))) {
+                                  filterMetricView.id = id;
+                                  cloudStack.uiCustom.metricsView(filterMetricView)();
+                              }
+                          });
+                      }
+                  }
+            });
+        };
+    };
+})(jQuery, cloudStack);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/ad592835/ui/scripts/ui/widgets/dataTable.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui/widgets/dataTable.js b/ui/scripts/ui/widgets/dataTable.js
index 22ddda6..4574052 100644
--- a/ui/scripts/ui/widgets/dataTable.js
+++ b/ui/scripts/ui/widgets/dataTable.js
@@ -177,14 +177,19 @@
             var sortData = [];
             var numericDataCount = 0;
             $elems.each(function() {
-                var text = $(this).html();
+                var text = $(this);
                 if (hasAllRowsSameValue) {
-                    if (firstElem !== text) {
+                    if (firstElem !== text.html()) {
                         hasAllRowsSameValue = false;
                     }
                 }
+                if (text.children()) {
+                    text = text.children().html();
+                } else {
+                    text = text.html();
+                }
                 if (isNumeric(text) || !text) {
-                    numericDataCount++;
+                    numericDataCount += 1;
                 }
                 sortData.push($(this));
             });