Merge pull request #129 from ahgittin/params

UI for defining parameters
diff --git a/ui-modules/app-inspector/app/components/providers/entity-api.provider.js b/ui-modules/app-inspector/app/components/providers/entity-api.provider.js
index 0f5e18c..6003474 100644
--- a/ui-modules/app-inspector/app/components/providers/entity-api.provider.js
+++ b/ui-modules/app-inspector/app/components/providers/entity-api.provider.js
@@ -167,7 +167,7 @@
         return $http.post('/v1/applications/'+ applicationId +'/entities/' + entityId + '/adjuncts/' + adjunctId + '/stop');
     }
     function destroyEntityAdjunct(applicationId, entityId, adjunctId) {
-        return $http.post('/v1/applications/'+ applicationId +'/entities/' + entityId + '/adjuncts/' + adjunctId + '/destroy');
+        return $http.delete('/v1/applications/'+ applicationId +'/entities/' + entityId + '/adjuncts/' + adjunctId);
     }
     function updateEntityAdjunctConfig(applicationId, entityId, adjunctId, configId, data) {
         return $http.post('/v1/applications/'+ applicationId +'/entities/' + entityId + '/adjuncts/' + adjunctId + '/config/' + configId, data);
diff --git a/ui-modules/app-inspector/app/views/main/inspect/summary/summary.controller.js b/ui-modules/app-inspector/app/views/main/inspect/summary/summary.controller.js
index 5c30829..51b0af3 100644
--- a/ui-modules/app-inspector/app/views/main/inspect/summary/summary.controller.js
+++ b/ui-modules/app-inspector/app/views/main/inspect/summary/summary.controller.js
@@ -188,7 +188,8 @@
             entityApi.updateEntityName(applicationId, entityId, this.name).then((response)=> {
                 vm.entity.name = vm.name;
             }).catch((error)=> {
-                brSnackbar.create('Cannot update entity name: the entity [' + entityId + '] is undefined');
+                let errorMessage= ('undefined' === typeof error.message)? error.error.message: error.message;
+                brSnackbar.create('Cannot update entity name: ' + errorMessage);
                 vm.name = vm.entity.name;
             });
         }
diff --git a/ui-modules/blueprint-composer/app/components/dsl-editor/dsl-editor.js b/ui-modules/blueprint-composer/app/components/dsl-editor/dsl-editor.js
index cf1e695..63d687a 100644
--- a/ui-modules/blueprint-composer/app/components/dsl-editor/dsl-editor.js
+++ b/ui-modules/blueprint-composer/app/components/dsl-editor/dsl-editor.js
@@ -18,7 +18,7 @@
  */
 import angular from 'angular';
 import angularSanitize from 'angular-sanitize';
-import {Dsl, KIND} from '../util/model/dsl.model';
+import {Dsl, DslParser, KIND} from '../util/model/dsl.model';
 import template from './dsl-editor.template.html';
 import brAutoFocus from 'brooklyn-ui-utils/autofocus/autofocus';
 import brUtils from 'brooklyn-ui-utils/utils/general';
