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('data-item').append( - $('').append($('')) - ); - var $tr = $('').appendTo($item.find('tbody')); + var $tr; + var $item = $('
').addClass('data-item'); + var multiRule = data; + + $item.append($('
').append($(''))); + $tr = $('').appendTo($item.find('tbody')); if (itemData) { $tr.data('multi-edit-data', itemData); @@ -20,9 +22,63 @@ if (options.ignoreEmptyFields && !data[fieldName]) { return true; } - var $td = $(''); + + $tr.append($('').appendTo($tr).html(item.name)); + + if (itemActions) { + var $itemActions = $('').appendTo( - $('
').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 = $('
').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($expandable) - ); + var $tbody = $('').appendTo($('
').appendTo($expandable)); $(data).each(function() { var field = this; - var $tr = $('').appendTo($tbody); + var $tr = _medit.multiItem.itemRow(field, itemActions, multiRule, $tbody).appendTo($tbody); - $tr.append( - $('').appendTo($tr).html(field.name) - ); - }); - - cloudStack.evenOdd($tbody, 'tr', { - even: function($elem) { - $elem.addClass('even'); - }, - odd: function($elem) { - $elem.addClass('odd'); - } + cloudStack.evenOdd($tbody, 'tr', { + even: function($elem) { + $elem.addClass('even'); + }, + odd: function($elem) { + $elem.addClass('odd'); + } + }); }); return $expandable.hide(); @@ -364,6 +571,7 @@ var $addVM; var fields = args.fields; var actions = args.actions; + var itemActions = multipleAdd ? args.itemActions : null; var noSelect = args.noSelect; var context = args.context; var ignoreEmptyFields = args.ignoreEmptyFields; @@ -379,12 +587,12 @@ // Setup input table headers $.each(args.fields, function(fieldName, field) { - var $th = $('').addClass('multi-actions')); } - var vmList = function() { - // Create a listing of instances, based on limited information - // from main instances list view - var $listView; - var instances = $.extend(true, {}, args.listView, { - context: context, - uiCustom: true - }); - - instances.listView.actions = { - select: { - label: 'Select instance', - type: multipleAdd ? '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'); - - return $listView; - }; - $addVM.bind('click', function() { // Validate form first if (!$multiForm.valid()) { @@ -599,60 +766,16 @@ return true; } - $dataList = vmList($multi).dialog({ - dialogClass: 'multi-edit-add-list panel', - width: 825, - title: args.add.label, - buttons: [ - { - text: 'Apply', - 'class': 'ok', - click: function() { - if (!$dataList.find( - 'input[type=radio]:checked, input[type=checkbox]:checked' - ).size()) { - cloudStack.dialog.notice({ message: 'Please select an instance '}); - - return false; - } - - $dataList.fadeOut(function() { - addItem($.map( - $dataList.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(); + _medit.vmList($multi, + args.listView, + args.context, + multipleAdd, 'Add VMs', + addItem); return true; }); + var listView = args.listView; var getData = function() { dataProvider({ context: context, @@ -662,6 +785,7 @@ $(args.data).each(function() { var data = this; var itemData = this._itemData; + _medit.addItem( data, fields, @@ -670,10 +794,12 @@ actions, { multipleAdd: multipleAdd, + itemActions: itemActions, noSelect: noSelect, context: $.extend(true, {}, context, this._context), ignoreEmptyFields: ignoreEmptyFields, - preFilter: actionPreFilter + preFilter: actionPreFilter, + listView: listView } ).appendTo($dataBody); });
').addClass(fieldName).html(field.label.toString()) - .attr('rel', fieldName) - .appendTo($thead); - var $td = $('').addClass(fieldName) - .attr('rel', fieldName) - .appendTo($inputForm); + var $th = $('').addClass(fieldName).html(field.label.toString()); + $th.attr('rel', fieldName); + $th.appendTo($thead); + var $td = $('').addClass(fieldName); + $td.attr('rel', fieldName); + $td.appendTo($inputForm); if (field.isHidden) { $th.hide(); @@ -392,12 +600,12 @@ } if (field.select) { - var $select = $(''); + + $select.attr({ + name: fieldName + }); + $select.appendTo($td); field.select({ $select: $select, $form: $multiForm, @@ -407,7 +615,6 @@ $('