You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2021/10/04 11:23:33 UTC

[brooklyn-ui] branch master updated: use new deleteable flag on bundles to give better info in the delete popup

This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-ui.git


The following commit(s) were added to refs/heads/master by this push:
     new 4d1b1d7  use new deleteable flag on bundles to give better info in the delete popup
     new 8e0b2c3  This closes #293
4d1b1d7 is described below

commit 4d1b1d7634d71d318f570f1d83dbe7bbcd2ee071
Author: Alex Heneveld <al...@cloudsoftcorp.com>
AuthorDate: Fri Oct 1 18:56:12 2021 +0100

    use new deleteable flag on bundles to give better info in the delete popup
    
    and add this to the LocationManager delete button
---
 ui-modules/catalog/app/index.js                    |   3 +-
 ui-modules/catalog/app/index.less                  |   1 +
 .../catalog/app/views/bundle/bundle.state.js       |  12 +-
 .../catalog/app/views/bundle/bundle.template.html  |  10 +-
 ui-modules/location-manager/app/index.js           |   3 +-
 ui-modules/location-manager/app/index.less         |   1 +
 .../app/views/detail/detail.controller.js          |  14 +-
 .../app/views/detail/detail.template.html          |  11 +-
 .../utils/catalog-deleter/catalog-deleter.html     | 157 +++++++++++++++++++++
 .../utils/catalog-deleter/catalog-deleter.js       | 145 +++++++++++++++++++
 .../catalog-deleter/catalog-deleter.less}          |  17 +--
 11 files changed, 335 insertions(+), 39 deletions(-)

diff --git a/ui-modules/catalog/app/index.js b/ui-modules/catalog/app/index.js
index 730af1c..fbf037e 100644
--- a/ui-modules/catalog/app/index.js
+++ b/ui-modules/catalog/app/index.js
@@ -28,6 +28,7 @@ import brInterstitialSpinner from 'brooklyn-ui-utils/interstitial-spinner/inters
 import brooklynModuleLinks from 'brooklyn-ui-utils/module-links/module-links';
 import brooklynUserManagement from "brooklyn-ui-utils/user-management/user-management";
 import brooklynCatalogUpdater from 'brooklyn-ui-utils/catalog-uploader/catalog-uploader';
+import brooklynCatalogDeleter from 'brooklyn-ui-utils/catalog-deleter/catalog-deleter';
 import mdHelper from 'brooklyn-ui-utils/md-helper';
 
 import uiRouter from 'angular-ui-router';
@@ -43,7 +44,7 @@ import brandAngularJs from 'brand-angular-js';
 const IS_PRODUCTION = process.env.NODE_ENV === 'production' || false;
 
 angular.module('brooklynCatalog', [ngAnimate, ngCookies, ngResource, brCore, brServerStatus, brInterstitialSpinner,
-    brooklynModuleLinks, brooklynUserManagement, brooklynCatalogUpdater, uiRouter, catalogState, catalogBundleState,
+    brooklynModuleLinks, brooklynUserManagement, brooklynCatalogUpdater, brooklynCatalogDeleter, uiRouter, catalogState, catalogBundleState,
     catalogBundleTypeState, quickLaunchOverrides, brandAngularJs, mdHelper])
     .config(['$logProvider', '$compileProvider', applicationConfig])
     .config(['$urlRouterProvider', routerConfig])
diff --git a/ui-modules/catalog/app/index.less b/ui-modules/catalog/app/index.less
index 18760e1..ba3d5fb 100644
--- a/ui-modules/catalog/app/index.less
+++ b/ui-modules/catalog/app/index.less
@@ -21,6 +21,7 @@
 @import '~brooklyn-ui-utils/quick-launch/quick-launch.less';
 @import '~brooklyn-ui-utils/yaml-editor/yaml-editor.less';
 @import '~brooklyn-ui-utils/catalog-uploader/catalog-uploader.less';
+@import '~brooklyn-ui-utils/catalog-deleter/catalog-deleter.less';
 
 // Add project less files here
 @import "components/type-item/index";
