From 9237e91344c1b91ff9bd00d4204bf4a20a063595 Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Wed, 23 Nov 2011 14:02:40 -0800 Subject: [PATCH] -bug 12153: Properly refresh list view after performing actions in detail view -Fix clicking too fast to add panel breaking browser -Add install wizard copy --- ui/css/cloudstack3.css | 28 +++-- ui/images/bg-transparent-white.png | Bin 0 -> 2944 bytes ui/scripts-test/installWizard.js | 166 ++++++++++++++++++++++++-- ui/scripts-test/instances.js | 4 +- ui/scripts/ui-custom/installWizard.js | 37 ++++-- ui/scripts/ui/widgets/cloudBrowser.js | 5 +- ui/scripts/ui/widgets/dataTable.js | 15 ++- ui/scripts/ui/widgets/detailView.js | 50 +++++++- ui/scripts/ui/widgets/listView.js | 20 ++-- 9 files changed, 273 insertions(+), 52 deletions(-) create mode 100644 ui/images/bg-transparent-white.png diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 9549157e1e1..2d298109353 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -310,8 +310,6 @@ body.login { width: 1024px; height: 768px; margin: auto; - overflow: hidden; - border: 1px solid #E2E2E2; border-top: none; position: relative; } @@ -349,7 +347,7 @@ body.login { } .install-wizard .step .subtitle { - color: #808080; + color: #4395C6; font-weight: bold; } @@ -357,6 +355,14 @@ body.login { color: #4A4A4A; font-size: 15px; line-height: 23px; + background: url(../images/bg-transparent-white.png); +} + +.install-wizard .step ul li { + margin: 14px 0 0 18px; + width: 465px; + font-size: 13px; + list-style: disc; } .install-wizard .step .field { @@ -462,10 +468,10 @@ body.login { .install-wizard .diagram { width: 910px; height: 385px; - /*+placement:shift 65px 397px;*/ + /*+placement:shift 65px 496px;*/ position: relative; left: 65px; - top: 397px; + top: 496px; position: absolute; z-index: 10; } @@ -565,14 +571,19 @@ body.login { /*** Setup form*/ .install-wizard .step .setup-form { display: inline-block; - background: #F0F0F0; + background: url(../images/bg-transparent-white.png); width: 469px; - border: 1px solid #CFCFCF; + border: 1px solid #DFDFDF; /*+text-shadow:0px 1px 0px #FFFFFF;*/ -moz-text-shadow: 0px 1px 0px #FFFFFF; -webkit-text-shadow: 0px 1px 0px #FFFFFF; -o-text-shadow: 0px 1px 0px #FFFFFF; text-shadow: 0px 1px 0px #FFFFFF; + /*+box-shadow:inset 0px 1px 5px #FFFFFF;*/ + -moz-box-shadow: inset 0px 1px 5px #FFFFFF; + -webkit-box-shadow: inset 0px 1px 5px #FFFFFF; + -o-box-shadow: inset 0px 1px 5px #FFFFFF; + box-shadow: inset 0px 1px 5px #FFFFFF; } .install-wizard .step .setup-form .title { @@ -586,7 +597,6 @@ body.login { display: inline-block; margin: 6px 0 1px 31px; padding: 9px; - background: #E2E2E2; color: #57646D; } @@ -1345,7 +1355,7 @@ div.list-view div.toolbar div.section-switcher div.section-select label { height: 100%; left: 0px; top: 0px; - background: #E2E9F0 url(../images/ajax-loader.gif) no-repeat center; + background: #F2F2F2 url(../images/ajax-loader.gif) no-repeat center; z-index: 500; /*+opacity:70%;*/ filter: alpha(opacity=70); diff --git a/ui/images/bg-transparent-white.png b/ui/images/bg-transparent-white.png new file mode 100644 index 0000000000000000000000000000000000000000..2d37cbac7f6faa37980dbda64414247da8319fdb GIT binary patch literal 2944 zcmchZfYKo$i%TfoECP~C zNDBx&mk^ZLhkMTRH#{%C^O-qk&Y79-t4X|Psz*b~K?wk$G0@kxxTf2`A}76$eyf@A zYf$*<+dKh)it%3&fxI_t08pBF!C?39dHM$WKJoPR<1v82c>Drdbz81{nJQmPo6jQnogqMQeFoCydj@i8PmW~by=RjcsacU|mh1pz; zI*D?YY@N3_HTqpb($M)z*gNm(w$shgtEL(Ct?DCm;}}^7Idzu4thqdjx&q35>w9#6 zPv6SAgk~hAfFGbIZ*=2*a>-8w&LUJ*@9=eyeFj9qlT_rO!z8~$>UKPVZU>rcPlSpg z>Ih7~YebG>0-B+z&{CkOON2t_2w8$c65urK>aqszi32C$7n_p+g+9v-BLa4>d6|g{ z(*X~odxAFbQ~}jv_mgx0LJBZ>81*QDS#cm`VC|s~>Y70Zj-ILkkW&LG^MqJYKoSO= z2Kf1dK};@S(mk|R{pU_C{TAffsoVxttbmeEq#e1qKbf_)1h)vzfb|ZOg7XdMJWYwN zkX&||NIAOiMm$E^z0oOpiJhCy-LTU{1ACstpU2XznCiNeyw%N<;ae;EnWBLX{C zPXRb=_3iv7LQahGj9wWFA{=O3>JXBC0~P4$g3?1N-B7m!oJ6ampdxXjbzBf9$#0rmavkQLaT$KJ5?xlG zXx04a{y38tbc(b^Qjlm3kTW{-&LAatER5%vs>E zi;Q$ULSKx3njwu*l0GZ})}tuEkO-3z#=g8^Q2!o2!7!ma@z{cYTdX2qPM?E6V{qQh zyMavUwHVKxf!KBQdiT2QI_Em?p&cdKMbrQNndPd8#n3}T_6_i#P%V_xRiY|nY|w_r6|3dvz1Gi>uZI8U?9(# z(}yTC4Ksx@IU$npiptW;rAjKRBdcL}TEzp)@LF9fSs}R+Z>`yOx}m$O@J6 z_)28CFI?k&gw16lsZF z>0QEE)T$ly<#E}5PQm8~r~+f(vZavrtv|QJB`@KZK7Yzi^r#V}jno39cxoJ*YOlP% zuPT>hKmH$cm_xTFti(CfIeR509Xqc2Zc6uWUAh#nKKnI+HJ?78zIQ^TLcBsoCOank zc@`$vQqfWsV-@56(UH;O(dk_Id^PDAnXUY-{F(gaW+&TVTQN*I=99ZY!@6y$%|52> zajdP9O$oxv)}c|i23;dK-CEOHcB>3)nqgX1Fpcc4`v)_Ksc2@e+n6QA*J6Ay;m$}G zR%CN;ZTEggdq#Lm_}`amWHj+}v{hWTToF$`1~4h#S6<4)z1i0Y?>)}imP8`P=*MPd z*D#F3qCH+{%lt__Br6j8B>E^JIngL;SRz|pBA>HwaiD$OF!03RVI_3p1fAcQ-?dkc z|FEyH@3kMl@ODJ*t!ROb?31tE1F-Mr8{-;OZ!@L5m#ZVpU}llXrY(L+SIKninwh%h zlAy{{=L1&tU+MvG*VNZk6_;d|Y#c`vJDLZYht0Zu3mjvYVK4XzZiG<6I4DC25+R8b zBef#!Zk(TmKIi?Cs-cW`|J5+Jris*=&a%O9QgUNhWB(U2bYfXqK&;5 z@niZcne(P|a(4u^aBO_6b(}`3Ujl+-Tsm7mXnX4UhTJVd=x z;-TuiVvAghMxTU-&Bzo2jWBp*bX!RRQmgRz(dXrhy`%V5^eWX_7;BE?fP-I15w7bv zwv44hfP^zQ5bXwgH;SyBWK8G{5`?b6Y&(qH;RD|t;aFvuO`rpL44 zYWLWNtKP@S&63=iWP{#~FRO3bh*M&qr9a@dA~O)O2Bf>S$CuH>A*HOUf;*#oBaK7& zmx^x=-V*j0x)@eoVM^kRti`zyo9)#Pl^Rt2L-3Sq%s21E-6yS9{gi}(_)M8q?OA8n zMzjlsns`S(=p2gbY$EdBrLv;jhz;LenAvkMLrB^zH#z@_LvyM#CnnW0;aRhpU(AXu z1aEN0`wo4{U3mSmqN)e$Hs&$mvAww@USLO?Rhhk+P5YQ;ZvG?9^8Jr{C%<&?lG&po z_||9)4Eyej>ZdmMvO(OaiU#~W{22ZRuCPC`*yKEO_IP}sGpy~G+|MbzUEArU`rv20 z6_J(6l*g3&xxsnksYY#L<@Q;&$pUS&+0^|!uh&8x-Q zdt>)Q+nd5XPPaJ=6mg5k?IHiL5T>UJl++g6k3xMwlBS zRo}t{fKWjIP|*PVCR}qHfTvOb>^K3SoC5%>?<>b%-Rs^e18psM@chqlA{rn?PEs#T zDE9j`%ky4%#n#}Dm&x)Co*Or`HBsc`^h88_|JPiT0 O2@G^hwQHeHQU3+p
Extending beyond individual virtual machine images running on commodity hardware, CloudStack™ provides a turnkey cloud infrastructure software stack for delivering virtual datacenters as a service - delivering all of the essential components to build, deploy, and manage multi-tier and multi-tenant cloud applications. Both open-source and Premium versions are available, with the open-source version offering nearly identical features. ' }); }, whatIsAZone: function(args) { args.response.success({ - text: 'A zone is integral to the CloudStack platform -- your entire network is represented via a zone. More text goes here...' + text: 'A zone is the largest organizational unit within a CloudStack™ deployment. A zone typically corresponds to a single datacenter, although it is permissible to have multiple zones in a datacenter. The benefit of organizing infrastructure into zones is to provide physical isolation and redundancy. For example, each zone can have its own power supply and network uplink, and the zones can be widely separated geographically (though this is not required).' }); }, whatIsAPod: function(args) { args.response.success({ - text: 'A pod is a part of a zone. More text goes here...' + text: 'A pod often represents a single rack. Hosts in the same pod are in the same subnet.

A pod is the second-largest organizational unit within a CloudStack™ deployment. Pods are contained within zones. Each zone can contain one or more pods; in the Basic Installation, you will have just one pod in your zone' }); }, whatIsACluster: function(args) { args.response.success({ - text: 'A cluster is a part of a zone. More text goes here...' + text: 'A cluster provides a way to group hosts. The hosts in a cluster all have identical hardware, run the same hypervisor, are on the same subnet, and access the same shared storage. Virtual machine instances (VMs) can be live-migrated from one host to another within the same cluster, without interrupting service to the user. A cluster is the third-largest organizational unit within a CloudStack™ deployment. Clusters are contained within pods, and pods are contained within zones.

CloudStack™ allows multiple clusters in a cloud deployment, but for a Basic Installation, we only need one cluster. ' }); }, whatIsAHost: function(args) { args.response.success({ - text: 'A host is a part of a zone. More text goes here...' + text: 'A host is a single computer. Hosts provide the computing resources that run the guest virtual machines. Each host has hypervisor software installed on it to manage the guest VMs (except for bare metal hosts, which are a special case discussed in the Advanced Installation Guide). For example, a Linux KVM-enabled server, a Citrix XenServer server, and an ESXi server are hosts. In a Basic Installation, we use a single host running XenServer.

The host is the smallest organizational unit within a CloudStack™ deployment. Hosts are contained within clusters, clusters are contained within pods, and pods are contained within zones. ' }); }, whatIsPrimaryStorage: function(args) { args.response.success({ - text: 'Primary storage is a part of a zone. More text goes here...' + text: 'A CloudStack™ cloud infrastructure makes use of two types of storage: primary storage and secondary storage. Both of these can be iSCSI or NFS servers, or localdisk.

Primary storage is associated with a cluster, and it stores the disk volumes of each guest VM for all the VMs running on hosts in that cluster. The primary storage server is typically located close to the hosts. ' }); }, whatIsSecondaryStorage: function(args) { args.response.success({ - text: 'Secondary storage is a part of a zone. More text goes here...' + text: 'Secondary storage is associated with a zone, and it stores the following:
  • Templates - OS images that can be used to boot VMs and can include additional configuration information, such as installed applications
  • ISO images - OS images that can be bootable or non-bootable
  • Disk volume snapshots - saved copies of VM data which can be used for data recovery or to create new templates
' }); } }, diff --git a/ui/scripts-test/instances.js b/ui/scripts-test/instances.js index 3025618a9ff..243f12dcc87 100644 --- a/ui/scripts-test/instances.js +++ b/ui/scripts-test/instances.js @@ -350,7 +350,9 @@ } }, notification: { - poll: testData.notifications.testPoll + poll: testData.notifications.customPoll({ + state: 'Destroyed' + }) }, action: function(args) { setTimeout(function() { diff --git a/ui/scripts/ui-custom/installWizard.js b/ui/scripts/ui-custom/installWizard.js index 5c32a06157c..d34682b0688 100644 --- a/ui/scripts/ui-custom/installWizard.js +++ b/ui/scripts/ui-custom/installWizard.js @@ -104,18 +104,31 @@ /** * Show tooltip for focused form elements */ - var showTooltip = function($formContainer) { - var $tooltip = elems.tooltip('Hints', 'Help content goes here.'); + var showTooltip = function($formContainer, sectionID) { + var $tooltip = elems.tooltip('Hints', ''); + $formContainer.find('input').focus(function() { + var $input = $(this); + + $tooltip.find('p').html(''); $tooltip.appendTo($formContainer); $tooltip.css({ top: $(this).position().top - 20 }); + + var content = getCopy( + 'tooltip.' + sectionID + '.' + $input.attr('name'), + $tooltip.find('p') + ); }); $formContainer.find('input').blur(function() { $tooltip.remove(); }); + + setTimeout(function() { + $formContainer.find('input:first').focus(); + }, 600); }; /** @@ -167,9 +180,9 @@ intro: function(args) { var $intro = $('
').addClass('intro'); var $title = $('
').addClass('title') - .html('What is CloudStack?'); + .html('What is CloudStack™?'); var $subtitle = $('
').addClass('subtitle') - .html('Subtitle text goes here'); + .html('Introduction to CloudStack™'); var $copy = getCopy('whatIsCloudStack', $('

')); var $continue = elems.nextButton('Continue with basic installation'); var $advanced = elems.nextButton('Setup advanced installation').addClass('advanced-installation'); @@ -249,7 +262,7 @@ $addZoneForm.find('.field:last').remove(); showDiagram('.part.zone'); - showTooltip($addZoneForm); + showTooltip($addZoneForm, 'addZone'); return $addZone.append( $addZoneForm @@ -281,7 +294,7 @@ }); showDiagram('.part.zone'); - showTooltip($addIPRangeForm); + showTooltip($addIPRangeForm, 'addIPRange'); // Remove unneeded fields $addIPRangeForm.find('.main-desc, .conditional').remove(); @@ -347,7 +360,7 @@ $addPodForm.find('.main-desc, .conditional').remove(); showDiagram('.part.zone, .part.pod'); - showTooltip($addPodForm); + showTooltip($addPodForm, 'addPod'); return $addPod.append( $addPodForm @@ -416,7 +429,7 @@ }); showDiagram('.part.zone, .part.cluster'); - showTooltip($addClusterForm); + showTooltip($addClusterForm, 'addCluster'); // Cleanup $addClusterForm.find('.message').remove(); @@ -510,7 +523,7 @@ }); showDiagram('.part.zone, .part.host'); - showTooltip($addHostForm); + showTooltip($addHostForm, 'addHost'); // Cleanup $addHostForm.find('.message').remove(); @@ -601,7 +614,7 @@ }); showDiagram('.part.zone, .part.primaryStorage'); - showTooltip($addPrimaryStorageForm); + showTooltip($addPrimaryStorageForm, 'addPrimaryStorage'); // Cleanup $addPrimaryStorageForm.find('.message').remove(); @@ -681,7 +694,7 @@ }); showDiagram('.part.zone, .part.secondaryStorage'); - showTooltip($addSecondaryStorageForm); + showTooltip($addSecondaryStorageForm, 'addSecondaryStorage'); // Cleanup $addSecondaryStorageForm.find('.message').remove(); @@ -702,7 +715,6 @@ .html('Congratulations!.'); var $subtitle = $('
').addClass('subtitle') .html('Click the launch button.'); - var $copy = getCopy('whatIsACluster', $('

')); var $continue = elems.nextButton('Launch'); $continue.click(function() { @@ -715,7 +727,6 @@ return $intro.append( $title, $subtitle, - $copy, $continue ); }, diff --git a/ui/scripts/ui/widgets/cloudBrowser.js b/ui/scripts/ui/widgets/cloudBrowser.js index 807bde56ac0..bb2a424afb5 100644 --- a/ui/scripts/ui/widgets/cloudBrowser.js +++ b/ui/scripts/ui/widgets/cloudBrowser.js @@ -245,6 +245,9 @@ // Remove existing panels from parent if ($parent) { + // Cleanup transitioning panels -- prevent old complete actions from running + $parent.siblings().stop(); + _breadcrumb.filter( $('div.panel.maximized') .removeClass('maximized') @@ -295,7 +298,7 @@ return $(this).width() == $panel.width(); }); - if (args.complete) args.complete($panel); + if ($panel.is(':visible') && args.complete) args.complete($panel); } }); }; diff --git a/ui/scripts/ui/widgets/dataTable.js b/ui/scripts/ui/widgets/dataTable.js index 0b2d4e6d554..96316a7fba0 100644 --- a/ui/scripts/ui/widgets/dataTable.js +++ b/ui/scripts/ui/widgets/dataTable.js @@ -103,11 +103,8 @@ var $rows = $table.find('tbody tr'); var $row = $($rows[rowIndex]); - $rows.filter( - function() { - return this != $row[0]; - }).removeClass('selected'); - return $row.toggleClass('selected'); + $row.siblings().removeClass('selected'); + return $row.addClass('selected'); }; var computeEvenOddRows = function() { @@ -235,9 +232,11 @@ return false; }); - $table.find('tbody tr').bind('click', function(event) { - if (noSelect == true) return true; - var rowIndex = $(this).index(); + $table.bind('click', function(event) { + var $tr = $(event.target).closest('tr'); + + if (!$tr.size() || noSelect) return true; + var rowIndex = $tr.index(); toggleSelectRow(rowIndex); diff --git a/ui/scripts/ui/widgets/detailView.js b/ui/scripts/ui/widgets/detailView.js index 5a5165b8451..630b2e194ea 100644 --- a/ui/scripts/ui/widgets/detailView.js +++ b/ui/scripts/ui/widgets/detailView.js @@ -22,7 +22,7 @@ $notifications.notifications('add', { section: notification.section, desc: notification.desc, - interval: 5000, + interval: 2000, _custom: notification._custom, poll: function(args) { var complete = args.complete; @@ -51,6 +51,35 @@ return true; }; + var replaceListViewItem = function($detailView, newData) { + var $row = $detailView.data('list-view-row'); + + if (!$row) return; + + var $listView = $row.closest('.list-view'); + var $newRow; + var jsonObj = $row.data('json-obj'); + + $listView.listView('replaceItem', { + $row: $row, + data: $.extend(jsonObj, newData), + after: function($newRow) { + $detailView.data('list-view-row', $newRow); + + setTimeout(function() { + $('.data-table').dataTable('selectRow', $newRow.index()); + }, 100); + } + }); + + // Refresh detail view context + $.extend( + $detailView.data('view-args').context[ + $detailView.data('view-args').section + ][0], newData + ); + }; + /** * Available UI actions to perform for buttons */ @@ -115,6 +144,7 @@ function(args) { $loading.remove(); updateTabContent(args.data); + replaceListViewItem($detailView, args.data); }, {}, @@ -124,7 +154,7 @@ ); } }); - } else { + } else { // Set loading appearance var $loading = $('
').addClass('loading-overlay'); $detailView.prepend($loading); @@ -159,6 +189,8 @@ if (additional && additional.complete) additional.complete($.extend(true, args, { $detailView: $detailView })); + + replaceListViewItem($detailView, args.data); }, {}, @@ -305,8 +337,7 @@ _custom: $detailView.data('_custom'), context: $detailView.data('view-args').context, response: { - data: data, - success: function(data) { + success: function() { var notificationArgs = { section: id, desc: 'Changed item properties' @@ -315,13 +346,16 @@ if (!action.notification) { convertInputs($inputs); addNotification( - notificationArgs, function(data) {}, [] + notificationArgs, function() {}, [] ); + replaceListViewItem($detailView, data); } else { $loading.appendTo($detailView); addNotification( $.extend(true, {}, action.notification, notificationArgs), function(args) { + replaceListViewItem($detailView, data); + convertInputs($inputs); $loading.remove(); }, [] @@ -534,7 +568,7 @@ var detailViewArgs = $detailView.data('view-args'); var fields = tabData.fields; var hiddenFields; - var context = detailViewArgs.context; + var context = detailViewArgs ? detailViewArgs.context : cloudStack.context; var isMultiple = tabData.multiple || tabData.isMultiple; if (isMultiple) { @@ -834,6 +868,10 @@ $detailView.addClass('detail-view'); $detailView.data('view-args', args); + if (args.$listViewRow) { + $detailView.data('list-view-row', args.$listViewRow); + } + // Create toolbar var $toolbar = makeToolbar().appendTo($detailView); diff --git a/ui/scripts/ui/widgets/listView.js b/ui/scripts/ui/widgets/listView.js index 6206ca06d63..f7627bee722 100644 --- a/ui/scripts/ui/widgets/listView.js +++ b/ui/scripts/ui/widgets/listView.js @@ -623,7 +623,7 @@ /** * Initialize detail view for specific ID from list view */ - var createDetailView = function(args, complete) { + var createDetailView = function(args, complete, $row) { var $panel = args.$panel; var title = args.title; var id = args.id; @@ -632,7 +632,8 @@ id: id, jsonObj: args.jsonObj, section: args.section, - context: args.context + context: args.context, + $listViewRow: $row }); var $detailView, $detailsPanel; @@ -812,7 +813,7 @@ var isUICustom = $listView.data('view-args') ? $tr.closest('.list-view').data('view-args').uiCustom : false; - if (options.actionFilter && !isUICustom) { + if ($.isFunction(options.actionFilter) && !isUICustom) { allowedActions = options.actionFilter({ context: $.extend(true, {}, options.context, { actions: allowedActions, @@ -1206,7 +1207,7 @@ createDetailView(detailViewArgs, function($detailView) { $detailView.data('list-view', $listView); - }); + }, $target.closest('tr')); return false; } @@ -1297,7 +1298,7 @@ return $tr; }; - var replaceItem = function($row, data, actionFilter) { + var replaceItem = function($row, data, actionFilter, after) { var $newRow; var $listView = $row.closest('.list-view'); var viewArgs = $listView.data('view-args'); @@ -1307,6 +1308,7 @@ ].listView : listViewArgs; var reorder = targetArgs.reorder; var $table = $row.closest('table'); + var defaultActionFilter = $row.data('list-view-action-filter'); $newRow = addTableRows( targetArgs.fields, @@ -1314,14 +1316,18 @@ $listView.find('table tbody'), targetArgs.actions, { - actionFilter: actionFilter, + actionFilter: actionFilter ? actionFilter : defaultActionFilter, reorder: reorder } )[0]; + $newRow.data('json-obj', data); + $row.replaceWith($newRow); $table.dataTable('refresh'); + if (after) after($newRow); + return $newRow; }; @@ -1330,7 +1336,7 @@ if (args == 'prependItem') { return prependItem(this, options.data, options.actionFilter); } else if (args =='replaceItem') { - replaceItem(this, options.data, options.actionFilter); + replaceItem(options.$row, options.data, options.actionFilter, options.after); } else if (args.sections) { var targetSection; $.each(args.sections, function(key) {