diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 7f6df22797e..da647877f27 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -7925,6 +7925,7 @@ div.ui-dialog div.multi-edit-add-list div.view div.data-table table.body tbody t width: 87px !important; min-width: 87px !important; max-width: 87px !important; + font-size: 10px; } /** Header fields*/ @@ -7974,6 +7975,15 @@ div.ui-dialog div.multi-edit-add-list div.view div.data-table table.body tbody t .multi-edit .header-fields input[type=submit] { } +/* Sortable */ +.multi-edit table tbody tr td.reorder, +.multi-edit table thead tr th.reorder { + width: 30px !important; + min-width: 30px !important; + max-width: 30px !important; +} + + /*Security Rules*/ .security-rules .multi-edit input { width: 69px; @@ -11726,13 +11736,15 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it .destroy .icon, .remove .icon, .delete .icon, -.decline .icon { +.decline .icon, +.deleteacllist .icon { background-position: 1px -92px; } .destroy:hover .icon, .remove:hover .icon, -.delete:hover .icon { +.delete:hover .icon, +.deleteacllist:hover .icon { background-position: 1px -674px; } @@ -11818,13 +11830,15 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it .downloadVolume .icon, .downloadTemplate .icon, -.downloadISO .icon { +.downloadISO .icon, +.replaceacllist .icon { background-position: -35px -125px; } .downloadVolume:hover .icon, .downloadTemplate:hover .icon, -.downloadISO:hover .icon { +.downloadISO:hover .icon, +.replaceacllist:hover .icon { background-position: -35px -707px; } @@ -11857,12 +11871,14 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it } .add .icon, -.addNew .icon { +.addNew .icon, +.assignVm .icon { background-position: -37px -61px; } .add:hover .icon, -.addNew:hover .icon { +.addNew:hover .icon, +.assignVm:hover .icon { background-position: -37px -643px; } diff --git a/ui/images/sprites.png b/ui/images/sprites.png index 132588d1004..f3c8226b64a 100644 Binary files a/ui/images/sprites.png and b/ui/images/sprites.png differ diff --git a/ui/modules/modules.js b/ui/modules/modules.js index d4502a195bc..1e4cd45c833 100644 --- a/ui/modules/modules.js +++ b/ui/modules/modules.js @@ -16,6 +16,7 @@ // under the License. (function($, cloudStack) { cloudStack.modules = [ + 'vpc', 'infrastructure', 'vnmcNetworkProvider', 'vnmcAsa1000v' diff --git a/ui/modules/vpc/vpc.css b/ui/modules/vpc/vpc.css new file mode 100644 index 00000000000..51c3f7e05bd --- /dev/null +++ b/ui/modules/vpc/vpc.css @@ -0,0 +1,313 @@ +/*[fmt]1C20-1C0D-E*/ +/* +* 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. +*/ +.vpc-network-chart { + width: 100%; + height: 100%; + overflow: auto; +} + +.vpc-network-chart .tiers { + margin: 40px 46px 0 0; + width: 362px; + float: right; +} + +.vpc-network-chart .tier-item { + border: 1px solid #477FB4; + overflow: hidden; + width: 326px; + height: 182px; + margin: 18px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + background: #8DB1D3; +} + +.vpc-network-chart .tier-item .header { + width: 100%; + float: left; + padding: 7px 0 6px; + position: relative; + /*+box-shadow:inset 0px 1px 1px #FFFFFF;*/ + -moz-box-shadow: inset 0px 1px 1px #FFFFFF; + -webkit-box-shadow: inset 0px 1px 1px #FFFFFF; + -o-box-shadow: inset 0px 1px 1px #FFFFFF; + box-shadow: inset 0px 1px 1px #FFFFFF; + background: #69839D; + border-bottom: 1px solid #40639E; +} + +.vpc-network-chart .tier-item .header .detail-link { + cursor: pointer; + background: #435667 url(../../images/sprites.png) -428px -83px; + /*+box-shadow:inset 0px 1px 4px #2F2F2F;*/ + -moz-box-shadow: inset 0px 1px 4px #2F2F2F; + -webkit-box-shadow: inset 0px 1px 4px #2F2F2F; + -o-box-shadow: inset 0px 1px 4px #2F2F2F; + box-shadow: inset 0px 1px 4px #2F2F2F; + width: 16px; + height: 16px; + float: right; + /*+placement:shift -8px 2px;*/ + position: relative; + left: -8px; + top: 2px; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; +} + +.vpc-network-chart .tier-item .header .detail-link:hover { + background-color: #454545; +} + +.vpc-network-chart .tier-item .header .title { + margin-left: 9px; + width: 268px; + height: 20px; + float: left; + overflow: hidden; +} + +.vpc-network-chart .tier-item .header .title span { + font-size: 20px; + color: #FFFFFF; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; +} + +.vpc-network-chart .tier-item .content { + width: 100%; + height: 100%; + /*+box-shadow:inset 0px 1px 1px #FFFFFF;*/ + -moz-box-shadow: inset 0px 1px 1px #FFFFFF; + -webkit-box-shadow: inset 0px 1px 1px #FFFFFF; + -o-box-shadow: inset 0px 1px 1px #FFFFFF; + box-shadow: inset 0px 1px 1px #FFFFFF; + border-bottom: 1px solid #8DB1D3; +} + +.vpc-network-chart .tier-item .content .dashboard { + height: 117px; +} + +.vpc-network-chart .tier-item .content .dashboard-item { + width: 145px; + height: 54px; + margin: 7px 9px 0; + background: #C1E0FE; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + float: left; + cursor: pointer; +} + +.vpc-network-chart .tier-item .content .dashboard-item:hover { + background-color: #DBEDFE; + /*+box-shadow:inset 0px 1px 2px #000000;*/ + -moz-box-shadow: inset 0px 1px 2px #000000; + -webkit-box-shadow: inset 0px 1px 2px #000000; + -o-box-shadow: inset 0px 1px 2px #000000; + box-shadow: inset 0px 1px 2px #000000; +} + +.vpc-network-chart .tier-item .content .dashboard-item .total { + font-size: 30px; + /*+placement:shift 7px 5px;*/ + position: relative; + left: 7px; + top: 5px; + color: #145CA1; + font-weight: 100; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; +} + +.vpc-network-chart .tier-item .content .dashboard-item .total.multiline { + font-size: 14px; +} + +.vpc-network-chart .tier-item .content .dashboard-item .name { + font-size: 11px; + text-transform: uppercase; + color: #0861B7; + /*+placement:shift 8px 7px;*/ + position: relative; + left: 8px; + top: 7px; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; +} + +.vpc-network-chart .tier-item .content .info { + /*+placement:shift 10px 5px;*/ + position: relative; + left: 10px; + top: 5px; +} + +.vpc-network-chart .tier-item .content .info .cidr-label { + font-size: 10px; + color: #1860A7; +} + +.vpc-network-chart .tier-item .content .info .cidr { + color: #364553; + font-size: 10px; + /*+text-shadow:0px 1px #C7D8E9;*/ + -moz-text-shadow: 0px 1px #C7D8E9; + -webkit-text-shadow: 0px 1px #C7D8E9; + -o-text-shadow: 0px 1px #C7D8E9; + text-shadow: 0px 1px #C7D8E9; +} + +.vpc-network-chart .tier-placeholder { + cursor: pointer; + background: #EFEFEF; + border: 4px dotted #B1B1B1; + /*+border-radius:8px;*/ + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; + width: 325px; + text-align: center; + padding: 57px 0 55px; + margin: 0 0 0 18px; +} + +.vpc-network-chart .tier-placeholder:hover { + background: #DCDCDC; + /*+border-radius:8px;*/ + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; + /*+box-shadow:inset 0px 1px 4px #000000;*/ + -moz-box-shadow: inset 0px 1px 4px #000000; + -webkit-box-shadow: inset 0px 1px 4px #000000; + -o-box-shadow: inset 0px 1px 4px #000000; + box-shadow: inset 0px 1px 4px #000000; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; +} + +.vpc-network-chart .tier-placeholder:hover span { + color: #535353; +} + +.vpc-network-chart .tier-placeholder span { + color: #AFAFAF; + font-size: 24px; + font-weight: 200; +} + +.vpc-network-chart .tier-item.router { + width: 258px; + height: 218px; + background: #BDBDBD; + border: 1px solid #808080; + float: left; + /*+placement:shift 10px 176px;*/ + position: relative; + left: 10px; + top: 176px; +} + +.vpc-network-chart .tier-item.router .header { + padding: 15px 0 14px; + border-bottom: 1px solid #808080; + background: #C3C6C9; +/*Old browsers*/ + background: -moz-linear-gradient(top, #c3c6c9 0%, #909497 100%); +/*FF3.6+*/ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#c3c6c9), color-stop(100%,#909497)); +/*Chrome,Safari4+*/ + background: -webkit-linear-gradient(top, #c3c6c9 0%,#909497 100%); +/*Chrome10+,Safari5.1+*/ + background: -o-linear-gradient(top, #c3c6c9 0%,#909497 100%); +/*Opera 11.10+*/ + background: -ms-linear-gradient(top, #c3c6c9 0%,#909497 100%); +/*IE10+*/ + background: linear-gradient(to bottom, #c3c6c9 0%,#909497 100%); +/*W3C*/ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#c3c6c9', endColorstr='#909497',GradientType=0 ); +/*IE6-8*/ +} + +.vpc-network-chart .tier-item.router .header .title { + width: 212px; + margin-top: 3px; +} + +.vpc-network-chart .tier-item.router .header .title span { + padding: 0 0 0 50px; +} + +.vpc-network-chart .tier-item.router .header span.icon { + background: url(../../images/sprites.png) -589px -1175px; + padding: 10px 10px 10px 20px; + float: left; + position: absolute; + top: 7px; + left: 10px; +} + +.vpc-network-chart .tier-item.router .dashboard-item { + width: 100px; + /*[empty]margin:;*/ + padding: 0px 2px 0px 6px; + height: 73px; + background: #A7A7A7; +} + +.vpc-network-chart .tier-item.router .dashboard-item span { + color: #FFFFFF; + /*+text-shadow:0px 1px #000000;*/ + -moz-text-shadow: 0px 1px #000000; + -webkit-text-shadow: 0px 1px #000000; + -o-text-shadow: 0px 1px #000000; + text-shadow: 0px 1px #000000; +} + +.vpc-network-chart .tier-item.router .dashboard-item:hover { + background-color: #818181; +} + diff --git a/ui/modules/vpc/vpc.js b/ui/modules/vpc/vpc.js new file mode 100644 index 00000000000..a6037c00fb7 --- /dev/null +++ b/ui/modules/vpc/vpc.js @@ -0,0 +1,327 @@ +// 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) { + var addTierDialog = function(args) { + var $placeholder = args.$placeholder; + var context = args.context; + var addAction = cloudStack.vpc.tiers.actions.add; + + cloudStack.dialog.createForm({ + context: context, + form: addAction.createForm, + after: function(args) { + var $loading = $('
| ').addClass('actions reorder').appendTo($tr).append(function() { + var $td = $(this); - var $td = $(' | ').addClass(fieldName).appendTo($tr);
- var $input, val;
- var $addButton = $multi.find('form .button.add-vm:not(.custom-action)').clone();
- var newItemRows = [];
- var addItemAction = function(data) {
- var $loading = $(' ').addClass('loading-overlay');
- var complete = function(args) {
- var $tbody = $item.find('.expandable-listing tbody');
+ $.each(reorder, function(actionName, action) {
+ var fnLabel = {
+ moveTop: _l('label.move.to.top'),
+ moveBottom: _l('label.move.to.bottom'),
+ moveUp: _l('label.move.up.row'),
+ moveDown: _l('label.move.down.row'),
+ moveDrag: _l('label.drag.new.position')
+ };
- $loading.remove();
- $(data).each(function() {
- var item = this;
- var $itemRow = _medit.multiItem.itemRow(item, options.itemActions, multiRule, $tbody);
+ $(' ')
+ .addClass('action reorder')
+ .addClass(actionName)
+ .append(
+ $('').addClass('icon').html(' ')
+ )
+ .attr({
+ title: _l(fnLabel[actionName])
+ })
+ .appendTo($td)
+ .click(function() {
+ if (actionName == 'moveDrag') return false;
- $itemRow.appendTo($tbody);
- newItemRows.push($itemRow);
+ rowActions[actionName]($tr);
+ $tr.closest('.data-body').find('.data-item').each(function() {
+ sort($(this), action);
+ });
- cloudStack.evenOdd($tbody, 'tr:visible', {
- even: function($elem) {
- $elem.removeClass('odd');
- $elem.addClass('even');
- },
- odd: function($elem) {
- $elem.removeClass('even');
- $elem.addClass('odd');
- }
+ return false;
});
- });
- };
- var error = function() {
- $(newItemRows).each(function() {
- var $itemRow = this;
+ });
+ });
+ }
- $itemRow.remove();
- });
- $loading.remove();
- };
- $loading.prependTo($item);
- options.itemActions.add.action({
- context: options.context,
- data: data,
- multiRule: multiRule,
- response: {
- success: function(args) {
- var notificationError = function(args) {
- error();
- };
+ // Setup columns
+ $.each(fields, function(fieldName, field) {
+ if (!field || (options.ignoreEmptyFields && !data[fieldName])) {
+ return true;
+ }
- cloudStack.ui.notifications.add(args.notification,
- complete, {},
- notificationError, {});
+ var $td = $(' ').addClass(fieldName).appendTo($tr);
+ var $input, val;
+ var $addButton = $multi.find('form .button.add-vm:not(.custom-action)').clone();
+ var newItemRows = [];
+ var addItemAction = function(data) {
+ var $loading = $(' | ').addClass('loading-overlay');
+ var complete = function(args) {
+ var $tbody = $item.find('.expandable-listing tbody');
+
+ $loading.remove();
+ $(data).each(function() {
+ var item = this;
+ var $itemRow = _medit.multiItem.itemRow(item, options.itemActions, multiRule, $tbody);
+
+ $itemRow.appendTo($tbody);
+ newItemRows.push($itemRow);
+
+ cloudStack.evenOdd($tbody, 'tr:visible', {
+ even: function($elem) {
+ $elem.removeClass('odd');
+ $elem.addClass('even');
},
- error: error
- }
+ odd: function($elem) {
+ $elem.removeClass('even');
+ $elem.addClass('odd');
+ }
+ });
});
};
+ var error = function() {
+ $(newItemRows).each(function() {
+ var $itemRow = this;
- if (!itemData) itemData = [{}];
+ $itemRow.remove();
+ });
+ $loading.remove();
+ };
- if (!options.noSelect &&
- $multi.find('th,td').filter(function() {
- return $(this).attr('rel') == fieldName;
- }).is(':hidden')) {
- return true;
- }
+ $loading.prependTo($item);
+ options.itemActions.add.action({
+ context: options.context,
+ data: data,
+ multiRule: multiRule,
+ response: {
+ success: function(args) {
+ var notificationError = function(args) {
+ error();
+ };
- if (!field.isPassword) {
- if (field.edit) {
- // Edit fields append value of data
- if (field.range) {
- var start = _s(data[field.range[0]]);
- var end = _s(data[field.range[1]]);
+ cloudStack.ui.notifications.add(args.notification,
+ complete, {},
+ notificationError, {});
+ },
+ error: error
+ }
+ });
+ };
- $td.append($('').html(start + ' - ' + end));
+ if (!itemData) itemData = [{}];
+
+ if (!options.noSelect &&
+ $multi.find('th,td').filter(function() {
+ return $(this).attr('rel') == fieldName;
+ }).is(':hidden')) {
+ return true;
+ }
+
+ if (!field.isPassword) {
+ if (field.edit) {
+ // Edit fields append value of data
+ if (field.range) {
+ var start = _s(data[field.range[0]]);
+ var end = _s(data[field.range[1]]);
+
+ $td.append($('').html(start + ' - ' + end));
+ } else {
+ var maxLengths = data['_maxLength'];
+
+ if (maxLengths &&
+ maxLengths[fieldName] &&
+ data[fieldName].length >= maxLengths[fieldName]) {
+ $td.append($('').html(_s(data[fieldName].toString().substr(0, maxLengths[fieldName] - 3).concat('...'))));
} else {
- var maxLengths = data['_maxLength'];
-
- if (maxLengths &&
- maxLengths[fieldName] &&
- data[fieldName].length >= maxLengths[fieldName]) {
- $td.append($('').html(_s(data[fieldName].toString().substr(0, maxLengths[fieldName] - 3).concat('...'))));
- } else {
- $td.append($('').html(_s(data[fieldName])));
- }
- $td.attr('title', data[fieldName]);
+ $td.append($('').html(_s(data[fieldName])));
}
- } else if (field.select) {
- // Get matching option text
- var $matchingSelect = $multi.find('select')
- .filter(function() {
- return $(this).attr('name') == fieldName;
- });
- var $matchingOption = $matchingSelect.find('option')
- .filter(function() {
- return $(this).val() == data[fieldName];
- });
+ $td.attr('title', data[fieldName]);
+ }
+ } else if (field.select) {
+ // Get matching option text
+ var $matchingSelect = $multi.find('select')
+ .filter(function() {
+ return $(this).attr('name') == fieldName;
+ });
+ var $matchingOption = $matchingSelect.find('option')
+ .filter(function() {
+ return $(this).val() == data[fieldName];
+ });
- var matchingValue = $matchingOption.size() ?
- $matchingOption.html() : data[fieldName];
-
- $td.append($('').html(_s(matchingValue)));
- } else if (field.addButton && !options.noSelect) {
- if (options.multipleAdd) {
- $addButton.click(function() {
- if ($td.hasClass('disabled')) return false;
-
- _medit.vmList($multi,
- options.listView,
- options.context,
- options.multipleAdd, _l('label.add.vms'),
- addItemAction,
- {
- multiRule: multiRule
- });
-
- return true;
- });
- $td.append($addButton);
- } else {
- // Show VM data
- var itemName = data._itemName ? itemData[0][data._itemName] : itemData[0].name;
- $td.html(options.multipleAdd ?
- itemData.length + ' VMs' : itemName);
- $td.click(function() {
- var $browser = $(this).closest('.detail-view').data('view-args').$browser;
-
- if (options.multipleAdd) {
- _medit.multiItem.details(itemData, $browser);
- } else {
- _medit.details(itemData[0], $browser, {
- context: options.context, itemName: itemName
- });
- }
- });
- }
- } else if (field.custom) {
- var $button = $(' ').addClass('button add-vm custom-action');
-
- $td.data('multi-custom-data', data[fieldName]);
- $button.html(data && data[fieldName] && data[fieldName]['_buttonLabel'] ?
- _l(data[fieldName]['_buttonLabel']) : _l(field.custom.buttonLabel));
- $button.click(function() {
+ var matchingValue = $matchingOption.size() ?
+ $matchingOption.html() : data[fieldName];
+
+ $td.append($('').html(_s(matchingValue)));
+ } else if (field.addButton && !options.noSelect) {
+ if (options.multipleAdd) {
+ $addButton.click(function() {
if ($td.hasClass('disabled')) return false;
- var $button = $(this);
- var context = $.extend(true, {},
- options.context ?
- options.context : cloudStack.context, {
- multiRules: [data]
- });
-
- field.custom.action({
- context: context,
- data: $td.data('multi-custom-data'),
- $item: $td,
- response: {
- success: function(args) {
- if (args.data['_buttonLabel']) {
- $button.html(_l(args.data['_buttonLabel']));
- }
- $td.data('multi-custom-data', args.data);
- }
- }
- });
+ _medit.vmList($multi,
+ options.listView,
+ options.context,
+ options.multipleAdd, _l('label.add.vms'),
+ addItemAction,
+ {
+ multiRule: multiRule
+ });
return true;
});
- $button.appendTo($td);
- }
- }
+ $td.append($addButton);
+ } else {
+ // Show VM data
+ var itemName = data._itemName ? itemData[0][data._itemName] : itemData[0].name;
+ $td.html(options.multipleAdd ?
+ itemData.length + ' VMs' : itemName);
+ $td.click(function() {
+ var $browser = $(this).closest('.detail-view').data('view-args').$browser;
- // Add blank styling for empty fields
- if ($td.html() == '') {
- $td.addClass('blank');
- }
-
- // Align width to main header
- _medit.refreshItemWidths($multi);
-
- if (data._hideFields &&
- $.inArray(fieldName, data._hideFields) > -1) {
- $td.addClass('disabled');
- }
-
- return true;
- });
-
- // Actions column
- var $actions = $(' ').addClass('multi-actions').appendTo($item.find('tr'));
-
- // Align action column width
- $actions.width($multi.find('th.multi-actions').width() + 4);
-
- // Action filter
- var allowedActions = options.preFilter ? options.preFilter({
- actions: $.map(actions, function(value, key) { return key; }),
- context: $.extend(true, {}, options.context, {
- multiRule: [data],
- actions: $.map(actions, function(value, key) { return key; })
- })
- }) : null;
-
- // Append actions
- $.each(actions, function(actionID, action) {
- if (allowedActions && $.inArray(actionID, allowedActions) == -1) return true;
-
- $actions.append(
- $(' | ').addClass('action')
- .addClass(actionID)
- .append($('').addClass('icon'))
- .attr({ title: _l(action.label) })
- .click(function() {
- var performAction = function(actionOptions) {
- if (!actionOptions) actionOptions = {};
-
- action.action({
- context: $.extend(true, {}, options.context, {
- multiRule: [data]
- }),
- data: actionOptions.data,
- response: {
- success: function(args) {
- var notification = args ? args.notification : null;
- var _custom = args ? args._custom : null;
- if (notification) {
- $('.notifications').notifications('add', {
- section: 'network',
- desc: notification.label,
- interval: 3000,
- _custom: _custom,
- poll: function(args) {
- var complete = args.complete;
- var error = args.error;
-
- notification.poll({
- _custom: args._custom,
- complete: function(args) {
- if (isDestroy) {
- $loading.remove();
- $dataItem.remove();
- } else {
- $multi.trigger('refresh');
- }
-
- complete();
-
- if (actionOptions.complete) actionOptions.complete();
- },
- error: function(args) {
- error(args);
- $loading.remove();
- $dataItem.show();
-
- return cloudStack.dialog.error;
- }
- });
- }
- });
- } else {
- $loading.remove();
- if (isDestroy) {
- $dataItem.remove();
- }
- }
- },
- error: function(message) {
- cloudStack.dialog.notice({ message: message });
- $item.show();
- $loading.remove();
- }
- }
+ if (options.multipleAdd) {
+ _medit.multiItem.details(itemData, $browser);
+ } else {
+ _medit.details(itemData[0], $browser, {
+ context: options.context, itemName: itemName
});
- };
+ }
+ });
+ }
+ } else if (field.custom) {
+ var $button = $(' ').addClass('button add-vm custom-action');
- var $target = $(this);
- var $dataItem = $target.closest('.data-item');
- var $expandable = $dataItem.find('.expandable-listing');
- var isDestroy = $target.hasClass('destroy');
- var isEdit = $target.hasClass('edit');
- var createForm = action.createForm;
+ $td.data('multi-custom-data', data[fieldName]);
+ $button.html(data && data[fieldName] && data[fieldName]['_buttonLabel'] ?
+ _l(data[fieldName]['_buttonLabel']) : _l(field.custom.buttonLabel));
+ $button.click(function() {
+ if ($td.hasClass('disabled')) return false;
+
+ var $button = $(this);
+ var context = $.extend(true, {},
+ options.context ?
+ options.context : cloudStack.context, {
+ multiRules: [data]
+ });
- if (isDestroy) {
- var $loading = _medit.loadingItem($multi, _l('label.removing') + '...');
-
- if ($expandable.is(':visible')) {
- $expandable.slideToggle(function() {
- $dataItem.hide();
- $dataItem.after($loading);
- });
- } else {
- // Loading appearance
- $dataItem.hide();
- $dataItem.after($loading);
+ field.custom.action({
+ context: context,
+ data: $td.data('multi-custom-data'),
+ $item: $td,
+ response: {
+ success: function(args) {
+ if (args.data['_buttonLabel']) {
+ $button.html(_l(args.data['_buttonLabel']));
+ }
+ $td.data('multi-custom-data', args.data);
}
}
+ });
- if (!isEdit) {
- if (createForm) {
- cloudStack.dialog.createForm({
- form: createForm,
- after: function(args) {
- var $loading = $(' ').addClass('loading-overlay').prependTo($dataItem);
- performAction({ data: args.data, complete: function() {
- $multi.trigger('refresh');
- } });
+ return true;
+ });
+ $button.appendTo($td);
+ }
+ }
+
+ // Add blank styling for empty fields
+ if ($td.html() == '') {
+ $td.addClass('blank');
+ }
+
+ // Align width to main header
+ _medit.refreshItemWidths($multi);
+
+ if (data._hideFields &&
+ $.inArray(fieldName, data._hideFields) > -1) {
+ $td.addClass('disabled');
+ }
+
+ return true;
+ });
+
+ // Actions column
+ var $actions = $(' ').addClass('multi-actions').appendTo($item.find('tr'));
+
+ // Align action column width
+ $actions.width($multi.find('th.multi-actions').width() + 4);
+
+ // Action filter
+ var allowedActions = options.preFilter ? options.preFilter({
+ actions: $.map(actions, function(value, key) { return key; }),
+ context: $.extend(true, {}, options.context, {
+ multiRule: [data],
+ actions: $.map(actions, function(value, key) { return key; })
+ })
+ }) : null;
+
+ // Append actions
+ $.each(actions, function(actionID, action) {
+ if (allowedActions && $.inArray(actionID, allowedActions) == -1) return true;
+
+ $actions.append(
+ $(' | ').addClass('action')
+ .addClass(actionID)
+ .append($('').addClass('icon'))
+ .attr({ title: _l(action.label) })
+ .click(function() {
+ var performAction = function(actionOptions) {
+ if (!actionOptions) actionOptions = {};
+
+ action.action({
+ context: $.extend(true, {}, options.context, {
+ multiRule: [data]
+ }),
+ data: actionOptions.data,
+ response: {
+ success: function(args) {
+ var notification = args ? args.notification : null;
+ var _custom = args ? args._custom : null;
+ if (notification) {
+ $('.notifications').notifications('add', {
+ section: 'network',
+ desc: notification.label,
+ interval: 3000,
+ _custom: _custom,
+ poll: function(args) {
+ var complete = args.complete;
+ var error = args.error;
+
+ notification.poll({
+ _custom: args._custom,
+ complete: function(args) {
+ if (isDestroy) {
+ $loading.remove();
+ $dataItem.remove();
+ } else {
+ $multi.trigger('refresh');
+ }
+
+ complete();
+
+ if (actionOptions.complete) actionOptions.complete();
+ },
+ error: function(args) {
+ error(args);
+ $loading.remove();
+ $dataItem.show();
+
+ return cloudStack.dialog.error;
+ }
+ });
+ }
+ });
+ } else {
+ $loading.remove();
+ if (isDestroy) {
+ $dataItem.remove();
+ }
}
- });
- } else {
- performAction();
- }
- } else {
- // Get editable fields
- var editableFields = {};
-
- $.each(fields, function(key, field) {
- if (field.isEditable) editableFields[key] = $.extend(true, {}, field, {
- defaultValue: data[key]
- });
- });
-
- cloudStack.dialog.createForm({
- form: {
- title: 'Edit rule',
- desc: '',
- fields: editableFields
},
+ error: function(message) {
+ cloudStack.dialog.notice({ message: message });
+ $item.show();
+ $loading.remove();
+ }
+ }
+ });
+ };
+
+ var $target = $(this);
+ var $dataItem = $target.closest('.data-item');
+ var $expandable = $dataItem.find('.expandable-listing');
+ var isDestroy = $target.hasClass('destroy');
+ var isEdit = $target.hasClass('edit');
+ var createForm = action.createForm;
+ var reorder = options.reorder;
+
+ if (isDestroy) {
+ var $loading = _medit.loadingItem($multi, _l('label.removing') + '...');
+
+ if ($expandable.is(':visible')) {
+ $expandable.slideToggle(function() {
+ $dataItem.hide();
+ $dataItem.after($loading);
+ });
+ } else {
+ // Loading appearance
+ $dataItem.hide();
+ $dataItem.after($loading);
+ }
+ }
+
+ if (!isEdit) {
+ if (createForm) {
+ cloudStack.dialog.createForm({
+ form: createForm,
after: function(args) {
var $loading = $(' ').addClass('loading-overlay').prependTo($dataItem);
performAction({ data: args.data, complete: function() {
@@ -378,324 +394,336 @@
} });
}
});
+ } else {
+ performAction();
}
- })
- );
- });
+ } else {
+ // Get editable fields
+ var editableFields = {};
- // Add tagger action
- if (options.tags) {
- $actions.prepend(
- $('')
- .addClass('action editTags')
- .attr('title', _l('label.edit.tags'))
- .append($('').addClass('icon'))
- .click(function() {
- $(' ')
- .dialog({
- dialogClass: 'editTags',
- title: _l('label.edit.tags'),
- width: 400,
- buttons: [
- {
- text: _l('label.done'),
- 'class': 'ok',
- click: function() {
- $(this).dialog('destroy');
- $('div.overlay:last').remove();
+ $.each(fields, function(key, field) {
+ if (field && field.isEditable) editableFields[key] = $.extend(true, {}, field, {
+ defaultValue: data[key]
+ });
+ });
- return true;
- }
+ cloudStack.dialog.createForm({
+ form: {
+ title: 'Edit rule',
+ desc: '',
+ fields: editableFields
+ },
+ after: function(args) {
+ var $loading = $(' ').addClass('loading-overlay').prependTo($dataItem);
+ performAction({ data: args.data, complete: function() {
+ $multi.trigger('refresh');
+ } });
+ }
+ });
+ }
+ })
+ );
+ });
+
+ // Add tagger action
+ if (options.tags) {
+ $actions.prepend(
+ $('')
+ .addClass('action editTags')
+ .attr('title', _l('label.edit.tags'))
+ .append($('').addClass('icon'))
+ .click(function() {
+ $(' ')
+ .dialog({
+ dialogClass: 'editTags',
+ title: _l('label.edit.tags'),
+ width: 400,
+ buttons: [
+ {
+ text: _l('label.done'),
+ 'class': 'ok',
+ click: function() {
+ $(this).dialog('destroy');
+ $('div.overlay:last').remove();
+
+ return true;
}
- ]
- })
- .append(
- $('').addClass('multi-edit-tags').tagger($.extend(true, {}, options.tags, {
- context: $.extend(true, {}, options.context, {
- multiRule: [multiRule]
- })
- }))
- )
- .closest('.ui-dialog').overlay();
+ }
+ ]
+ })
+ .append(
+ $('').addClass('multi-edit-tags').tagger($.extend(true, {}, options.tags, {
+ context: $.extend(true, {}, options.context, {
+ multiRule: [multiRule]
+ })
+ }))
+ )
+ .closest('.ui-dialog').overlay();
+
+ return false;
+ })
+ )
+ }
+
+ // Add expandable listing, for multiple-item
+ if (options.multipleAdd) {
+ // Create expandable box
+ _medit.multiItem.expandable($item.find('tr').data('multi-edit-data'),
+ options.itemActions,
+ multiRule).appendTo($item);
+
+ // Expandable icon/action
+ $item.find('td:first').prepend(
+ $(' ').addClass('expand').click(function() {
+ $item.closest('.data-item').find('.expandable-listing').slideToggle();
+ }));
+ }
+
+ return $item;
+ },
+
+ vmList: function($multi, listView, context, isMultipleAdd, label, complete, options) {
+ if (!options) options = {};
+
+ // Create a listing of instances, based on limited information
+ // from main instances list view
+ var $listView;
+ var instances = $.extend(true, {}, listView, {
+ context: $.extend(true, {}, context, {
+ multiData: getMultiData($multi),
+ multiRule: options.multiRule ? [options.multiRule] : null
+ }),
+ uiCustom: true
+ });
+
+ instances.listView.actions = {
+ select: {
+ label: 'Select instance',
+ type: isMultipleAdd ? 'checkbox' : 'radio',
+ action: {
+ uiCustom: function(args) {
+ var $item = args.$item;
+ var $input = $item.find('td.actions input:visible');
+
+ if ($input.attr('type') == 'checkbox') {
+ if ($input.is(':checked'))
+ $item.addClass('multi-edit-selected');
+ else
+ $item.removeClass('multi-edit-selected');
+ } else {
+ $item.siblings().removeClass('multi-edit-selected');
+ $item.addClass('multi-edit-selected');
+ }
+ }
+ }
+ }
+ };
+
+ $listView = $(' ').listView(instances);
+
+ // Change action label
+ $listView.find('th.actions').html(_l('Select'));
+
+ var $dataList = $listView.addClass('multi-edit-add-list').dialog({
+ dialogClass: 'multi-edit-add-list panel',
+ width: 825,
+ title: label,
+ buttons: [
+ {
+ text: _l('label.apply'),
+ 'class': 'ok',
+ click: function() {
+ if (!$listView.find('input[type=radio]:checked, input[type=checkbox]:checked').size()) {
+ cloudStack.dialog.notice({ message: _l('message.select.item')});
return false;
- })
- )
- }
-
- // Add expandable listing, for multiple-item
- if (options.multipleAdd) {
- // Create expandable box
- _medit.multiItem.expandable($item.find('tr').data('multi-edit-data'),
- options.itemActions,
- multiRule).appendTo($item);
-
- // Expandable icon/action
- $item.find('td:first').prepend(
- $(' ').addClass('expand').click(function() {
- $item.closest('.data-item').find('.expandable-listing').slideToggle();
- }));
- }
-
- return $item;
- },
-
- vmList: function($multi, listView, context, isMultipleAdd, label, complete, options) {
- if (!options) options = {};
-
- // Create a listing of instances, based on limited information
- // from main instances list view
- var $listView;
- var instances = $.extend(true, {}, listView, {
- context: $.extend(true, {}, context, {
- multiData: getMultiData($multi),
- multiRule: options.multiRule ? [options.multiRule] : null
- }),
- uiCustom: true
- });
-
- instances.listView.actions = {
- select: {
- label: 'Select instance',
- type: isMultipleAdd ? 'checkbox' : 'radio',
- action: {
- uiCustom: function(args) {
- var $item = args.$item;
- var $input = $item.find('td.actions input:visible');
-
- if ($input.attr('type') == 'checkbox') {
- if ($input.is(':checked'))
- $item.addClass('multi-edit-selected');
- else
- $item.removeClass('multi-edit-selected');
- } else {
- $item.siblings().removeClass('multi-edit-selected');
- $item.addClass('multi-edit-selected');
- }
}
+
+ $dataList.fadeOut(function() {
+ complete($.map(
+ $listView.find('tr.multi-edit-selected'),
+
+ // Attach VM data to row
+ function(elem) {
+ var itemData = $(elem).data('json-obj');
+ var $subselect = $(elem).find('.subselect select');
+
+ // Include subselect data
+ if ($subselect && $subselect.val()) {
+ return $.extend(itemData, {
+ _subselect: $subselect.val()
+ });
+ }
+
+ return itemData;
+ }
+ ));
+ $dataList.remove();
+ });
+
+ $('div.overlay').fadeOut(function() {
+ $('div.overlay').remove();
+ });
+
+ return true;
+ }
+ },
+ {
+ text: _l('label.cancel'),
+ 'class': 'cancel',
+ click: function() {
+ $dataList.fadeOut(function() {
+ $dataList.remove();
+ });
+ $('div.overlay').fadeOut(function() {
+ $('div.overlay').remove();
+ });
}
}
- };
+ ]
+ }).parent('.ui-dialog').overlay();
+ },
- $listView = $(' ').listView(instances);
+ /**
+ * Align width of each data row to main header
+ */
+ refreshItemWidths: function($multi) {
+ $multi.find('.data-body').width(
+ $multi.find('form > table.multi-edit').width()
+ );
- // Change action label
- $listView.find('th.actions').html(_l('Select'));
+ $multi.find('.data tr').filter(function() {
+ return !$(this).closest('.expandable-listing').size();
+ }).each(function() {
+ var $tr = $(this);
- var $dataList = $listView.addClass('multi-edit-add-list').dialog({
- dialogClass: 'multi-edit-add-list panel',
- width: 825,
- title: label,
- buttons: [
- {
- text: _l('label.apply'),
- 'class': 'ok',
- click: function() {
- if (!$listView.find('input[type=radio]:checked, input[type=checkbox]:checked').size()) {
- cloudStack.dialog.notice({ message: _l('message.select.item')});
+ $tr.find('td').each(function() {
+ var $td = $(this);
- return false;
- }
-
- $dataList.fadeOut(function() {
- complete($.map(
- $listView.find('tr.multi-edit-selected'),
-
- // Attach VM data to row
- function(elem) {
- var itemData = $(elem).data('json-obj');
- var $subselect = $(elem).find('.subselect select');
-
- // Include subselect data
- if ($subselect && $subselect.val()) {
- return $.extend(itemData, {
- _subselect: $subselect.val()
- });
- }
-
- return itemData;
- }
- ));
- $dataList.remove();
- });
-
- $('div.overlay').fadeOut(function() {
- $('div.overlay').remove();
- });
-
- return true;
- }
- },
- {
- text: _l('label.cancel'),
- 'class': 'cancel',
- click: function() {
- $dataList.fadeOut(function() {
- $dataList.remove();
- });
- $('div.overlay').fadeOut(function() {
- $('div.overlay').remove();
- });
- }
- }
- ]
- }).parent('.ui-dialog').overlay();
- },
-
- /**
- * Align width of each data row to main header
- */
- refreshItemWidths: function($multi) {
- $multi.find('.data-body').width(
- $multi.find('form > table.multi-edit').width()
- );
-
- $multi.find('.data tr').filter(function() {
- return !$(this).closest('.expandable-listing').size();
- }).each(function() {
- var $tr = $(this);
-
- $tr.find('td').each(function() {
- var $td = $(this);
-
- $td.width($($multi.find('th:visible')[$td.index()]).width() + 5);
- });
+ $td.width($($multi.find('th:visible')[$td.index()]).width() + 5);
});
- },
+ });
+ },
- /**
- * Create a fake 'loading' item box
- */
- loadingItem: function($multi, label) {
- var $loading = $(' ').addClass('data-item loading');
+ /**
+ * Create a fake 'loading' item box
+ */
+ loadingItem: function($multi, label) {
+ var $loading = $(' ').addClass('data-item loading');
- // Align height with existing items
- var $row = $multi.find('.data-item:first');
+ // Align height with existing items
+ var $row = $multi.find('.data-item:first');
- // Set label
- if (label) {
- $loading.append(
- $(' ').addClass('label').append(
- $('').html(_l(label))
- )
- );
+ // Set label
+ if (label) {
+ $loading.append(
+ $(' ').addClass('label').append(
+ $('').html(_l(label))
+ )
+ );
+ }
+
+ return $loading;
+ },
+ details: function(data, $browser, options) {
+ if (!options) options = {};
+
+ var detailViewArgs, $detailView;
+
+ detailViewArgs = $.extend(true, {}, cloudStack.sections.instances.listView.detailView);
+ detailViewArgs.actions = null;
+ detailViewArgs.$browser = $browser;
+ detailViewArgs.id = data.id;
+ detailViewArgs.jsonObj = data;
+ detailViewArgs.context = options.context;
+
+ $browser.cloudBrowser('addPanel', {
+ title: options.itemName ? options.itemName : data.name,
+ maximizeIfSelected: true,
+ complete: function($newPanel) {
+ $newPanel.detailView(detailViewArgs);
}
+ });
+ },
+ multiItem: {
+ /**
+ * Show listing of load balanced VMs
+ */
+ details: function(data, $browser) {
+ var listViewArgs, $listView;
- return $loading;
- },
- details: function(data, $browser, options) {
- if (!options) options = {};
-
- var detailViewArgs, $detailView;
-
- detailViewArgs = $.extend(true, {}, cloudStack.sections.instances.listView.detailView);
- detailViewArgs.actions = null;
- detailViewArgs.$browser = $browser;
- detailViewArgs.id = data.id;
- detailViewArgs.jsonObj = data;
- detailViewArgs.context = options.context;
+ // Setup list view
+ listViewArgs = $.extend(true, {}, cloudStack.sections.instances);
+ listViewArgs.listView.actions = null;
+ listViewArgs.listView.filters = null;
+ listViewArgs.$browser = $browser;
+ listViewArgs.listView.detailView.actions = null;
+ listViewArgs.listView.dataProvider = function(args) {
+ setTimeout(function() {
+ args.response.success({
+ data: data
+ });
+ }, 50);
+ };
+ $listView = $(' ').listView(listViewArgs);
+ // Show list view of selected VMs
$browser.cloudBrowser('addPanel', {
- title: options.itemName ? options.itemName : data.name,
+ title: _l('label.item.listing'),
+ data: '',
+ noSelectPanel: true,
maximizeIfSelected: true,
complete: function($newPanel) {
- $newPanel.detailView(detailViewArgs);
+ return $newPanel.listView(listViewArgs);
}
});
},
- multiItem: {
- /**
- * Show listing of load balanced VMs
- */
- details: function(data, $browser) {
- var listViewArgs, $listView;
- // Setup list view
- listViewArgs = $.extend(true, {}, cloudStack.sections.instances);
- listViewArgs.listView.actions = null;
- listViewArgs.listView.filters = null;
- listViewArgs.$browser = $browser;
- listViewArgs.listView.detailView.actions = null;
- listViewArgs.listView.dataProvider = function(args) {
- setTimeout(function() {
- args.response.success({
- data: data
- });
- }, 50);
- };
- $listView = $(' ').listView(listViewArgs);
+ itemRow: function(item, itemActions, multiRule, $tbody) {
+ var $tr = $(' ').addClass('name').appendTo($tr).append($itemName));
+
+ $itemName.click(function() {
+ _medit.details(item, $('#browser .container'), {
+ itemName: itemName,
+ context: {
+ instances: [item]
}
});
- },
+ });
- itemRow: function(item, itemActions, multiRule, $tbody) {
- var $tr = $(' | ').addClass('name').appendTo($tr).append($itemName));
-
- $itemName.click(function() {
- _medit.details(item, $('#browser .container'), {
- itemName: itemName,
- context: {
- instances: [item]
- }
- });
- });
-
- var itemState=multiRule._itemState ? item[multiRule._itemState] :item.state;
- var $itemState = $('').html(_s(itemState));
- $tr.append($(' | ').addClass('state').appendTo($tr).append("Application State - ").append($itemState));
+ var itemState=multiRule._itemState ? item[multiRule._itemState] :item.state;
+ var $itemState = $('').html(_s(itemState));
+ $tr.append($(' | ').addClass('state').appendTo($tr).append("Application State - ").append($itemState));
- if (itemActions) {
- var $itemActions = $(' | ').addClass('actions item-actions');
+ if (itemActions) {
+ var $itemActions = $(' | ').addClass('actions item-actions');
- $.each(itemActions, function(itemActionID, itemAction) {
- if (itemActionID == 'add')
- return true;
-
- if(item._hideActions != null && $.inArray(itemActionID, item._hideActions) > -1)
- return true;
+ $.each(itemActions, function(itemActionID, itemAction) {
+ if (itemActionID == 'add')
+ return true;
+
+ if(item._hideActions != null && $.inArray(itemActionID, item._hideActions) > -1)
+ return true;
- var $itemAction = $(' | ').addClass('action').addClass(itemActionID);
+ var $itemAction = $(' ').addClass('action').addClass(itemActionID);
- $itemAction.click(function() {
- itemAction.action({
- item: item,
- multiRule: multiRule,
- response: {
- success: function(args) {
- if (itemActionID == 'destroy') {
- var notification = args.notification;
- var success = function(args) { $tr.remove(); };
- var successArgs = {};
- var error = function(args) {
- $tr.show();
- cloudStack.evenOdd($tbody, 'tr:visible', {
- even: function($elem) {
- $elem.removeClass('odd');
- $elem.addClass('even');
- },
- odd: function($elem) {
- $elem.removeClass('even');
- $elem.addClass('odd');
- }
- });
- };
- var errorArgs = {};
-
- $tr.hide();
+ $itemAction.click(function() {
+ itemAction.action({
+ item: item,
+ multiRule: multiRule,
+ response: {
+ success: function(args) {
+ if (itemActionID == 'destroy') {
+ var notification = args.notification;
+ var success = function(args) { $tr.remove(); };
+ var successArgs = {};
+ var error = function(args) {
+ $tr.show();
cloudStack.evenOdd($tbody, 'tr:visible', {
even: function($elem) {
$elem.removeClass('odd');
@@ -706,83 +734,124 @@
$elem.addClass('odd');
}
});
- cloudStack.ui.notifications.add(notification,
- success, successArgs,
- error, errorArgs);
- }
- },
- error: function(message) {
- if (message) {
- cloudStack.dialog.notice({ message: message });
- }
+ };
+ var errorArgs = {};
+
+ $tr.hide();
+ cloudStack.evenOdd($tbody, 'tr:visible', {
+ even: function($elem) {
+ $elem.removeClass('odd');
+ $elem.addClass('even');
+ },
+ odd: function($elem) {
+ $elem.removeClass('even');
+ $elem.addClass('odd');
+ }
+ });
+ cloudStack.ui.notifications.add(notification,
+ success, successArgs,
+ error, errorArgs);
+ }
+ },
+ error: function(message) {
+ if (message) {
+ cloudStack.dialog.notice({ message: message });
}
}
- });
+ }
});
- $itemAction.append($('').addClass('icon'));
- $itemAction.appendTo($itemActions);
-
- return true;
});
+ $itemAction.append($('').addClass('icon'));
+ $itemAction.appendTo($itemActions);
- $itemActions.appendTo($tr);
- }
-
- return $tr;
- },
-
- expandable: function(data, itemActions, multiRule) {
- var $expandable = $(' ').addClass('expandable-listing');
- var $tbody = $('').appendTo($(' |