@@ -166,7 +166,7 @@
             newValue.forEach((argument, index) => {
                 let dsl;
                 try {
-                    dsl = dslService.parse(argument, scope.entity, blueprintService.get());
+                    dsl = new DslParser().parseString(argument, scope.entity, blueprintService.get());
                     scope.dsl.params.splice(index + 1, 1, dsl);
                 } catch (ex) {
                     $log.debug(`Argument ${index} is not a DSL. Defaulting to string`, ex);
@@ -271,7 +271,7 @@
             }
 
             try {
-                dsl = dslService.parse(dsl.toString(), scope.entity, blueprintService.get());
+                dsl = new DslParser().parseString(dsl.toString(), scope.entity, blueprintService.get());
             } catch (ex) {
                 $log.debug(`Cannot get DSL relationship for DSL "${dsl}`, ex);
             }
diff --git a/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.js b/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.js
index e751638..75343e3 100644
--- a/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.js
+++ b/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.js
@@ -18,18 +18,18 @@
  */
 import angular from 'angular';
 import template from './dsl-viewer.template.html';
-import {KIND} from '../util/model/dsl.model';
+import {KIND, FAMILY} from '../util/model/dsl.model';
 
 const MODULE_NAME = 'brooklyn.components.dsl-viewer';
 const TEMPLATE_URL = 'blueprint-composer/component/dsl-viewer/index.html';
 
 angular.module(MODULE_NAME, [])
-    .directive('dslViewer', dslViewerDirective)
+    .directive('dslViewer', ['$log', dslViewerDirective])
     .run(['$templateCache', templateCache]);
 
 export default MODULE_NAME;
 
-export function dslViewerDirective() {
+export function dslViewerDirective($log) {
     return {
         restrict: 'E',
         templateUrl: function (tElement, tAttrs) {
@@ -42,30 +42,77 @@
     };
 
     function link(scope) {
-        scope.isTargetDsl = (dsl) => {
-            return dsl.kind === KIND.TARGET;
+        
+        function getIconForFunction(dsl) {
+            if (dsl.name === 'config') return 'fa-cog';
+            if (dsl.name === 'sensor') return 'fa-rss';
+            if (dsl.name === 'attributeWhenReady') return 'fa-pause';
+            if (dsl.name === 'literal') return 'fa-clone';
+            if (dsl.name === 'formatString') return 'fa-qrcode';
+            // catch-all
+            $log.warn("unexpected DSL function, using default icon", dsl, dsl.name); 
+            return 'fa-bolt';
         };
-        scope.isMethodDsl = (dsl) => {
-            return dsl.kind === KIND.METHOD;
+        
+        function updateModeAndIcon(dsl) {
+            var fam = dsl.kind && dsl.kind.family;
+            if (fam === FAMILY.FUNCTION) {
+                switch (dsl.kind) {
+                    case KIND.METHOD: {
+                        // "method" -- eg config, attrWhenReady -- shows param inline if one param
+                        // (if more than one, which shouldn't happen currently, shows all in a list)
+                        scope.mode = "method"; 
+                        scope.icon = getIconForFunction(dsl); 
+                        return;
+                    }
+                    case KIND.UTILITY: {
+                        // "utility" -- eg format string -- shows first param inline, then other params in list
+                        scope.mode = "utility"; 
+                        scope.icon = getIconForFunction(dsl); 
+                        return;
+                    }
+                    case KIND.TARGET: {
+                        scope.mode = "target"; 
+                        scope.icon = null;
+                        scope.relatedEntity = getRelatedEntity(dsl);
+                        return;
+                    }
+                }      
+            }
+            if (fam === FAMILY.CONSTANT) {
+                scope.mode = "constant"; 
+                scope.icon = null; 
+                return;
+            }
+            if (fam === FAMILY.REFERENCE) {
+                scope.mode = "reference"; 
+                scope.icon = null; 
+                return;
+            }
+            // catch-all
+            $log.warn("unexpected DSL family, using default icon", dsl, dsl.kind);
+            scope.icon = "fa-bolt";
+            scope.mode = "DSL";
+            return;
         };
-        scope.isFormatStringDsl = (dsl) => {
-            return dsl.kind === KIND.UTILITY && dsl.name === 'formatString';
-        };
-        scope.isLiteralDsl = (dsl) => {
-            return [KIND.STRING, KIND.NUMBER].includes(dsl.kind);
-        };
-        scope.getRelatedEntity = () => {
-            if (scope.dsl.params.length > 0) {
+        
+        function getRelatedEntity(dsl) {
+            if (dsl.params.length > 0) {
                 // If the DSL is looking at an entity ID
-                return scope.dsl.getRoot().relationships.find(entity => entity.id === scope.dsl.params[0].name)
-            } else if (scope.dsl.getRoot().relationships.length > 0) {
+                return dsl.getRoot().relationships.find(entity => entity.id === dsl.params[0].name)
+            } else if (dsl.getRoot().relationships.length > 0) {
                 // If the DSL is of the form $brooklyn:self() or $brooklyn:parent()
-                return scope.dsl.getRoot().relationships[0];
+                return dsl.getRoot().relationships[0];
             } else {
                 // Otherwise, there is no related entity
                 return null;
             }
         }
+        
+        scope.$watch('dsl', () => {
+            updateModeAndIcon(scope.dsl);
+        }, true);
+        
     }
 }
 
diff --git a/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.less b/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.less
new file mode 100644
index 0000000..efb26d3
--- /dev/null
+++ b/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.less
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+dsl-viewer {
+    .dsl-constant {
+        font-size: 80%;  // same as label
+    }
+}
diff --git a/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.template.html b/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.template.html
index 49d8f9c..3574ecf 100644
--- a/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.template.html
+++ b/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.template.html
@@ -16,36 +16,75 @@
   specific language governing permissions and limitations
   under the License.
 -->
-<span ng-if="isTargetDsl(dsl)">
-    <span ng-if="dsl.getRoot().relationships.length === 0" class="label label-primary">{{dsl.params[0].name}}</span>
-    <a ng-if="dsl.getRoot().relationships.length > 0" ui-sref="main.graphical.edit.entity({entityId: getRelatedEntity()._id})"><span class="label label-primary">{{(getRelatedEntity() | entityName) || 'New application'}}</span></a>
+<span ng-switch="mode">
+
+  <span ng-switch-when="target">
+    <span ng-if="dsl.getRoot().relationships.length === 0" class="label label-primary">
+        <span ng-if="!dsl.params[0] || !dsl.params[0].name || (dsl.name!='component' && dsl.name!='entity')">
+            {{dsl.name}}
+        </span>
+        {{dsl.params[0].name}}
+    </span>
+    <a ng-if="dsl.getRoot().relationships.length > 0" ui-sref="main.graphical.edit.entity({entityId: relatedEntity._id})"><span class="label label-primary">{{relatedEntity ? (relatedEntity | entityName) || 'Application' : dsl.name}}</span></a>
     <span ng-if="dsl.next">

         <dsl-viewer dsl="dsl.next"></dsl-viewer>
     </span>
-</span>
-
-<span ng-if="isMethodDsl(dsl) && !isFormatStringDsl(dsl)">
+  </span>
+  
+  <span ng-switch-when="method">
     <span class="label label-default">
-        <i class="fa" ng-class="{'fa-cog': dsl.name === 'config', 'fa-rss': dsl.name === 'sensor', 'fa-pause': dsl.name === 'attributeWhenReady'}"></i>
+        <i class="fa" ng-class="icon"></i>
         {{dsl.name}}
     </span>
-    →
-    <dsl-viewer dsl="dsl.params[0]"></dsl-viewer>
-</span>
-
-<span ng-if="isFormatStringDsl(dsl)">
-    <span class="label label-info">
-        <i class="fa fa-qrcode"></i>
-        pattern
+    
+    <span ng-if="dsl.params.length == 1">
+        →
+        <dsl-viewer dsl="dsl.params[0]"></dsl-viewer>
     </span>
-    →
-    {{dsl.params[0].name}}
-    <ol ng-if="dsl.params.length > 0">
+    <ul ng-if="dsl.params.length > 1">
+        <li ng-repeat="argument in dsl.params | limitTo:dsl.params.length">
+            <dsl-viewer dsl="argument"></dsl-viewer>
+        </li>
+    </ul>
+    
+    <span ng-if="dsl.next">
+        ⇒
+        <dsl-viewer dsl="dsl.next"></dsl-viewer>
+    </span>
+  </span>
+  
+  <span ng-switch-when="utility">
+    <span class="label label-info">
+        <i class="fa" ng-class="icon"></i>
+        {{dsl.name}}
+    </span>
+    
+    <span ng-if="dsl.params.length >= 1">
+        →
+        <dsl-viewer dsl="dsl.params[0]"></dsl-viewer>
+    </span>
+    <ul ng-if="dsl.params.length > 1">
         <li ng-repeat="argument in dsl.params | limitTo:dsl.params.length:1">
             <dsl-viewer dsl="argument"></dsl-viewer>
         </li>
-    </ol>
-</span>
+    </ul>
+  </span>
 
-<span ng-if="isLiteralDsl(dsl)">{{dsl.name}}</span>
\ No newline at end of file
+  <span ng-switch-when="constant">
+    <span class="dsl-constant">
+        <samp>{{dsl.name}}</samp>
+    </span>
+  </span>
+
+  <span ng-switch-when="reference">
+    <span class="label label-primary">
+        {{dsl.name}}
+    </span>
+  </span>
+
+  <span ng-switch-default>
+    {{ !mode || mode==='unknown' ? '' : mode }}: {{ dsl.name }}
+  </span>
+  
+</span>
diff --git a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
index 38687da..5e43ab7 100644
--- a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
+++ b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
@@ -266,15 +266,28 @@
         return deferred.promise;
     }
 
+    function locationType(location) {
+        if (!location || typeof location === 'string') return location;
+        if (typeof location === 'object' && Object.keys(location).length==1) return Object.keys(location)[0];
+        return null;
+    }
+    
     function refreshLocationMetadata(entity) {
         let deferred = $q.defer();
 
         if (entity.hasLocation()) {
-            paletteApi.getLocation(entity.location).then((location)=> {
-                deferred.resolve(populateLocationFromApiSuccess(entity, location.catalog || location));
-            }).catch(function () {
-                deferred.resolve(populateLocationFromApiError(entity));
-            });
+            let type = locationType(entity.location);
+            if (type.startsWith("jclouds:")) {
+                // types eg jclouds:aws-ec2 are low-level, not in the catalog
+                deferred.resolve(populateLocationFromApiSuccess(entity, { yamlHere: entity.location }));
+            } else {
+                paletteApi.getLocation(locationType(entity.location)).then((location)=> {
+                    let loc = Object.assign({}, location.catalog || location, { yamlHere: entity.location });
+                    deferred.resolve(populateLocationFromApiSuccess(entity, loc));
+                }).catch(function () {
+                    deferred.resolve(populateLocationFromApiError(entity));
+                });
+            }
         } else {
             deferred.resolve(entity);
         }
@@ -609,18 +622,30 @@
         return entity;
     }
 
-    function populateLocationFromApiSuccess(entity, data) {
+    function populateLocationFromApiCommon(entity, data) {
         entity.clearIssues({group: 'location'});
-        entity.location = data.symbolicName;
-        entity.miscData.set('locationName', data.name);
-        entity.miscData.set('locationIcon', data.iconUrl || iconGenerator(data.symbolicName));
+        entity.location = data.yamlHere || data.symbolicName;
+        
+        let name = data.name || data.displayName;
+        if (!name && data.yamlHere) {
+            name = typeof data.yamlHere === 'object' ? Object.keys(data.yamlHere)[0] : data.yamlHere;
+        }
+        if (!name) name =  data.symbolicName;
+        entity.miscData.set('locationName', name);
+        
+        // use icon on item, but if none then generate using *yaml* to distinguish when someone has changed it
+        // (especially for things like jclouds:aws-ec2 -- the config is more interesting than the type name)
+        entity.miscData.set('locationIcon', data==null ? null : data.iconUrl || iconGenerator(data.yamlHere ? JSON.stringify(data.yamlHere) : data.symbolicName));
         return entity;
     }
+    
+    function populateLocationFromApiSuccess(entity, data) {
+        populateLocationFromApiCommon(entity, data);
+    }
 
     function populateLocationFromApiError(entity) {
-        entity.clearIssues({group: 'location'});
+        populateLocationFromApiCommon(entity, { yamlHere: entity.location });
         entity.addIssue(Issue.builder().level(ISSUE_LEVEL.WARN).group('location').message($sce.trustAsHtml(`Location <samp>${!(entity.location instanceof String) ? JSON.stringify(entity.location) : entity.location}</samp> does not exist in your local catalog. Deployment might fail.`)).build());
-        entity.miscData.set('locationName', entity.location);
         entity.miscData.set('locationIcon', typeNotFoundIcon);
         return entity;
     }
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
index 5b7cb11..00f2467 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
@@ -311,6 +311,8 @@
 
         scope.nonempty = (o) => o && Object.keys(o).length;
         scope.defined = specEditor.defined = (o) => (typeof o !== 'undefined');
+        specEditor.isInstance = (x, type) => (typeof x === type);
+                
         specEditor.advanceOutToFormGroupInPanel = (element, event) => {
             focusIfPossible(event, findAncestor(element, "form-group", "panel-body")) || element[0].blur();
         };
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
index 50c5246..3cc94b9 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
@@ -22,9 +22,6 @@
 @hide-info-button-when-not-hovered: false;
 @hide-unset-undefault-values: true;
 
-@label-gray: darken(@gray-light, 10%);
-@gray-lightest: #f8f8f9;
-
 spec-editor {
   display: block;
   margin-top: 15px;
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
index 81b5964..0ed851c 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
@@ -563,7 +563,12 @@
             <p ng-repeat="issue in state.issues | filter:{group:'location'}" class="alert alert-{{issue.severity}}">
                 <em ng-bind-html="issue.message"></em>
             </p>
-            <p>Will be deployed to: <strong>{{model.miscData.get('locationName')}}</strong></p>
+            <p>
+                Targeted at: 
+                <strong ng-if="specEditor.isInstance(model.location, 'string')">{{ model.miscData.get('locationName') }}</strong>
+                <pre ng-if="!specEditor.isInstance(model.location, 'string')">{{ model.location | json }}</pre>
+            </p>
+            <br/>
             <a class="btn btn-default" ui-sref="main.graphical.edit.add({entityId: model._id, family: 'location'})">Change location</a>
             <button class="btn btn-danger btn-link" ng-click="model.clearIssues({group: 'location'}).removeLocation()">Remove</button>
         </div>
diff --git a/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js b/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js
index 0e2d9d0..b199ab0 100755
--- a/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js
+++ b/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js
@@ -618,11 +618,13 @@
             .duration(_configHolder.transition)
             .attr('opacity', (d)=>(d.data.hasLocation() ? 1 : 0));
         appendElements(location, _configHolder.nodes.location);
+        
         nodeData.select('g.node-location image')
             .transition()
             .duration(_configHolder.transition)
-            .attr('opacity', (d)=>(d.data.miscData.get('locationIcon') ? 1 : 0))
-            .attr('xlink:href', (d)=>(d.data.miscData.get('locationIcon')));
+            .attr('opacity', (d)=>(d.data.miscData.get('locationIcon') ? 1 : 0));
+        nodeData.select('g.node-location image')
+            .attr('xlink:href', (d)=>d.data.miscData.get('locationIcon'));
 
         // Draw important adjuncts (i.e policies/enrichers)
         // -----------------------------------------------------
diff --git a/ui-modules/blueprint-composer/app/components/util/model/entity.model.js b/ui-modules/blueprint-composer/app/components/util/model/entity.model.js
index af43652..782da39 100644
--- a/ui-modules/blueprint-composer/app/components/util/model/entity.model.js
+++ b/ui-modules/blueprint-composer/app/components/util/model/entity.model.js
@@ -272,6 +272,7 @@
      */
     set location(location) {
         LOCATIONS.set(this, location);
+        this.miscData.delete('locationRemoved');
         this.touch();
     }
 
@@ -281,6 +282,13 @@
      */
     removeLocation() {
         LOCATIONS.delete(this);
+        this.miscData.delete('locationName');
+        this.miscData.delete('locationIcon');
+        
+        // this field provides a way for consumers to detect if the location was explicitly removed;
+        // this can be useful to prevent default locations from being applied
+        this.miscData.set('locationRemoved', true);
+        
         this.touch();
     }
 
diff --git a/ui-modules/blueprint-composer/app/index.less b/ui-modules/blueprint-composer/app/index.less
index 1562b40..e42474c 100644
--- a/ui-modules/blueprint-composer/app/index.less
+++ b/ui-modules/blueprint-composer/app/index.less
@@ -35,9 +35,7 @@
 @import "components/blueprint-data-manager/blueprint-data-manager.style.less";
 @import "components/catalog-saver/catalog-saver.less";
 @import "components/dsl-editor/dsl-editor";
-
-@gray-lighter: #E1E5E7;
-@gray-light: #818899;
+@import "components/dsl-viewer/dsl-viewer";
 
 .make-icon(@size) {
   width: auto;
diff --git a/ui-modules/catalog/app/views/bundle/bundle.state.js b/ui-modules/catalog/app/views/bundle/bundle.state.js
index dd6e248..8323050 100644
--- a/ui-modules/catalog/app/views/bundle/bundle.state.js
+++ b/ui-modules/catalog/app/views/bundle/bundle.state.js
@@ -77,7 +77,8 @@
         catalogApi.deleteBundle($scope.bundle.symbolicName, $scope.bundle.version).then(data => {
             $state.go(catalogState);
         }).catch(error => {
-            brSnackbar.create('Could not delete this bundle: ' + error.message);
+            let errorMessage= ('undefined' === typeof error.message)? error.error.message: error.message;
+            brSnackbar.create('Could not delete this bundle: ' + errorMessage);
         }).finally(() => {
             $scope.state.deleting = false;
         });
@@ -103,7 +104,8 @@
         $scope.bundleVersions = bundleVersions.map(bundleVersion => bundleVersion.version);
         $scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
     }).catch(error => {
-        brSnackbar.create(`Could not load bundle ${$stateParams.bundleId}:${$stateParams.bundleVersion}: ${error.status === 404 ? 'Not found' : error.message}`);
+        let errorMessage= ('undefined' === typeof error.message)? error.error.message: error.message;
+        brSnackbar.create(`Could not load bundle ${$stateParams.bundleId}:${$stateParams.bundleVersion}: ${error.status === 404 ? 'Not found' : errorMessage}`);
         $state.go(catalogState);
     });
 }
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 426c1a9..01c0818 100644
--- a/ui-modules/location-manager/app/views/detail/detail.controller.js
+++ b/ui-modules/location-manager/app/views/detail/detail.controller.js
@@ -86,7 +86,7 @@
             $state.go('locations');
             brSnackbar.create('Location "' + $filter('locationName')(vm.location) + '" deleted successfully');
         }).catch(error => {
-            brSnackbar.create('Could not delete this location: ' + error.message);
+            brSnackbar.create('Could not delete this location: ' + error.error.message);
         });
     };
     vm.editLocation = function () {
diff --git a/ui-modules/location-manager/app/views/wizard/advanced/advanced.controller.js b/ui-modules/location-manager/app/views/wizard/advanced/advanced.controller.js
index 975d9c3..0107bc2 100644
--- a/ui-modules/location-manager/app/views/wizard/advanced/advanced.controller.js
+++ b/ui-modules/location-manager/app/views/wizard/advanced/advanced.controller.js
@@ -78,7 +78,7 @@
         catalogApi.create(payload).then(data => {
             $state.go('detail', {symbolicName: vm.id, version: vm.version});
         }).catch(error => {
-            brSnackbar.create('Could not save location: ' + error.data.message ? error.data.message : error.data);
+            brSnackbar.create('Could not save location: ' + error.error.message ? error.error.message : error.data);
         });
     };
 
diff --git a/ui-modules/location-manager/app/views/wizard/byon/byon.controller.js b/ui-modules/location-manager/app/views/wizard/byon/byon.controller.js
index c12c789..51ab1db 100644
--- a/ui-modules/location-manager/app/views/wizard/byon/byon.controller.js
+++ b/ui-modules/location-manager/app/views/wizard/byon/byon.controller.js
@@ -95,7 +95,7 @@
         catalogApi.create(payload).then(data => {
             $state.go('detail', {symbolicName: vm.id, version: vm.version});
         }).catch(error => {
-            brSnackbar.create('Could not save location: ' + error.data.message ? error.data.message : error.data);
+            brSnackbar.create('Could not save location: ' + error.error.message ? error.error.message : error.error);
         });
     };
 
diff --git a/ui-modules/location-manager/app/views/wizard/cloud/cloud.controller.js b/ui-modules/location-manager/app/views/wizard/cloud/cloud.controller.js
index 4e015c4..996a156 100644
--- a/ui-modules/location-manager/app/views/wizard/cloud/cloud.controller.js
+++ b/ui-modules/location-manager/app/views/wizard/cloud/cloud.controller.js
@@ -102,7 +102,7 @@
         catalogApi.create(payload).then(data => {
             $state.go('detail', {symbolicName: vm.id, version: vm.version});
         }).catch(error => {
-            brSnackbar.create('Could not save location: ' + error.data.message ? error.data.message : error.data);
+            brSnackbar.create('Could not save location: ' + error.error.message ? error.error.message : error.error);
         });
     };
 
diff --git a/ui-modules/rest-api-docs/app/views/main/main.controller.js b/ui-modules/rest-api-docs/app/views/main/main.controller.js
index dba0024..fe08a72 100644
--- a/ui-modules/rest-api-docs/app/views/main/main.controller.js
+++ b/ui-modules/rest-api-docs/app/views/main/main.controller.js
@@ -56,7 +56,8 @@
         jsonEditor: false,
         defaultModelRendering: 'schema',
         showRequestHeaders: false,
-        showOperationIds: false
+        showOperationIds: false,
+        validatorUrl: null
     });
 
     swaggerUi.load();
