diff --git a/ui/css/cloudstack3-ie7.css b/ui/css/cloudstack3-ie7.css index aece82cff73..5b783f55c32 100644 --- a/ui/css/cloudstack3-ie7.css +++ b/ui/css/cloudstack3-ie7.css @@ -63,3 +63,13 @@ div.panel div.list-view div.fixed-header { float: right; } +.multi-edit .data .data-body .data-item .expandable-listing { + width: 99.8%; + border: 1px solid #CFC9C9; + max-height: 161px; + position: relative; + overflow: auto; + overflow-x: hidden; + top: expression(this.offsetParent.scrollTop); +} + diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 140a0b2be52..5267b49b06f 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -777,6 +777,10 @@ div.notification-box .container ul li { cursor: pointer; } +div.notification-box .container ul li.error { + background: url(../images/icons.png) no-repeat 10px -179px; +} + div.notification-box .container ul li span { float: left; /*+placement:shift 48px 16px;*/ @@ -6198,6 +6202,11 @@ div.panel.ui-dialog div.list-view div.fixed-header { .multi-edit .data .data-body .data-item { margin-bottom: 14px; border: 1px solid #CDCCCC; + position: relative; +} + +.multi-edit .data .data-body .data-item .loading-overlay { + background-position: 50% 50%; } .multi-edit .data .data-body .data-item.loading { @@ -6225,8 +6234,6 @@ div.panel.ui-dialog div.list-view div.fixed-header { .multi-edit .data .data-body .data-item tr { background: #EFEFEF; border: none; - display: block; - width: 115%; } .multi-edit .data .data-body .data-item table tbody tr td { @@ -6234,10 +6241,8 @@ div.panel.ui-dialog div.list-view div.fixed-header { border-left: none; border-right: 1px solid #CFC9C9; height: 15px; - float: left; overflow: hidden; padding-right: 0; - padding-bottom: 4px; } .multi-edit .data .data-body .data-item table tbody tr td.blank { @@ -6292,7 +6297,7 @@ div.panel.ui-dialog div.list-view div.fixed-header { } .multi-edit .data .data-body .data-item tr td .custom-action { - margin: -6px 0 0 -1px; + margin: -2px 0 0 0px; } .multi-edit .data .data-body .data-item tr td.add-vm:hover { @@ -6301,10 +6306,10 @@ div.panel.ui-dialog div.list-view div.fixed-header { } .multi-edit .data .data-body .data-item tr td.multi-actions .icon { - /*+placement:shift -3px -8px;*/ + /*+placement:shift -3px -2px;*/ position: relative; left: -3px; - top: -8px; + top: -2px; } .multi-edit .data .data-body .data-item .expandable-listing { @@ -6324,7 +6329,6 @@ div.panel.ui-dialog div.list-view div.fixed-header { .multi-edit .data .data-body .data-item .expandable-listing tr td { background: #DDE0E2; border: none; - width: 100%; margin: 0; text-indent: 37px; } diff --git a/ui/scripts/network.js b/ui/scripts/network.js index ccee3f2ef18..750942240f1 100644 --- a/ui/scripts/network.js +++ b/ui/scripts/network.js @@ -1604,6 +1604,9 @@ listView: $.extend(true, {}, cloudStack.sections.instances, { listView: { dataProvider: function(args) { + var itemData = $.isArray(args.context.multiRule) && args.context.multiRule[0]['_itemData'] ? + args.context.multiRule[0]['_itemData'] : []; + $.ajax({ url: createURL('listVirtualMachines'), data: { @@ -1612,16 +1615,21 @@ dataType: 'json', async: true, success: function(data) { + var vmData = $.grep( + data.listvirtualmachinesresponse.virtualmachine ? + data.listvirtualmachinesresponse.virtualmachine : [], + function(instance) { + var isActiveState = $.inArray(instance.state, ['Destroyed']) == -1; + var notExisting = !$.grep(itemData, function(item) { + return item.id == instance.id; + }).length; + + return isActiveState && notExisting; + } + ); + args.response.success({ - data: $.grep( - data.listvirtualmachinesresponse.virtualmachine ? - data.listvirtualmachinesresponse.virtualmachine : [], - function(instance) { - return $.inArray(instance.state, [ - 'Destroyed' - ]) == -1; - } - ) + data: vmData }); }, error: function(data) { @@ -1702,6 +1710,7 @@ async: true, success: function(data) { var lbCreationComplete = false; + var jobID = data.assigntoloadbalancerruleresponse.jobid; args.response.success({ _custom: { @@ -1784,6 +1793,64 @@ } } }, + itemActions: { + add: { + label: 'Add VM(s) to load balancer rule', + action: function(args) { + $.ajax({ + url: createURL('assignToLoadBalancerRule'), + data: { + id: args.multiRule.id, + virtualmachineids: $.map(args.data, function(elem) { + return elem.id; + }).join(',') + }, + success: function(json) { + args.response.success({ + notification: { + _custom: { + jobId: json.assigntoloadbalancerruleresponse.jobid + }, + desc: 'Add VM(s) to load balancer rule', + poll: pollAsyncJobResult + } + }); + }, + error: function(json) { + args.response.error(); + cloudStack.dialog.notice({ message: parseXMLHttpResponse(json) }); + } + }); + } + }, + destroy: { + label: 'Remove VM from load balancer', + action: function(args) { + $.ajax({ + url: createURL('removeFromLoadBalancerRule'), + data: { + id: args.multiRule.id, + virtualmachineids: args.item.id + }, + success: function(json) { + args.response.success({ + notification: { + _custom: { + jobId: json.removefromloadbalancerruleresponse.jobid + }, + desc: 'Remove VM from load balancer rule', + poll: pollAsyncJobResult + } + }); + }, + error: function(json) { + args.response.error(); + cloudStack.dialog.notice({ message: parseXMLHttpResponse(json) }); + } + }); + } + } + }, dataProvider: function(args) { var apiCmd = "listLoadBalancerRules"; //if(args.context.networks[0].type == "Shared") diff --git a/ui/scripts/ui/widgets/multiEdit.js b/ui/scripts/ui/widgets/multiEdit.js index 28a0f32ea0a..f7204cd878b 100644 --- a/ui/scripts/ui/widgets/multiEdit.js +++ b/ui/scripts/ui/widgets/multiEdit.js @@ -6,10 +6,12 @@ addItem: function(data, fields, $multi, itemData, actions, options) { if (!options) options = {}; - var $item = $('
| ').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');
+ },
+ odd: function($elem) {
+ $elem.removeClass('even');
+ $elem.addClass('odd');
+ }
+ });
+ });
+ };
+ 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();
+ };
+
+ cloudStack.ui.notifications.add(args.notification,
+ complete, {},
+ notificationError, {});
+ },
+ error: error
+ }
+ });
+ };
if ($multi.find('th,td').filter(function() {
return $(this).attr('rel') == fieldName;
@@ -35,33 +91,36 @@
var start = data[field.range[0]];
var end = data[field.range[1]];
- $td.append(
- $('').html(start + ' - ' + end)
- );
+ $td.append($('').html(start + ' - ' + end));
} else {
- $td.append(
- $('').html(data[fieldName])
- );
+ $td.append($('').html(data[fieldName]));
}
} else if (field.select) {
- $td.append(
- $('').html(
- // Get matching option text
- $multi.find('select').filter(function() {
- return $(this).attr('name') == fieldName;
- }).find('option').filter(function() {
- return $(this).val() == data[fieldName];
- }).html()
- )
- );
+ $td.append($('').html(
+ // Get matching option text
+ $multi.find('select').filter(function() {
+ return $(this).attr('name') == fieldName;
+ }).find('option').filter(function() {
+ return $(this).val() == data[fieldName];
+ }).html()));
} else if (field.addButton && $.isArray(itemData) && !options.noSelect) {
- // Show VM data
- $td
- .html(
- options.multipleAdd ?
- itemData.length + ' VMs' : itemData[0].name
- )
- .click(function() {
+ if (options.multipleAdd) {
+ $addButton.click(function() {
+ _medit.vmList($multi,
+ options.listView,
+ options.context,
+ options.multipleAdd, 'Add VMs',
+ addItemAction,
+ {
+ multiRule: multiRule
+ });
+ });
+ $td.append($addButton);
+ } else {
+ // Show VM data
+ $td.html(options.multipleAdd ?
+ itemData.length + ' VMs' : itemData[0].name);
+ $td.click(function() {
var $browser = $(this).closest('.detail-view').data('view-args').$browser;
if (options.multipleAdd) {
@@ -70,30 +129,33 @@
_medit.details(itemData[0], $browser, { context: options.context });
}
});
+ }
} else if (field.custom) {
- $td.data('multi-custom-data', data[fieldName]);
- $(' ').addClass('button add-vm custom-action')
- .html(data && data[fieldName] && data[fieldName]['_buttonLabel'] ?
- data[fieldName]['_buttonLabel'] : field.custom.buttonLabel)
- .click(function() {
- var $button = $(this);
- field.custom.action({
- context: options.context ? options.context : cloudStack.context,
- data: $td.data('multi-custom-data'),
- $item: $td,
- response: {
- success: function(args) {
- if (args.data['_buttonLabel']) {
- $button.html(args.data['_buttonLabel']);
- }
- $td.data('multi-custom-data', args.data)
+ var $button = $(' ').addClass('button add-vm custom-action');
+
+ $td.data('multi-custom-data', data[fieldName]);
+ $button.html(data && data[fieldName] && data[fieldName]['_buttonLabel'] ?
+ data[fieldName]['_buttonLabel'] : field.custom.buttonLabel);
+ $button.click(function() {
+ var $button = $(this);
+
+ field.custom.action({
+ context: options.context ? options.context : cloudStack.context,
+ data: $td.data('multi-custom-data'),
+ $item: $td,
+ response: {
+ success: function(args) {
+ if (args.data['_buttonLabel']) {
+ $button.html(args.data['_buttonLabel']);
}
+ $td.data('multi-custom-data', args.data);
}
- })
- })
- .appendTo($td);
+ }
+ });
+ });
+ $button.appendTo($td);
}
- };
+ }
// Add blank styling for empty fields
if ($td.html() == '') {
@@ -108,14 +170,10 @@
});
// Actions column
- var $actions = $(' ').addClass('multi-actions').appendTo(
- $item.find('tr')
- );
+ var $actions = $(' | ').addClass('multi-actions').appendTo($item.find('tr'));
// Align action column width
- $actions.width(
- $multi.find('th.multi-actions').width() + 4
- );
+ $actions.width($multi.find('th.multi-actions').width() + 4);
// Action filter
var allowedActions = options.preFilter ? options.preFilter({
@@ -130,8 +188,7 @@
if (allowedActions && $.inArray(actionID, allowedActions) == -1) return true;
$actions.append(
- $(' | ')
- .addClass('action')
+ $(' ').addClass('action')
.addClass(actionID)
.append($('').addClass('icon'))
.attr({ title: action.label })
@@ -213,23 +270,111 @@
// Add expandable listing, for multiple-item
if (options.multipleAdd) {
// Create expandable box
- _medit.multiItem.expandable(
- $item.find('tr').data('multi-edit-data')
- ).appendTo($item);
-
+ _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();
- })
- );
+ $(' ').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, {
+ 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('Select');
+
+ var $dataList = $listView.dialog({
+ dialogClass: 'multi-edit-add-list panel',
+ width: 825,
+ title: label,
+ buttons: [
+ {
+ text: 'Apply',
+ 'class': 'ok',
+ click: function() {
+ if (!$listView.find('input[type=radio]:checked, input[type=checkbox]:checked').size()) {
+ cloudStack.dialog.notice({ message: 'Please select an instance '});
+
+ return false;
+ }
+
+ $dataList.fadeOut(function() {
+ complete($.map(
+ $listView.find('tr.multi-edit-selected'),
+
+ // Attach VM data to row
+ function(elem) {
+ return $(elem).data('json-obj');
+ }
+ ));
+ $dataList.remove();
+ });
+
+ $('div.overlay').fadeOut(function() {
+ $('div.overlay').remove();
+ });
+
+ return true;
+ }
+ },
+ {
+ text: '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
*/
@@ -241,13 +386,7 @@
$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);
});
});
},
@@ -325,28 +464,96 @@
});
},
- expandable: function(data) {
+ itemRow: function(item, itemActions, multiRule, $tbody) {
+ var $tr = $(' ').appendTo($tr).html(item.name));
+
+ if (itemActions) {
+ var $itemActions = $(' | ').addClass('actions item-actions');
+
+ $.each(itemActions, function(itemActionID, itemAction) {
+ if (itemActionID == 'add') return true;
+
+ 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();
+ 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;
+ });
+
+ $itemActions.appendTo($tr);
+ }
+
+ return $tr;
+ },
+
+ expandable: function(data, itemActions, multiRule) {
var $expandable = $(' ').addClass('expandable-listing');
- var $tbody = $('').appendTo(
- $('
|