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('loading-overlay') + .prependTo($placeholder); + + addAction.action({ + context: context, + data: args.data, + response: { + success: function(args) { + cloudStack.ui.notifications.add( + // Notification + { + desc: addAction.label + }, + + // Success + function(args) { + $loading.remove(); + $placeholder.closest('.vpc-network-chart').trigger('reload'); + }, + + {}, + + // Error + function(args) { + $loading.remove(); + } + ); + }, + error: function(errorMsg) { + cloudStack.dialog.notice({ message: _s(errorMsg) }); + $loading.remove(); + } + } + }); + } + }); + }; + + var elems = { + tier: function(args) { + var tier = args.tier; + var context = $.extend(true, {}, args.context, { + networks: [tier] + }); + var dashboardItems = args.dashboardItems; + var $tier = $('
').addClass('tier-item'); + var $header = $('
').addClass('header'); + var $title = $('
').addClass('title').append($('')); + var $content = $('
').addClass('content'); + var $browser = $('#browser .container'); + var $dashboard = elems.dashboard({ + context: context, + dashboardItems: dashboardItems + }); + var $detailLink = $('
').addClass('detail-link'); + var $info = $('
').addClass('info'); + var $cidrLabel = $('').addClass('cidr-label'); + var $cidr = $('').addClass('cidr'); + + $detailLink.click(function() { + $browser.cloudBrowser('addPanel', { + title: tier.displayname ? tier.displayname : tier.name, + complete: function($panel) { + var $detailView = $('
').detailView( + $.extend(true, {}, cloudStack.vpc.tiers.detailView, { + $browser: $browser, + context: context, + onActionComplete: function() { + $tier.closest('.vpc-network-chart').trigger('reload'); + } + }) + ); + + $detailView.appendTo($panel); + } + }); + }); + + $cidrLabel.html('CIDR: '); + $cidr.html(tier.cidr); + $title.find('span').html(tier.displayname ? tier.displayname : tier.name); + $header.append($title, $detailLink); + $info.append($cidrLabel, $cidr); + $content.append($dashboard, $info); + $tier.append($header, $content); + + return $tier; + }, + + router: function(args) { + var $router = elems.tier({ + context: args.context, + tier: { + name: 'Router', + }, + dashboardItems: args.dashboardItems + }).addClass('router'); + + $router.find('.info, .detail-link').remove(); + $router.find('.header').prepend($('').addClass('icon').html(' ')); + + return $router; + }, + + tierPlaceholder: function(args) { + var context = args.context; + var $placeholder = $('
').addClass('tier-placeholder'); + + $placeholder.append($('').append('Create network')); + $placeholder.click(function() { + addTierDialog({ + context: context, + $placeholder: $placeholder + }); + }); + + return $placeholder; + }, + + dashboard: function(args) { + var $dashboard = $('
').addClass('dashboard'); + var context = args.context; + var tier = context.networks[0]; + + $(args.dashboardItems).map(function(index, dashboardItem) { + var $dashboardItem = $('
').addClass('dashboard-item'); + var $name = $('
').addClass('name').append($('')); + var $total = $('
').addClass('total').append($('')); + var id = dashboardItem.id; + + $name.find('span').html(dashboardItem.name); + + + if (dashboardItem.totalMultiLine) { + $total.find('span').html(dashboardItem.totalMultiLine); + $total.addClass('multiline'); + } else { + $total.find('span').html(dashboardItem.total ? dashboardItem.total : 0); + } + + $dashboardItem.append($total, $name); + $dashboardItem.appendTo($dashboard); + + $dashboardItem.click(function() { + var section = cloudStack.vpc.sections[id]; + var $section = $('
'); + var $loading = $('
').addClass('loading-overlay'); + + if ($.isFunction(section)) { + section = cloudStack.vpc.sections[id](); + } + + var before = section.before; + var load = function() { + $('#browser .container').cloudBrowser('addPanel', { + title: tier.name + ' - ' + dashboardItem.name, + maximizeIfSelected: true, + complete: function($panel) { + if (section.listView) { + $section.listView($.extend(true, {}, section, { + onActionComplete: function() { + $dashboardItem.closest('.vpc-network-chart').trigger('reload'); + }, + context: context + })); + } + + $section.appendTo($panel); + } + }); + }; + + if (before) { + before.check({ + context: context, + response: { + success: function(result) { + // true means content exists + if (result) { + load(); + } else { + cloudStack.dialog.confirm({ + message: before.messages.confirm, + action: function() { + $loading.appendTo($dashboardItem.closest('.vpc-network-chart')); + before.action({ + context: context, + response: { + success: function() { + $loading.remove(); + $dashboardItem.closest('.vpc-network-chart').trigger('reload'); + load(); + } + } + }); + } + }) + } + } + } + }); + } else { + load(); + } + }); + }); + + return $dashboard; + } + }; + + cloudStack.modules.vpc = function(module) { + var vpc = cloudStack.vpc; + var vpcSection = cloudStack.sections.network.sections.vpc; + var listConfigureAction = vpcSection.listView.actions.configureVpc.action; + var detailsConfigureAction = vpcSection.listView.detailView.actions.configureVpc.action; + + var vpcChart = function(args) { + var context = args.context; + var vpcItem = context.vpc[0]; + + var chart = function(args) { + args = args ? args : {}; + + var $chart = $('
').addClass('vpc-network-chart'); + var $tiers = $('
').addClass('tiers'); + var $toolbar = $('
').addClass('toolbar'); + + $toolbar.appendTo($chart); + $tiers.appendTo($chart); + + // Get tiers + var $loading = $('
').addClass('loading-overlay').prependTo($chart); + vpc.tiers.dataProvider({ + context: context, + response: { + success: function(data) { + var tiers = data.tiers; + var $placeholder = elems.tierPlaceholder({ + context: context + }); + + $(tiers).map(function(index, tier) { + var $tier = elems.tier({ + context: context, + tier: tier, + dashboardItems: tier._dashboardItems + }); + $tier.appendTo($tiers); + }); + + // Add placeholder tier + $tiers.append($placeholder); + $loading.remove(); + + if (!tiers.length) { + addTierDialog({ + context: context, + $placeholder: $placeholder + }); + } + + if (args.complete) { + args.complete($chart); + } + + // Router + elems.router({ + context: context, + dashboardItems: data.routerDashboard + }).appendTo($chart); + } + } + }); + + $chart.bind('reload', function() { + chart({ + complete: function($newChart) { + $chart.replaceWith($newChart); + } + }); + }); + + return $chart; + }; + + $('#browser .container').cloudBrowser('addPanel', { + title: vpcItem.displaytext ? vpcItem.displaytext : vpcItem.name, + maximizeIfSelected: true, + complete: function($panel) { + var $chart = chart(); + + $chart.appendTo($panel); + } + }); + }; + + listConfigureAction.custom = vpcChart; + detailsConfigureAction.custom = vpcChart; + }; +}(jQuery, cloudStack)); diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index c76d843ed6e..31237a8855b 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -204,6 +204,14 @@ affinitygroupid: args.context.affinityGroups[0].id }); } + + if("vpc" in args.context && + "networks" in args.context) { + $.extend(data, { + vpcid: args.context.vpc[0].id, + networkid: args.context.networks[0].id + }); + } $.ajax({ url: createURL('listVirtualMachines'), diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js index 035299059c2..d87f0dcb0ae 100644 --- a/ui/scripts/sharedFunctions.js +++ b/ui/scripts/sharedFunctions.js @@ -276,7 +276,7 @@ cloudStack.actionFilter = { guestNetwork: function(args) { var jsonObj = args.context.item; var allowedActions = []; - + allowedActions.push('replaceacllist'); if(jsonObj.type == 'Isolated') { allowedActions.push('edit'); //only Isolated network is allowed to upgrade to a different network offering (Shared network is not allowed to) allowedActions.push('restart'); diff --git a/ui/scripts/ui/dialog.js b/ui/scripts/ui/dialog.js index bb372fbf3d6..45f928e38a9 100644 --- a/ui/scripts/ui/dialog.js +++ b/ui/scripts/ui/dialog.js @@ -14,7 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -(function($, cloudStack) { +(function($, cloudStack, _l) { cloudStack.dialog = { /** * Error message form @@ -506,6 +506,94 @@ } }, + // Dialog with list view selector + listView: function(args) { + var listView = args.listView; + var after = args.after; + var context = args.context; + var $listView = $('
'); + + listView.actions = { + select: { + label: _l('label.select.instance'), + type: listView.type, + 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'); + } + } + } + } + }; + + // Init list view + $listView = $('
').listView({ + context: context, + uiCustom: true, + listView: listView + }); + + // Change action label + $listView.find('th.actions').html(_l('label.select')); + + $listView.dialog({ + dialogClass: 'multi-edit-add-list panel', + width: 825, + title: _l('Select VM'), + 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.instance')}); + + return false; + } + + after({ + context: $.extend(true, {}, context, { + instances: $listView.find('tr.multi-edit-selected').map(function(index, row) { + var $row = $(row); + + return $row.data('json-obj'); + }) + }) + }); + + $listView.remove(); + + $('div.overlay').remove(); + } + }, + { + text: _l('label.cancel'), + 'class': 'cancel', + click: function() { + $listView.fadeOut(function() { + $listView.remove(); + }); + $('div.overlay').fadeOut(function() { + $('div.overlay').remove(); + }); + } + } + ] + }).parent('.ui-dialog').overlay(); + }, + /** * to change a property(e.g. validation) of a createForm field after a createForm is rendered */ @@ -612,4 +700,4 @@ return false; } }; -})(window.jQuery, window.cloudStack); +})(window.jQuery, window.cloudStack, _l); diff --git a/ui/scripts/ui/widgets/detailView.js b/ui/scripts/ui/widgets/detailView.js index a721a44ec05..11e82b35f1c 100644 --- a/ui/scripts/ui/widgets/detailView.js +++ b/ui/scripts/ui/widgets/detailView.js @@ -70,7 +70,8 @@ action.notification : {}; var messages = action.messages; var id = args.id; - var context = args.context ? args.context : $detailView.data('view-args').context; + var context = $.extend(true, {}, + args.context ? args.context : $detailView.data('view-args').context); var _custom = $detailView.data('_custom'); var customAction = action.action.custom; var noAdd = action.noAdd; @@ -273,7 +274,7 @@ notification.desc = messages.notification(messageArgs); notification.section = 'instances'; - if (!action.createForm) { + if (!action.createForm && !action.listView) { if (messages && messages.confirm) { cloudStack.dialog.confirm({ message: messages.confirm(messageArgs), @@ -286,7 +287,7 @@ } else { performAction({ id: id }); } - } else { + } else if (action.createForm) { cloudStack.dialog.createForm({ form: action.createForm, after: function(args) { @@ -301,6 +302,15 @@ }, context: context }); + } else if (action.listView) { + cloudStack.dialog.listView({ + context: context, + listView: action.listView, + after: function(args) { + context = args.context; + performAction(); + } + }); } } }, diff --git a/ui/scripts/ui/widgets/listView.js b/ui/scripts/ui/widgets/listView.js index 0d5ef6fc751..d68f91fac7c 100644 --- a/ui/scripts/ui/widgets/listView.js +++ b/ui/scripts/ui/widgets/listView.js @@ -104,6 +104,10 @@ cloudStack.ui.notifications.add( notification, function(args) { + if (listViewArgs.onActionComplete) { + listViewArgs.onActionComplete(); + } + if ($item.is(':visible') && !isHeader) { replaceItem( $item, @@ -175,6 +179,10 @@ if (additional && additional.success) additional.success(args); + if (listViewArgs.onActionComplete == true) { + listViewArgs.onActionComplete(); + } + cloudStack.ui.notifications.add( notification, @@ -213,6 +221,10 @@ if (options.complete) { options.complete(args); } + + if (listViewArgs.onActionComplete) { + listViewArgs.onActionComplete(); + } }, {}, @@ -1187,6 +1199,10 @@ $quickViewTooltip.hide(); }, onActionComplete: function() { + if (listViewArgs.onActionComplete) { + listViewArgs.onActionComplete(); + } + $tr.removeClass('loading').find('td:last .loading').remove(); $quickViewTooltip.remove(); } @@ -1798,6 +1814,8 @@ }); } + detailViewArgs.data.onActionComplete = listViewArgs.onActionComplete; + createDetailView( detailViewArgs, function($detailView) { //complete(), callback funcion @@ -1873,7 +1891,7 @@ if (!options) options = {}; var viewArgs = listView.data('view-args'); - var listViewArgs = viewArgs.listView ? viewArgs.listView : viewArgs; + var listViewArgs = $.isPlainObject(viewArgs.listView) ? viewArgs.listView : viewArgs; var targetArgs = listViewArgs.activeSection ? listViewArgs.sections[ listViewArgs.activeSection ].listView : listViewArgs; @@ -1903,7 +1921,7 @@ var $newRow; var $listView = $row.closest('.list-view'); var viewArgs = $listView.data('view-args'); - var listViewArgs = viewArgs.listView ? viewArgs.listView : viewArgs; + var listViewArgs = $.isPlainObject(viewArgs.listView) ? viewArgs.listView : viewArgs; var targetArgs = listViewArgs.activeSection ? listViewArgs.sections[ listViewArgs.activeSection ].listView : listViewArgs; diff --git a/ui/scripts/ui/widgets/multiEdit.js b/ui/scripts/ui/widgets/multiEdit.js index a1272fda31c..936c20c48a0 100755 --- a/ui/scripts/ui/widgets/multiEdit.js +++ b/ui/scripts/ui/widgets/multiEdit.js @@ -30,347 +30,363 @@ var $tr; var $item = $('
').addClass('data-item'); var multiRule = data; + var reorder = options.reorder; $item.append($('').append($(''))); $tr = $('').appendTo($item.find('tbody')); + $item.data('json-obj', multiRule); if (itemData) { $tr.data('multi-edit-data', itemData); } - // Setup columns - $.each(fields, function(fieldName, field) { - if (options.ignoreEmptyFields && !data[fieldName]) { - return true; - } + // Add reorder actions + if (reorder) { + $(''); + var itemName = multiRule._itemName ? item[multiRule._itemName] : item.name; + var $itemName = $('').html(_s(itemName)); - // Show list view of selected VMs - $browser.cloudBrowser('addPanel', { - title: _l('label.item.listing'), - data: '', - noSelectPanel: true, - maximizeIfSelected: true, - complete: function($newPanel) { - return $newPanel.listView(listViewArgs); + $tr.append($(''); - var itemName = multiRule._itemName ? item[multiRule._itemName] : item.name; - var $itemName = $('').html(_s(itemName)); - - $tr.append($('').appendTo($('
').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($expandable)); - - $(data).each(function() { - var field = this; - var $tr = _medit.multiItem.itemRow(field, itemActions, multiRule, $tbody).appendTo($tbody); - - cloudStack.evenOdd($tbody, 'tr', { - even: function($elem) { - $elem.addClass('even'); - }, - odd: function($elem) { - $elem.addClass('odd'); - } - }); + return true; }); - return $expandable.hide(); + $itemActions.appendTo($tr); } + + return $tr; + }, + + expandable: function(data, itemActions, multiRule) { + var $expandable = $('
').addClass('expandable-listing'); + var $tbody = $('
').appendTo($('
').appendTo($expandable)); + + $(data).each(function() { + var field = this; + var $tr = _medit.multiItem.itemRow(field, itemActions, multiRule, $tbody).appendTo($tbody); + + cloudStack.evenOdd($tbody, 'tr', { + even: function($elem) { + $elem.addClass('even'); + }, + odd: function($elem) { + $elem.addClass('odd'); + } + }); + }); + + return $expandable.hide(); } - }; + } +}; - $.fn.multiEdit = function(args) { - var dataProvider = args.dataProvider; - var multipleAdd = args.multipleAdd; - var tags = args.tags; - var $multi = $('
').addClass('multi-edit').appendTo(this); - var $multiForm = $('
').appendTo($multi); - var $inputTable = $('
').addClass('multi-edit').appendTo($multiForm); - var $dataTable = $('
').addClass('data').appendTo($multi); - 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; - var actionPreFilter = args.actionPreFilter; - var readOnlyCheck = args.readOnlyCheck; +$.fn.multiEdit = function(args) { + var dataProvider = args.dataProvider; + var multipleAdd = args.multipleAdd; + var tags = args.tags; + var $multi = $('
').addClass('multi-edit').appendTo(this); + var $multiForm = $('').appendTo($multi); + var $inputTable = $('
').addClass('multi-edit').appendTo($multiForm); + var $dataTable = $('
').addClass('data').appendTo($multi); + 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; + var actionPreFilter = args.actionPreFilter; + var readOnlyCheck = args.readOnlyCheck; + var reorder = args.reorder; - var $thead = $('
').appendTo( - $('').appendTo($inputTable) - ); - var $inputForm = $('').appendTo( - $('').appendTo($inputTable) - ); - var $dataBody = $('
').addClass('data-body').appendTo($dataTable); + var $thead = $('
').appendTo( + $('').appendTo($inputTable) + ); + var $inputForm = $('').appendTo( + $('').appendTo($inputTable) + ); + var $dataBody = $('
').addClass('data-body').appendTo($dataTable); - // Setup input table headers - $.each(args.fields, function(fieldName, field) { - var $th = $('
').addClass(fieldName).html(_l(field.label.toString())); + // Setup input table headers + + if (reorder) { + $('').addClass('reorder').appendTo($thead); + $('').addClass('reorder').appendTo($inputForm); + $multi.find('.data-body').sortable({ + handle: '.action.moveDrag', + + update: function(event, ui) { + reorder.moveDrag.action({ + context: $.extend(true, {}, context, { + // Passes all rules, so that each index can be updated + multiRule: $multi.find('.data-item').map(function(index, item) { + return $(item).data('json-obj'); + }) + }), + response: { + success: function(args) { + } + } + }); + } + }); + } + + $.each(args.fields, function(fieldName, field) { + if (!field) return true; + + var $th = $('').addClass(fieldName).html(_l(field.label.toString())); $th.attr('rel', fieldName); $th.appendTo($thead); var $td = $('').addClass(fieldName); @@ -1055,7 +1124,8 @@ ignoreEmptyFields: ignoreEmptyFields, preFilter: actionPreFilter, listView: listView, - tags: tags + tags: tags, + reorder: reorder } ).appendTo($dataBody); }); diff --git a/ui/scripts/vpc.js b/ui/scripts/vpc.js index 4890dcc6ee9..3581b88ea34 100644 --- a/ui/scripts/vpc.js +++ b/ui/scripts/vpc.js @@ -26,9 +26,49 @@ } return hiddenFields; // Returns fields to be hidden + }, + reorder: { + moveDrag: { + action: function(args) { + $(args.context.multiRule.toArray().reverse()).map(function(index, rule) { + $.ajax({ + url: createURL('updateNetworkACLItem'), + data: { + id: rule.id, + number: index + 1 + }, + success: function(json) { + var pollTimer = setInterval(function() { + pollAsyncJobResult({ + _custom: { jobId: json.createnetworkaclresponse.jobid }, + complete: function() { + clearInterval(pollTimer); + }, + error: function(errorMsg) { + clearInterval(pollTimer); + cloudStack.dialog.notice(errorMsg); + } + }); + }, 1000); + } + }); + }); + } + } }, fields: { 'cidrlist': { edit: true, label: 'label.cidr' }, + action: { + label: 'Action', + select: function(args) { + args.response.success({ + data: [ + { name: 'Allow', description: 'Allow' }, + { name: 'Deny', description: 'Deny' } + ] + }); + } + }, 'protocol': { label: 'label.protocol', select: function(args) { @@ -45,28 +85,27 @@ var $otherFields = $inputs.filter(function() { var name = $(this).attr('name'); - return name != 'icmptype' && name != 'icmpcode' && name != 'cidrlist'; + return name != 'protocolnumber' && + name != 'icmptype' && + name != 'icmpcode' && + name != 'cidrlist'; }); - var $protocolinput = args.$form.find('th,td'); + var $protocolinput = args.$form.find('td input'); var $protocolFields = $protocolinput.filter(function(){ - var name = $(this).attr('rel'); + var name = $(this).attr('name'); return $.inArray(name,['protocolnumber']) > -1; }); - if($(this).val() == 'protocolnumber' ){ - - $protocolFields.show(); - } - else{ - $protocolFields.hide(); - } - - - if ($(this).val() == 'icmp') { + if ($(this).val() == 'protocolnumber' ){ + $icmpFields.hide(); + $otherFields.hide(); + $protocolFields.show().addClass('required'); + } else if ($(this).val() == 'icmp') { $icmpFields.show(); $icmpFields.attr('disabled', false); + $protocolFields.hide().removeClass('required'); $otherFields.attr('disabled', 'disabled'); $otherFields.hide(); $otherFields.parent().find('label.error').hide(); @@ -77,6 +116,7 @@ $icmpFields.attr('disabled', 'disabled'); $icmpFields.hide(); $icmpFields.parent().find('label.error').hide(); + $protocolFields.hide().removeClass('required'); } }); @@ -90,12 +130,14 @@ ] }); + + setTimeout(function() { args.$select.trigger('change'); }, 100); } }, - 'protocolnumber': {label:'Protocol Number',isDisabled:true,isHidden:true,edit:true}, - 'startport': { edit: true, label: 'label.start.port' , isOptional:true }, - 'endport': { edit: true, label: 'label.end.port' , isOptional:true}, + 'protocolnumber': {label:'Protocol Number',edit:true}, + 'startport': { edit: true, label: 'label.start.port', isOptional: true }, + 'endport': { edit: true, label: 'label.end.port', isOptional: true }, 'networkid': { label: 'Select Tier', select: function(args) { @@ -180,8 +222,7 @@ $.ajax({ url: createURL('createNetworkACL'), data: $.extend(args.data, { - networkid: args.context.networks ? - args.context.networks[0].id : args.data.networkid + aclid: args.context.aclLists[0].id }), dataType: 'json', success: function(data) { @@ -189,14 +230,6 @@ _custom: { jobId: data.createnetworkaclresponse.jobid, getUpdatedItem: function(json) { - var networkName = $multi.find('select[name=networkid] option[value=' + args.data.networkid + ']').html(); - var data = $.extend(json.queryasyncjobresultresponse.jobresult.networkacl, { - networkid: networkName - }); - var aclRules = $multi.data('acl-rules'); - - aclRules.push(data); - $multi.data('acl-rules', aclRules); $(window).trigger('cloudStack.fullRefresh'); return data; @@ -231,7 +264,7 @@ args.response.success({ _custom: { jobId: jobID, - getUpdatedItem: function() { + getUpdateIdtem: function() { $(window).trigger('cloudStack.fullRefresh'); } }, @@ -289,6 +322,531 @@ }; cloudStack.vpc = { + // nTier sections + sections: { + tierVMs: function() { + var list = $.extend(true, {}, cloudStack.sections.instances); + + list.listView.actions.add.action.custom = cloudStack.uiCustom.instanceWizard( + $.extend(true, {}, cloudStack.instanceWizard, { + pluginForm: { name: 'vpcTierInstanceWizard' } + }) + ); + + return list; + }, + + tierPortForwarders: function() { + return cloudStack.vpc.ipAddresses.listView(); + }, + + tierStaticNATs: function() { + return cloudStack.vpc.staticNatIpAddresses.listView(); + }, + + // Internal load balancers + internalLoadBalancers: { + title: 'Internal LB', + listView: { + id: 'internalLoadBalancers', + fields: { + name: { label: 'label.name' }, + sourceipaddress: { label: 'Source IP Address' } + }, + dataProvider: function(args) { + $.ajax({ + url: createURL('listLoadBalancers'), + data: { + networkid: args.context.networks[0].id + }, + success: function(json) { + var items = json.listloadbalancerssresponse.loadbalancer; + args.response.success({ data: items }); + + } + }); + }, + actions: { + add: { + label: 'Add Internal LB', + createForm: { + title: 'Add Internal LB', + fields: { + name: { label: 'label.name', validation: { required: true } }, + description: { label: 'label.description', validation: { required: false } }, + sourceipaddress: { label: 'Source IP Address', validation: { required: false } }, + sourceport: { label: 'sourceport', validation: { required: true } }, + instanceport: { label: 'instanceport', validation: { required: true } }, + algorithm: { + label: 'label.algorithm', + validation: { required: true }, + select: function(args) { + args.response.success({ + data: [ + { id: 'source', description: 'source' }, + { id: 'roundrobin', description: 'roundrobin' }, + { id: 'leastconn', description: 'leastconn' } + ] + }); + } + } + } + }, + messages: { + notification: function(args) { + return 'Add Internal LB'; + } + }, + action: function(args) { + var data = { + name: args.data.name, + sourceport: args.data.sourceport, + instanceport: args.data.instanceport, + algorithm: args.data.algorithm, + networkid: args.context.networks[0].id, + sourceipaddressnetworkid: args.context.networks[0].id, + scheme: 'Internal' + }; + if(args.data.description != null && args.data.description.length > 0){ + $.extend(data, { + description: args.data.description + }); + } + if(args.data.sourceipaddress != null && args.data.sourceipaddress.length > 0){ + $.extend(data, { + sourceipaddress: args.data.sourceipaddress + }); + } + $.ajax({ + url: createURL('createLoadBalancer'), + data: data, + success: function(json){ + var jid = json.createloadbalancerresponse.jobid; + args.response.success( + {_custom: + {jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.loadbalancer; + } + } + } + ); + } + }); + + args.response.success(); + }, + notification: { + poll: pollAsyncJobResult + } + } + }, + + detailView: { + name: 'Internal Lb details', + actions: { + assignVm: { + label: 'Assign VMs to LB', + messages: { + notification: function(args) { return 'Assign VM to internal LB rule'; } + }, + listView: $.extend(true, {}, cloudStack.sections.instances.listView, { + type: 'checkbox', + filters: false, + dataProvider: function(args) { + $.ajax({ + url: createURL('listVirtualMachines'), + data: { + networkid: args.context.networks[0].id, + listAll: true + }, + success: function(json) { + var instances = json.listvirtualmachinesresponse.virtualmachine; + + // Pre-select existing instances in LB rule + $(instances).map(function(index, instance) { + instance._isSelected = $.grep( + args.context.internalLoadBalancers[0].loadbalancerinstance, + + function(lbInstance) { + return lbInstance.id == instance.id; + } + ).length ? true : false; + }); + + args.response.success({ + data: instances + }); + } + }); + } + }), + action: function(args) { + var vms = args.context.instances; + var array1 = []; + for(var i = 0; i < vms.length; i++) { + array1.push(vms[i].id); + } + var virtualmachineids = array1.join(','); + + $.ajax({ + url: createURL('assignToLoadBalancerRule'), + data: { + id: args.context.internalLoadBalancers[0].id, + virtualmachineids: virtualmachineids + }, + dataType: 'json', + async: true, + success: function(data) { + var jid = data.assigntoloadbalancerruleresponse.jobid; + args.response.success({ + _custom: { jobId: jid } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + } + }, + tabs: { + details: { + title: 'label.details', + fields: [ + { + name: { label: 'label.name' } + }, + { + id: { label: 'label.id' } + } + ], + dataProvider: function(args) { + $.ajax({ + url: createURL('listLoadBalancers'), + data: { + id: args.context.internalLoadBalancers[0].id + }, + success: function(json) { + var item = json.listloadbalancerssresponse.loadbalancer[0]; + args.response.success({ data: item }); + } + }); + } + }, + rules: { + title: 'label.rules', + multiple: true, + fields: [ + { + sourceport: { label: 'Source Port' }, + instanceport: { label: 'Instance Port' } + } + ], + dataProvider: function(args) { + $.ajax({ + url: createURL('listLoadBalancers'), + data: { + id: args.context.internalLoadBalancers[0].id + }, + success: function(json) { + var item = json.listloadbalancerssresponse.loadbalancer[0]; + args.response.success({ data: item.loadbalancerrule }); + } + }); + } + } , + assignedVms: { + title: 'Assigned VMs', + multiple: true, + fields: [ + { + name: { label: 'label.name' }, + ipaddress: { label: 'label.ip.address' } + } + ], + dataProvider: function(args) { + $.ajax({ + url: createURL('listLoadBalancers'), + data: { + id: args.context.internalLoadBalancers[0].id + }, + success: function(json) { + var item = json.listloadbalancerssresponse.loadbalancer[0]; + args.response.success({ data: item.loadbalancerinstance }); + } + }); + } + } + } + } + } + }, + publicLbIps: { + title: 'Public LB', + listView: { + id: 'publicLbIps', + fields: { + ipaddress: { + label: 'label.ips', + converter: function(text, item) { + if (item.issourcenat) { + return text + ' [' + _l('label.source.nat') + ']'; + } + + return text; + } + }, + zonename: { label: 'label.zone' }, + virtualmachinedisplayname: { label: 'label.vm.name' }, + state: { + converter: function(str) { + // For localization + return str; + }, + label: 'label.state', indicator: { 'Allocated': 'on', 'Released': 'off' } + } + }, + dataProvider: function(args) { + $.ajax({ + url: createURL('listPublicIpAddresses'), + async: false, + data: { networkid: args.context.networks[0].id, forloadbalancing: true }, + success: function(json) { + var items = json.listpublicipaddressesresponse; + args.response.success({ data: items }); + } + }); + } + } + }, + + // Private gateways + privateGateways: function() { + return cloudStack.vpc.gateways.listView() + }, + + // Public IP Addresses + publicIPs: function() { + return cloudStack.vpc.ipAddresses.listView() + }, + + // Network ACL lists + networkACLLists: { + listView: { + id: 'aclLists', + fields: { + name: { label: 'label.name' }, + description: {label:'Description'}, + id: { label: 'id' } + }, + dataProvider: function(args) { + $.ajax({ + url:createURL('listNetworkACLLists&vpc_id=' + args.context.vpc[0].id), + success:function(json){ + var items = json.listnetworkacllistsresponse.networkacllist; + + args.response.success({ + data:items + }); + } + }); + }, + + actions:{ + add:{ + label:'Add ACL List', + createForm:{ + label: 'Add ACL List', + fields:{ + name:{label:'ACL List Name',validation:{required:true}}, + description:{label:'Description',validation:{required:true}} + } + }, + messages: { + notification: function(args) { + return 'Add Network ACL List'; + } + }, + action:function(args){ + var data = { + name:args.data.name, + description:args.data.description + + }; + + $.ajax({ + url:createURL('createNetworkACLList&vpcid='+ args.context.vpc[0].id), + data:data, + success:function(json){ + var items = json.createnetworkacllistresponse; + args.response.success({ + data:items + }); + } + }); + } + } + }, + + detailView: { + isMaximized: true, + actions:{ + remove: { + label:'Delete ACL List', + messages: { + confirm: function(args) { + return 'Are you sure you want to delete this ACL list ?'; + }, + notification: function(args) { + return 'Delete ACL list'; + } + }, + action:function(args){ + $.ajax({ + url:createURL('deleteNetworkACLList&id=' + args.context.aclLists[0].id), + success:function(json){ + var jid = json.deletenetworkacllistresponse.jobid; + args.response.success( + {_custom: + { jobId: jid + } + } + ); + }, + error:function(json){ + args.response.error(parseXMLHttpResponse(json)); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + } + }, + + tabs: { + details: { + title: 'label.details', + fields: [ + { + name: { label: 'label.name', isEditable: true }, + description: {label:'Description'}, + id:{label:'id'} + } + ], + dataProvider: function(args) { + var items = args.context.aclLists[0]; + setTimeout(function() { + args.response.success({ + data: items, + actionFilter: function(args) { + var allowedActions = []; + if(isAdmin()) { + allowedActions.push("remove"); + + } + return allowedActions; + } + }); + }); + } + }, + + aclRules: { + title: 'ACL List Rules', + custom: function(args) { + return $('
').multiEdit($.extend(true, {}, aclMultiEdit, { + context: args.context, + fields: { + networkid: false + }, + dataProvider: function(args) { + $.ajax({ + url:createURL('listNetworkACLs&aclid=' + args.context.aclLists[0].id), + success:function(json){ + var items = json.listnetworkaclsresponse.networkacl; + + args.response.success({ + data:items + /* { + cidrlist: '10.1.1.0/24', + protocol: 'TCP', + startport: 22, endport: 22, + networkid: 0, + traffictype: 'Egress' + }, + { + cidrlist: '10.2.1.0/24', + protocol: 'UDP', + startport: 56, endport: 72, + networkid: 0, + trafficType: 'Ingress' + } + ]*/ + }); + } + }); + } + })); + } + } + } + } + } + }, + siteToSiteVPNs: function() { + return $.extend(true, {}, cloudStack.vpc.siteToSiteVPN, { + // siteToSiteVPN is multi-section so doesn't have an explicit + // 'listView' block + // + // -- use this as a flag for VPC chart to render as a list view + listView: true, + before: { + messages: { + confirm: 'Please confirm that you would like to create a site-to-site VPN gateway for this VPC.', + notification: 'Create site-to-site VPN gateway' + }, + check: function(args) { + var items; + + $.ajax({ + url: createURL('listVpnGateways&listAll=true'), + data: { + vpcid: args.context.vpc[0].id + }, + success: function(json) { + var items = json.listvpngatewaysresponse.vpngateway; + + args.response.success(items && items.length); + } + }); + }, + action: function(args) { + $.ajax({ + url: createURL("createVpnGateway"), + data: { + vpcid: args.context.vpc[0].id + }, + success: function(json) { + var jid = json.createvpngatewayresponse.jobid; + var pollTimer = setInterval(function() { + pollAsyncJobResult({ + _custom: { jobId: jid }, + complete: function() { + clearInterval(pollTimer); + args.response.success(); + } + }); + }, g_queryAsyncJobResultInterval); + } + }); + } + } + }); + } + }, + routerDetailView: function() { return { title: 'VPC router details', @@ -594,7 +1152,7 @@ ipAddresses: { listView: function() { var listView = $.extend(true, {}, cloudStack.sections.network.sections.ipAddresses); - + listView.listView.fields = { ipaddress: listView.listView.fields.ipaddress, zonename: listView.listView.fields.zonename, @@ -605,6 +1163,35 @@ return listView; } }, + staticNatIpAddresses: { + listView: function() { + var listView = $.extend(true, {}, cloudStack.sections.network.sections.ipAddresses); + + listView.listView.fields = { + ipaddress: listView.listView.fields.ipaddress, + zonename: listView.listView.fields.zonename, + associatednetworkname: { label: 'label.network.name' }, + state: listView.listView.fields.state + }; + + listView.listView.dataProvider = function(args) { + $.ajax({ + url: createURL('listPublicIpAddresses'), + data: { networkid: args.context.networks[0].id, isstaticnat: true }, + success: function(json) { + args.response.success({ + data: json.listpublicipaddressesresponse.publicipaddress + }); + }, + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + }); + }; + + return listView; + } + }, acl: { multiEdit: aclMultiEdit, @@ -1451,7 +2038,7 @@ listAll: true }, success: function(json) { - var items = json.listvpncustomergatewaysresponse.vpncustomergateway; + var items = json.listvpncustomergatewaysresponse.vpncustomergateway ? json.listvpncustomergatewaysresponse.vpncustomergateway: []; args.response.success({ data: $.map(items, function(item) { return { @@ -1651,6 +2238,9 @@ path: 'network.ipAddresses', label: 'label.menu.ipaddresses', preFilter: function(args) { + return false; + + /// Disabled if (args.context.networks[0].state == 'Destroyed') return false; @@ -1895,7 +2485,76 @@ notification: { poll: pollAsyncJobResult } - } + }, + + replaceacllist:{ + + label:'Replace ACL List', + createForm:{ + title:'Replace ACL List', + label:'Replace ACL List', + fields:{ + aclid:{ + label:'ACL', + select:function(args){ + $.ajax({ + url: createURL('listNetworkACLLists'), + dataType: 'json', + async: true, + success: function(json) { + var objs = json.listnetworkacllistsresponse.networkacllist; + var items = []; + $(objs).each(function() { + + items.push({id: this.id, description: this.name}); + }); + args.response.success({data: items}); + } + }); + } + } + } + }, + action: function(args) { + $.ajax({ + url: createURL("replaceNetworkACLList&networkid=" + args.context.networks[0].id + "&aclid=" + args.data.aclid ), + dataType: "json", + success: function(json) { + var jid = json.replacenetworkacllistresponse.jobid; + args.response.success( + + {_custom: + { + jobId: jid, + getUpdatedItem: function(json) { + var item = json.queryasyncjobresultresponse.jobresult.aclid; + return {data:item}; + } + } + } + + ) + }, + + error:function(json){ + + args.response.error(parseXMLHttpResponse(json)); + } + }); + }, + notification: { + poll: pollAsyncJobResult + }, + + messages: { + confirm: function(args) { + return 'Do you want to replace the ACL with a new one ?'; + }, + notification: function(args) { + return 'ACL replaced'; + } + } + } }, tabFilter: function(args) { @@ -1921,7 +2580,7 @@ } }); - var hiddenTabs = ['ipAddresses']; // Disable IP address tab; it is redundant with 'view all' button + var hiddenTabs = ['ipAddresses', 'acl']; // Disable IP address tab; it is redundant with 'view all' button if(networkOfferingHavingELB == false) hiddenTabs.push("addloadBalancer"); @@ -2057,6 +2716,8 @@ isEditable: true }, + aclid:{label:'ACL id'}, + domain: { label: 'label.domain' }, account: { label: 'label.account' } } @@ -2506,23 +3167,169 @@ async: true, success: function(json) { var networks = json.listnetworksresponse.network; - if(networks != null && networks.length > 0) { - for(var i = 0; i < networks.length; i++) { + var networkACLLists, publicIpAddresses, privateGateways, vpnGateways; + var error = false; + + // Get network ACL lists + $.ajax({ + url: createURL('listNetworkACLLists'), + data: { 'vpc_id': args.context.vpc[0].id }, + async: false, + success: function(json) { + networkACLLists = json.listnetworkacllistsresponse; + }, + error: function(json) { + error = true; + } + }); + + // Get public IPs + $.ajax({ + url: createURL('listPublicIpAddresses'), + async: false, + data: { 'vpcid': args.context.vpc[0].id }, + success: function(json) { + publicIpAddresses = json.listpublicipaddressesresponse; + }, + error: function(json) { + error = true; + } + }); + + // Get private gateways + $.ajax({ + url: createURL('listPrivateGateways'), + async: false, + data: { 'vpcid': args.context.vpc[0].id }, + success: function(json) { + privateGateways = json.listprivategatewaysresponse; + }, + error: function(json) { + error = true; + } + }); + + // Get VPN gateways + $.ajax({ + url: createURL('listVpnGateways'), + async: false, + data: { 'vpcid': args.context.vpc[0].id }, + success: function(json) { + vpnGateways = json.listvpngatewaysresponse; + }, + error: function(json) { + error = true; + } + }); + + args.response.success({ + routerDashboard: [ + { + id: 'privateGateways', + name: 'Private gateways', + total: privateGateways.count + }, + { + id: 'publicIPs', + name: 'Public IP addresses', + total: publicIpAddresses.count + }, + { + id: 'siteToSiteVPNs', + name: 'Site-to-site VPNs', + total: vpnGateways.count + }, + { + id: 'networkACLLists', + name: 'Network ACL lists', + total: networkACLLists.count + } + ], + tiers: $(networks).map(function(index, tier) { + var internalLoadBalancers, publicLbIps, virtualMachines, staticNatIps; + + // Get internal load balancers $.ajax({ - url: createURL("listVirtualMachines"), - dataType: "json", - data: { - networkid: networks[i].id, - listAll: true - }, + url: createURL('listLoadBalancers'), async: false, + data: { networkid: tier.id }, success: function(json) { - networks[i].virtualMachines = json.listvirtualmachinesresponse.virtualmachine; + internalLoadBalancers = json.listloadbalancerssresponse; + }, + error: function(json) { + error = true; } }); - } + + // Get Public LB IPs + $.ajax({ + url: createURL('listPublicIpAddresses'), + async: false, + data: { networkid: tier.id, forloadbalancing: true }, + success: function(json) { + publicLbIps = json.listpublicipaddressesresponse; + }, + error: function(json) { + error = true; + } + }); + + // Get static NAT IPs + $.ajax({ + url: createURL('listPublicIpAddresses'), + async: false, + data: { networkid: tier.id, isstaticnat: true }, + success: function(json) { + staticNatIps = json.listpublicipaddressesresponse; + }, + error: function(json) { + error = true; + } + }); + + // Get VMs + $.ajax({ + url: createURL('listVirtualMachines'), + async: false, + data: { networkid: tier.id }, + success: function(json) { + virtualMachines = json.listvirtualmachinesresponse; + }, + error: function(json) { + error = true; + } + }); + + return $.extend(tier, { + _dashboardItems: [ + { + id: 'internalLoadBalancers', + name: 'Internal LB', + total: internalLoadBalancers.count + }, + { + id: 'publicLbIps', + name: 'Public LB IP', + total: publicLbIps.count + }, + { + id: 'tierStaticNATs', + name: 'Static NATs', + total: staticNatIps.count + }, + { + id: 'tierVMs', + name: 'Virtual Machines', + total: virtualMachines.count + } + ] + }); + }) + }); + + if (error) { + cloudStack.dialog.notice({ message: 'Error loading dashboard data.' }); } - args.response.success({ tiers: networks }); } }); }