diff --git a/ui-modules/utils/br-core/style/buttons.less b/ui-modules/utils/br-core/style/buttons.less
index e87dbc3..baefe44 100644
--- a/ui-modules/utils/br-core/style/buttons.less
+++ b/ui-modules/utils/br-core/style/buttons.less
@@ -23,6 +23,9 @@
 .btn-accent {
     .button-variant(@btn-accent-color; @btn-accent-bg; @btn-accent-border);
 }
+.btn-light {
+  .button-variant(@gray-dark; @gray-lightest; @gray-lightest);
+}
 
 .btn-outline {
     &.btn-primary:not(:hover) {
@@ -49,4 +52,9 @@
         background-color: transparent;
         color: @brand-danger;
     }
+    
+    &.btn-light:not(:hover) {
+        background-color: transparent;
+        color: @gray-lightest;
+    }
 }
diff --git a/ui-modules/utils/br-core/style/variables.less b/ui-modules/utils/br-core/style/variables.less
index efe673a..439ee28 100644
--- a/ui-modules/utils/br-core/style/variables.less
+++ b/ui-modules/utils/br-core/style/variables.less
@@ -21,18 +21,18 @@
 @brand-accent: #bf3727;
 
 
-// Create path variables for fonts and images
-@br-core-path-font: '../fonts';
-@br-core-path-img: '../img';
-// Override the font-awesome path to use our custom one
-@fa-font-path: @br-core-path-font;
+// bootstrap colours at
+// https://github.com/twbs/bootstrap-sass/blob/master/assets/stylesheets/bootstrap/_variables.scss
 