diff --git a/ui-modules/catalog/app/views/bundle/bundle.state.js b/ui-modules/catalog/app/views/bundle/bundle.state.js
index fb7832e..74d3fcb 100644
--- a/ui-modules/catalog/app/views/bundle/bundle.state.js
+++ b/ui-modules/catalog/app/views/bundle/bundle.state.js
@@ -76,17 +76,7 @@ export function bundleController($scope, $state, $stateParams, brSnackbar, brUti
         $scope.state.orderBy = orderBys[0];
     };
 
-    $scope.deleteBundle = () => {
-        $scope.state.deleting = true;
-        catalogApi.deleteBundle($scope.bundle.symbolicName, $scope.bundle.version).then(data => {
-            $state.go(catalogState);
-        }).catch(error => {
-            let errorMessage= ('undefined' === typeof error.message)? error.error.message: error.message;
-            brSnackbar.create('Could not delete this bundle: ' + errorMessage);
-        }).finally(() => {
-            $scope.state.deleting = false;
-        });
-    };
+    $scope.onDeleted = () => { $scope.state.deleting = false; $state.go(catalogState); }
 
     $scope.downloadBundleUrl = () => {
         return !$scope.bundle ? /* loading */ "" :
diff --git a/ui-modules/catalog/app/views/bundle/bundle.template.html b/ui-modules/catalog/app/views/bundle/bundle.template.html
index 53ce2b9..19ae694 100644
--- a/ui-modules/catalog/app/views/bundle/bundle.template.html
+++ b/ui-modules/catalog/app/views/bundle/bundle.template.html
@@ -54,8 +54,8 @@
             <div class="col-sm-9 col-xs-6">
                 <input ng-model="state.search.$" type="text" placeholder="Search for types" class="form-control" />
             </div>
-            <div class="col-sm-3 col-xs-6 text-right" ng-init="isDeleteOpened = false">
-                <button class="btn btn-danger" ng-disabled="state.deleting" uib-popover-template="'deletePopoverTemplate.html'" popover-placement="bottom-right" popover-trigger="'outsideClick'" popover-append-to-body="true">
+            <div class="col-sm-3 col-xs-6 text-right">
+                <button class="btn btn-danger" ng-disabled="state.deleting" uib-popover-template="'deletePopoverTemplate.html'" popover-class="catalog-delete-popover" popover-placement="bottom-right" popover-trigger="'outsideClick'" popover-append-to-body="true">
                     <i class="fa fa-fw fa-trash"></i> Delete bundle
                 </button>
             </div>
@@ -136,8 +136,8 @@
     </section>
 
     <script type="text/ng-template" id="deletePopoverTemplate.html">
-        <h4>Are you sure you wish to delete this bundle?</h4>
-        <p><small>All types contained within it <strong>will be deleted</strong></small></p>
-        <button class="btn btn-danger text-right" ng-click="deleteBundle()" ng-disabled="state.deleting">{{state.deleting ? 'Deleting...' : "Yes, I'm sure"}}</button>
+        <brooklyn-catalog-deleter symbolic-name="bundle.symbolicName" version="bundle.version" mode="bundle"
+                                  on-deleting="state.deleting = true" on-deleting-finished="state.deleting = false" on-deleted="onDeleted()" on-failed=""/>
     </script>
+
 </ui-view>
diff --git a/ui-modules/location-manager/app/index.js b/ui-modules/location-manager/app/index.js
index 9749fba..17d7374 100644
--- a/ui-modules/location-manager/app/index.js
+++ b/ui-modules/location-manager/app/index.js
@@ -30,6 +30,7 @@ import brooklynModuleLinks from 'brooklyn-ui-utils/module-links/module-links';
 import brSensitiveField from 'brooklyn-ui-utils/sensitive-field/sensitive-field';
 import brooklynUserManagement from 'brooklyn-ui-utils/user-management/user-management';
 import brooklynApi from 'brooklyn-ui-utils/brooklyn.api/brooklyn.api';
+import brooklynCatalogDeleter from 'brooklyn-ui-utils/catalog-deleter/catalog-deleter';
 
 import locationsState from 'views/locations/locations.controller';
 import detailState from 'views/detail/detail.controller';
@@ -42,7 +43,7 @@ import brandAngularJs from 'brand-angular-js';
 
 const IS_PRODUCTION = process.env.NODE_ENV === 'production' || false;
 
-angular.module('brooklynLocationManager', [ngAnimate, ngCookies, uiRouter, brCore, brServerStatus, brAutoFocus, brInterstitialSpinner, brooklynModuleLinks, brSensitiveField, brooklynUserManagement, brooklynApi, locationsState, detailState, wizardState, wizardAdvancedState, wizardByonState, wizardCloudState, brandAngularJs])
+angular.module('brooklynLocationManager', [ngAnimate, ngCookies, uiRouter, brCore, brServerStatus, brAutoFocus, brInterstitialSpinner, brooklynModuleLinks, brSensitiveField, brooklynUserManagement, brooklynApi, brooklynCatalogDeleter, locationsState, detailState, wizardState, wizardAdvancedState, wizardByonState, wizardCloudState, brandAngularJs])
     .config(['$urlRouterProvider', '$logProvider', '$compileProvider', applicationConfig])
     .run(['$rootScope', '$state', 'brSnackbar', errorHandler])
     .run(['$http', httpConfig]);
diff --git a/ui-modules/location-manager/app/index.less b/ui-modules/location-manager/app/index.less
index 75c7f0b..b44b854 100644
--- a/ui-modules/location-manager/app/index.less
+++ b/ui-modules/location-manager/app/index.less
@@ -19,6 +19,7 @@
 @import '~brooklyn-shared/style/first.less';
 
 @import '~brooklyn-ui-utils/sensitive-field/sensitive-field.less';
+@import '~brooklyn-ui-utils/catalog-deleter/catalog-deleter.less';
 
 // Add project less files here
 @import 'views/locations/locations.less';
diff --git a/ui-modules/location-manager/app/views/detail/detail.controller.js b/ui-modules/location-manager/app/views/detail/detail.controller.js
index 01c0818..fdd75ac 100644
--- a/ui-modules/location-manager/app/views/detail/detail.controller.js
+++ b/ui-modules/location-manager/app/views/detail/detail.controller.js
@@ -79,16 +79,14 @@ export function detailStateConfig($stateProvider) {
 export function detailController($scope, $filter, $state, $stateParams, brSnackbar, catalogApi, location) {
     $scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
 
+    $scope.state = {};
+    $scope.onDeleted = () => {
+        $state.go('locations');
+        brSnackbar.create('Location "' + $filter('locationName')(vm.location) + '" deleted successfully');
+    };
+
     let vm = this;
     vm.location = angular.copy(location);
-    vm.deleteLocation = function () {
-        catalogApi.deleteLocation(vm.location.symbolicName, vm.location.version).then(data => {
-            $state.go('locations');
-            brSnackbar.create('Location "' + $filter('locationName')(vm.location) + '" deleted successfully');
-        }).catch(error => {
-            brSnackbar.create('Could not delete this location: ' + error.error.message);
-        });
-    };
     vm.editLocation = function () {
         if (vm.location['spec'].indexOf('byon') >= 0) {
             $state.go('wizard.byon', {symbolicName: $stateParams.symbolicName, version: $stateParams.version});
diff --git a/ui-modules/location-manager/app/views/detail/detail.template.html b/ui-modules/location-manager/app/views/detail/detail.template.html
index 600f02b..7fe1dfd 100644
--- a/ui-modules/location-manager/app/views/detail/detail.template.html
+++ b/ui-modules/location-manager/app/views/detail/detail.template.html
@@ -71,7 +71,10 @@
                     </div>
                 </br-card-content>
                 <br-card-actions>
-                    <br-button type="btn-danger" on-click="vm.deleteLocation()" ng-if="!vm.location.readOnly">Delete</br-button>
+                    <button class="btn btn-danger" ng-disabled="state.deleting" uib-popover-template="'deleteLocationPopoverTemplate.html'" popover-class="catalog-delete-popover" popover-placement="bottom-right" popover-trigger="'outsideClick'" popover-append-to-body="true">
+                        <i class="fa fa-fw fa-trash"></i> Delete
+                    </button>
+
                     <div class="message-read-only" ng-if="vm.location.readOnly">This location can not be edited or deleted.</div>
                 </br-card-actions>
             </br-card>
@@ -80,4 +83,10 @@
             </br-card>
         </div>
     </div>
+
+    <script type="text/ng-template" id="deleteLocationPopoverTemplate.html">
+        <brooklyn-catalog-deleter symbolic-name="vm.location.symbolicName" version="vm.location.version" mode="location"
+                                  on-deleting="state.deleting = true" on-deleting-finished="state.deleting = false" on-deleted="onDeleted()" on-failed=""/>
+    </script>
+
 </div>
diff --git a/ui-modules/utils/catalog-deleter/catalog-deleter.html b/ui-modules/utils/catalog-deleter/catalog-deleter.html
new file mode 100644
index 0000000..daaa5b8
--- /dev/null
+++ b/ui-modules/utils/catalog-deleter/catalog-deleter.html
@@ -0,0 +1,157 @@
+<!--
+  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.
+-->
+<div class="brooklyn-catalog-deleter" xmlns="http://www.w3.org/1999/html">
+    <div ng-switch on="mode">
+
+        <div ng-switch-when="bundle">
+            <h4>Are you sure you wish to delete this bundle?</h4>
+
+            <div ng-switch on="!bundle && bundleLoading ? 'loading' : 'loaded-or-error'">
+                <div ng-switch-when="loading">
+                    <i class="fa fa-spin fa-spinner"></i>
+                    Loading context information for <code>{{id}}</code>. Please wait...
+                </div>
+
+                <div ng-switch-default>
+
+                    <div ng-switch on="bundle && bundle.deleteable">
+                        <p ng-switch-when="true">
+                            Bundle <code>{{id}}</code> will be permanently removed from the system.
+                        </p>
+                        <p ng-switch-default>
+                            Bundle <code>{{id}}</code>
+                            <span ng-switch on="deleteable===false">
+                                <span ng-switch-when="true">
+                                    is a system bundle so will
+                                </span>
+                                <span ng-switch-default>
+                                   appears to be a system bundle so may
+                                </span>
+                            </span>
+                            be automatically re-installed in the system after a restart or failover.
+                        </p>
+                    </div>
+
+                </div>
+            </div>
+
+            <p>All types contained within this bundle <strong>will be deleted</strong>.</p>
+            <p>Any currently active deployments that rely on this bundle or types it contain may become unstable.
+                If any such deployments are present, manual attention may be required to permit failover, hot-standby, and restart.</p>
+        </div>
+
+        <div ng-switch-when="type">
+            <!-- NOT USED AT PRESENT -->
+            <h4>Are you sure you wish to delete this type?</h4>
+
+            <p ng-switch on="bundle ? 'loaded' : bundleLoading ? 'loading' : bundleError ? 'error' : 'other'">
+                <span ng-switch-when="loaded">
+                    Type <code>{{id}}</code> is contained in bundle <code>{{bundle.symbolicName}}:{{bundle.version}}</code>.
+                    <span ng-switch on="vm.checkSingleBomBundle(bundle)">
+                        <span ng-switch-when="single-bom-match">
+                            This bundle is only for this type and so will also be deleted.
+
+                            <span ng-if="!bundle.deleteable">
+                                This bundle may be a system bundle and so may
+                                be automatically re-installed in the system after a restart or failover,
+                                reinstating this type.
+                            </span>
+                        </span>
+
+                        <span ng-switch-default>
+                            This bundle may contain other resources so will not be deleted,
+                            which means this type will be re-installed in the system after a restart or failover.
+                            You may want to <a href="/brooklyn-ui-catalog/#!/bundles/{{bundle.symbolicName}}/{{bundle.version}}">review the bundle</a> and delete it instead.
+                        </span>
+                    </span>
+                </span>
+
+                <span ng-switch-when="loading">
+                    <i class="fa fa-spin fa-spinner"></i>
+                    Loading context information for <code>{{id}}</code>. Please wait...
+                </span>
+                <span ng-switch-when="error">
+                    Context information for <code>{{id}}</code> is unavailable.
+                    This may have already been deleted or there may be access issues.
+                    Consult the log for more information.
+                </span>
+                <span ng-switch-default>
+                    Type <code>{{id}}</code> appears to have been installed programmatically or by the system, not using a bundle.
+                    Depending how the type was installed, it might be re-installed automatically by a deployment or on restart or failover.
+                </span>
+            </p>
+
+            <p>Any currently active deployments that rely on this type may become unstable.
+                If any such deployments are present, manual attention may be required to permit failover, hot-standby, and restart.</p>
+        </div>
+
+        <div ng-switch-when="location">
+            <h4>Are you sure you wish to delete this location?</h4>
+
+            <p ng-switch on="bundle ? 'loaded' : bundleLoading ? 'loading' : bundleError ? 'error' : 'other'">
+                <span ng-switch-when="loaded">
+                    Location <code>{{id}}</code> is contained in bundle <code>{{bundle.symbolicName}}:{{bundle.version}}</code>.
+                    <span ng-switch on="vm.checkSingleBomBundle(bundle)">
+                        <span ng-switch-when="single-bom-match">
+                            This bundle is only for this location and so will also be deleted.
+
+                            <span ng-if="!bundle.deleteable">
+                                This bundle may be a system bundle and so may
+                                be automatically re-installed in the system after a restart or failover,
+                                reinstating this location.
+                            </span>
+                        </span>
+
+                        <span ng-switch-default>
+                            This bundle may contain other resources so will not be deleted,
+                            which means this type will be re-installed in the system after a restart or failover.
+                            You may want to <a href="/brooklyn-ui-catalog/#!/bundles/{{bundle.symbolicName}}/{{bundle.version}}">review the bundle</a> and delete it instead.
+                        </span>
+                    </span>
+                </span>
+
+                <span ng-switch-when="loading">
+                    <i class="fa fa-spin fa-spinner"></i>
+                    Loading context information for <code>{{id}}</code>. Please wait...
+                </span>
+                <span ng-switch-when="error">
+                    Context information for <code>{{id}}</code> is unavailable.
+                    This may have already been deleted or there may be access issues.
+                    Consult the log for more information.
+                </span>
+                <span ng-switch-default>
+                    Location <code>{{id}}</code> appears to have been installed programmatically or by the system, not using a bundle.
+                    Depending how the location was installed, it might be re-installed automatically by a deployment or on restart or failover.
+                </span>
+            </p>
+
+            <p>Any currently active deployments that rely on this type may become unstable.
+                If any such deployments are present, manual attention may be required to permit failover, hot-standby, and restart.</p>
+        </div>
+
+        <div ng-switch-default>
+            <!-- shouldn't happen -->
+            <h4>Are you sure you wish to delete <code>{{id}}</code>?</h4>
+            <p>The category of this item is unknown, so be sure you understand the impact before proceeding.</p>
+        </div>
+
+    </div>
+
+    <button class="btn btn-danger text-right" ng-click="vm.delete()" ng-disabled="state.deleting">{{state.deleting ? 'Deleting...' : "Yes, I'm sure"}}</button>
+</div>
\ No newline at end of file
diff --git a/ui-modules/utils/catalog-deleter/catalog-deleter.js b/ui-modules/utils/catalog-deleter/catalog-deleter.js
new file mode 100644
index 0000000..05c523d
--- /dev/null
+++ b/ui-modules/utils/catalog-deleter/catalog-deleter.js
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+import angular from 'angular';
+import template from './catalog-deleter.html';
+import catalogApi from '../providers/catalog-api.provider';
+
+const MODULE_NAME = 'brooklyn.components.catalog-deleter';
+
+/**
+ * @ngdoc module
+ * @name brooklyn.components.catalog-deleter
+ * @requires catalogApi
+ *
+ * @description
+ * Adds an overlay on top of the current DOM element to upload files to the catalog. Files can either by added via
+ * classic file selection or drag & drop. This support multiple files to be uploaded at once.
+ */
+angular.module(MODULE_NAME, [catalogApi])
+    .directive('brooklynCatalogDeleter', ['$compile', 'catalogApi', 'brSnackbar', catalogDeleterDirective]);
+
+export default MODULE_NAME;
+
+function catalogDeleterDirective($compile, catalogApi, brSnackbar) {
+    return {
+        restrict: 'E',
+        scope: {
+            mode: '@',
+            symbolicName: '<',
+            version: '<',
+            onDeleting: '&',
+            onDeleted: '&',
+            onFailed: '&',
+            onDeletingFinished: '&',
+        },
+        template: template,
+        controller: ['$scope', catalogDeleterController],
+        controllerAs: 'vm',
+    };
+
+    function catalogDeleterController($scope) {
+        let vm = this;
+
+        $scope.id = $scope.symbolicName + ':' + $scope.version;
+
+        function getBundle(bundleSymbolicName, bundleVersion) {
+            catalogApi.getBundle(bundleSymbolicName, bundleVersion).then(data => {
+                $scope.bundle = data;
+
+            }).catch(err => {
+                console.log("Error loading bundle: ", err);
+                $scope.bundleError = true;
+            }).finally(() => {
+                $scope.bundleLoading = false;
+            });
+        }
+
+        if ($scope.mode==='bundle') {
+            $scope.bundleLoading = true;
+            getBundle($scope.symbolicName, $scope.version);
+
+        } else {
+            $scope.bundleLoading = true;
+            catalogApi.getType($scope.symbolicName, $scope.version).then(data => {
+                let bundleSymbolicName, bundleVersion;
+                if (data.containingBundle) {
+                    let parts = data.containingBundle.split(':');
+                    if (parts.length>=1) {
+                        bundleSymbolicName = parts[0];
+                        if (parts.length>=2) {
+                            bundleVersion = parts[1];
+                            if (parts.length>2) {
+                                throw 'Invalid containing bundle '+data.containingBundle;
+                            }
+                        }
+                    }
+                }
+                if (!bundleSymbolicName) {
+                    throw 'Unavailable or invalid containing bundle '+data.containingBundle;
+                }
+
+                getBundle(bundleSymbolicName, bundleVersion);
+
+            }).catch(err => {
+                console.log("Error loading type: ", err);
+                if ($scope.mode==='location') {
+                    // don't display an error, probably it is a legacy-installed location
+                } else {
+                    $scope.bundleError = true;
+                }
+                $scope.bundleLoading = false;
+            });
+        }
+
+        vm.delete = () => {
+            if ($scope.onDeleting) $scope.onDeleting();
+            let promise;
+            if ($scope.mode==='bundle') {
+                promise = catalogApi.deleteBundle($scope.symbolicName, $scope.version)
+            } else if ($scope.mode==='location') {
+                promise = catalogApi.deleteLocation($scope.symbolicName, $scope.version)
+            } else if ($scope.mode==='type') {
+                // not used
+                throw 'deleteType not supported';
+            } else {
+                // shouldn't happen
+                throw 'Unknown mode: '+$scope.mode;
+            }
+
+            promise.then(data => {
+                if ($scope.onDeleted) $scope.onDeleted(data);
+            }).catch(error => {
+                let errorMessage= ('undefined' === typeof error.message)? error.error.message: error.message;
+                brSnackbar.create('Could not delete this bundle: ' + errorMessage);
+                if ($scope.onFailed) $scope.onFailed(error);
+            }).finally(() => {
+                if ($scope.onDeletingFinished) $scope.onDeletingFinished();
+            });
+        };
+        vm.checkSingleBomBundle = (bundle) => {
+            if (bundle) {
+                if (bundle.format=='brooklyn-bom-bundle' && bundle.types && bundle.types.length===1 && bundle.types[0].symbolicName===$scope.symbolicName && bundle.types[0].version===$scope.version) {
+                    return 'single-bom-match';
+                }
+            }
+            return 'default';
+        }
+    }
+
+}
diff --git a/ui-modules/location-manager/app/index.less b/ui-modules/utils/catalog-deleter/catalog-deleter.less
similarity index 65%
copy from ui-modules/location-manager/app/index.less
copy to ui-modules/utils/catalog-deleter/catalog-deleter.less
index 75c7f0b..49ef9aa 100644
--- a/ui-modules/location-manager/app/index.less
+++ b/ui-modules/utils/catalog-deleter/catalog-deleter.less
@@ -16,15 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-@import '~brooklyn-shared/style/first.less';
-
-@import '~brooklyn-ui-utils/sensitive-field/sensitive-field.less';
-
-// Add project less files here
-@import 'views/locations/locations.less';
-@import 'views/detail/detail.less';
-@import 'views/wizard/wizard.less';
-@import 'components/dynamic-config/dynamic-config';
-
-// Load last so that these style rules and var values trump others
-@import '~brooklyn-shared/style/last.less';
+.brooklyn-catalog-deleter {
+}
+.catalog-delete-popover {
+  max-width: 500px;
+}