+@gray-light: #818899;   // override bootstrap default of #777
+@gray-lighter: #E1E5E7; // override bootstrap default of #eee
+@gray-lightest: #f8f8f9;
+@label-gray: darken(@gray-light, 10%);  // between @gray and @gray-light
 
-@body-bg: hsl(223,30%,97%);
+@body-bg: hsl(223,30%,97%);  // override bootstrap default, #333, same as @gray-dark, used for the button bar, body default bg colour from bootstrap scaffolding
+
 @card-border-color: mix(black, @body-bg, 7%);
 
-@font-family-sans-serif: "myriad-pro-1", Helvetica, Arial, sans-serif;
-
 /* Colors in pattern lab */
 .brand-primary {
   background-color: @brand-primary;
@@ -54,3 +54,13 @@
 @state-accent-text:             @accent-500;
 @state-accent-bg:               @accent-30;
 @state-accent-border:           darken(spin(@state-accent-bg, -10), 5%);
+
+
+// Create path variables for fonts and images
+
+@br-core-path-font: '../fonts';
+@br-core-path-img: '../img';
+// Override the font-awesome path to use our custom one
+@fa-font-path: @br-core-path-font;
+
+@font-family-sans-serif: "myriad-pro-1", Helvetica, Arial, sans-serif;
diff --git a/ui-modules/utils/yaml-editor/addon/schemas/blueprint.json b/ui-modules/utils/yaml-editor/addon/schemas/blueprint.json
index bbf118f..fcc8ebc 100644
--- a/ui-modules/utils/yaml-editor/addon/schemas/blueprint.json
+++ b/ui-modules/utils/yaml-editor/addon/schemas/blueprint.json
@@ -22,7 +22,6 @@
   "title": "Blueprint",
   "description": "A blueprint that is composed of one or more entities",
   "type": "object",
-  "required": [ "services" ],
   "properties": {
     "name": {
       "title": "Blueprint name",