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 2018/07/26 10:50:31 UTC

[14/28] brooklyn-ui git commit: This adds the contributed brooklyn-ui-angular code in the folder new/.

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/index.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/index.js b/new/ui-modules/blueprint-composer/app/index.js
new file mode 100755
index 0000000..f7a55b2
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/index.js
@@ -0,0 +1,132 @@
+/*
+ * 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 ngAnimate from 'angular-animate';
+import ngResource from "angular-resource";
+import ngCookies from "angular-cookies";
+import uiRouter from "angular-ui-router";
+import "angular-ui-router/release/stateEvents";
+import ngClipboard from "ngclipboard";
+
+import brCore from 'brooklyn-ui-utils/br-core/br-core';
+
+import brDragndrop from "brooklyn-ui-utils/dragndrop/dragndrop.directive.js"
+import brServerStatus from 'brooklyn-ui-utils/server-status/server-status';
+import brAutoFocus from 'brooklyn-ui-utils/autofocus/autofocus';
+import brIconGenerator from 'brooklyn-ui-utils/icon-generator/icon-generator';
+import brInterstitialSpinner from 'brooklyn-ui-utils/interstitial-spinner/interstitial-spinner';
+import brooklynModuleLinks from 'brooklyn-ui-utils/module-links/module-links';
+import brooklynUserManagement from 'brooklyn-ui-utils/user-management/user-management';
+import brYamlEditor from 'brooklyn-ui-utils/yaml-editor/yaml-editor';
+import brUtils from 'brooklyn-ui-utils/utils/general';
+
+import brSpecEditor from 'components/spec-editor/spec-editor.directive';
+import brooklynCatalogSaver from 'components/catalog-saver/catalog-saver.directive';
+import paletteApiProvider from "./components/providers/palette-api.provider"
+
+import brooklynApi from "brooklyn-ui-utils/brooklyn.api/brooklyn.api";
+import {designerDirective} from "components/designer/designer.directive";
+import {
+    catalogSelectorDirective,
+    catalogSelectorSearchFilter,
+    catalogSelectorSortFilter
+} from "components/catalog-selector/catalog-selector.directive";
+import customActionDirective from "./components/custom-action/custom-action.directive";
+import {onErrorDirective} from "components/catalog-selector/on-error.directive";
+import {breadcrumbsDirective} from "components/breacrumbs/breadcrumbs.directive";
+import {recursionHelperFactory} from "components/factories/recursion-helper.factory";
+import {objectCacheFactory} from 'components/factories/object-cache.factory';
+import {entityNameFilter, entityVersionFilter, entityTypesFilter} from "components/filters/entity.filter";
+import {locationsFilter} from "components/filters/locations.filter";
+import {blueprintServiceProvider} from "components/providers/blueprint-service.provider";
+import {dslServiceProvider} from "components/providers/dsl-service.provider";
+import {paletteDragAndDropServiceProvider} from "components/providers/palette-dragndrop.provider";
+import {actionServiceProvider} from "./components/providers/action-service.provider";
+import {mainState} from "views/main/main.controller";
+import {yamlState} from "views/main/yaml/yaml.state";
+import {graphicalState} from "views/main/graphical/graphical.state";
+import {graphicalEditState} from "views/main/graphical/edit/edit.controller";
+import {graphicalEditAddState} from "views/main/graphical/edit/add/add";
+import {graphicalEditEntityState} from "views/main/graphical/edit/entity/edit.entity.controller";
+import {graphicalEditPolicyState} from "views/main/graphical/edit/policy/edit.policy.controller";
+import {graphicalEditEnricherState} from "views/main/graphical/edit/enricher/edit.enricher.controller";
+import {graphicalEditSpecState} from "views/main/graphical/edit/spec/edit.spec.controller";
+import {graphicalEditDslState, dslParamLabelFilter} from "views/main/graphical/edit/dsl/edit.dsl.controller";
+import bottomSheet from "brooklyn-ui-utils/bottom-sheet/bottom-sheet";
+import stackViewer from 'angular-java-stack-viewer';
+
+angular.module('app', [ngAnimate, ngResource, ngCookies, ngClipboard, uiRouter, 'ui.router.state.events', brCore, brServerStatus, brAutoFocus, brIconGenerator, brInterstitialSpinner, brooklynModuleLinks, brooklynUserManagement, brYamlEditor, brUtils, brSpecEditor, brooklynCatalogSaver, brooklynApi, bottomSheet, stackViewer, brDragndrop, customActionDirective, paletteApiProvider])
+    .directive('designer', ['$log', '$state', '$q', 'iconGenerator', 'catalogApi', 'blueprintService', 'brSnackbar', 'paletteDragAndDropService', designerDirective])
+    .directive('onError', onErrorDirective)
+    .directive('catalogSelector', catalogSelectorDirective)
+    .directive('breadcrumbs', breadcrumbsDirective)
+    .provider('blueprintService', blueprintServiceProvider)
+    .provider('dslService', dslServiceProvider)
+    .provider('paletteDragAndDropService', paletteDragAndDropServiceProvider)
+    .provider('actionService', actionServiceProvider)
+    .factory('recursionHelper', ['$compile', recursionHelperFactory])
+    .factory('objectCache', ['$cacheFactory', objectCacheFactory])
+    .filter('entityName', entityNameFilter)
+    .filter('entityVersion', entityVersionFilter)
+    .filter('entityTypes', entityTypesFilter)
+    .filter('locations', locationsFilter)
+    .filter('catalogSelectorSearch', catalogSelectorSearchFilter)
+    .filter('catalogSelectorSort', ['$filter', catalogSelectorSortFilter])
+    .filter('dslParamLabel', ['$filter', dslParamLabelFilter])
+    .config(['$urlRouterProvider', '$stateProvider', '$logProvider', applicationConfig])
+    .config(['actionServiceProvider', actionConfig])
+    .run(['$rootScope', '$state', 'brSnackbar', errorHandler])
+    .run(['$http', httpConfig]);
+
+function applicationConfig($urlRouterProvider, $stateProvider, $logProvider) {
+    $logProvider.debugEnabled(false);
+    $urlRouterProvider
+        .otherwise(graphicalState.url);
+    $stateProvider
+        .state(mainState)
+        .state(yamlState)
+        .state(graphicalState)
+        .state(graphicalEditAddState)
+        .state(graphicalEditState)
+        .state(graphicalEditEntityState)
+        .state(graphicalEditPolicyState)
+        .state(graphicalEditEnricherState)
+        .state(graphicalEditSpecState)
+        .state(graphicalEditDslState);
+}
+
+function actionConfig(actionServiceProvider) {
+    actionServiceProvider.addAction("deploy", {html: '<button class="btn btn-success" ng-click="vm.deployApplication()" ng-disabled="vm.deploying">Deploy</button>'});
+    actionServiceProvider.addAction("add", {html: '<catalog-saver config="vm.saveToCatalogConfig"></catalog-saver>'});
+}
+
+function errorHandler($rootScope, $state, brSnackbar) {
+    $rootScope.$on('$stateChangeError', (event, toState, toParams, fromState, fromParams, error)=> {
+        brSnackbar.create(error.detail);
+        if (toState === yamlState) {
+            $state.go(toState);
+        } else {
+            $state.go(graphicalState);
+        }
+    });
+}
+
+function httpConfig($http){
+    $http.defaults.headers.common['X-Csrf-Token-Required-For-Requests'] = 'write';
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/index.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/index.less b/new/ui-modules/blueprint-composer/app/index.less
new file mode 100644
index 0000000..5baf074
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/index.less
@@ -0,0 +1,54 @@
+/*
+ * 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 '~brooklyn-shared/style/first.less';
+
+@import "~brooklyn-ui-utils/bottom-sheet/bottom-sheet.less";
+@import "~brooklyn-ui-utils/yaml-editor/yaml-editor.less";
+
+// Add project less files here
+@import "views/main/main";
+@import "views/main/graphical/edit/edit";
+@import "views/main/graphical/edit/dsl/edit.dsl.less";
+@import "views/main/graphical/graphical.state.less";
+@import "views/main/yaml/yaml";
+@import "components/designer/designer";
+@import "components/spec-editor/spec-editor";
+@import "components/catalog-selector/catalog-selector";
+@import "components/breacrumbs/breadcrumbs";
+@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;
+
+.make-icon(@size) {
+  width: auto;
+  height: auto;
+  max-width: @size;
+  max-height: @size;
+}
+
+.navbar {
+  margin-bottom: 0;
+}
+
+// Load last so that these style rules and var values trump others
+@import "~brooklyn-shared/style/last.less";
+

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/index.spec.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/index.spec.js b/new/ui-modules/blueprint-composer/app/index.spec.js
new file mode 100644
index 0000000..e241074
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/index.spec.js
@@ -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.
+ */
+describe('Blueprint composer module', () => {
+    it('has a dummy unit test', () => {
+        expect(true).toBeTruthy();
+    });
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/bottom-sheet.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/bottom-sheet.less b/new/ui-modules/blueprint-composer/app/views/main/bottom-sheet.less
new file mode 100644
index 0000000..21f6d23
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/bottom-sheet.less
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+@progress-border-radius: 0px;
+
+.bottom-sheet {
+  .progress {
+    margin-bottom: 0px;
+  }
+  .container-padding {
+    padding: 10px 15px;
+    overflow: hidden;
+
+    .bottom-sheet-links a {
+      margin-right: 10px;
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/bottom-sheet.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/bottom-sheet.template.html b/new/ui-modules/blueprint-composer/app/views/main/bottom-sheet.template.html
new file mode 100644
index 0000000..833c453
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/bottom-sheet.template.html
@@ -0,0 +1,38 @@
+<!--
+  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.
+-->
+<uib-progressbar class="progress-striped active" type="info" ng-if="ctrl.loading"></uib-progressbar>
+
+<div class="container-padding">
+    <h4 ng-if="ctrl.loading">Deploying...</h4>
+    <h4 ng-if="ctrl.error" class="text-danger">{{ctrl.error.title}}</h4>
+    <p ng-if="ctrl.error" class="text-muted">{{ctrl.error.message}}</p>
+    <p ng-if="ctrl.error.details" class="text-muted bottom-sheet-links">
+        <a class="text-danger" ng-click="ctrl.viewStackTrace = !ctrl.viewStackTrace">
+            <i class="fa fa-list-ul"></i> {{ctrl.viewStackTrace ? 'Hide' : 'View'}} stacktrace
+        </a>
+        <a class="text-muted" ngclipboard ngclipboard-success="ctrl.onClipboardSuccess(e)" ngclipboard-error="ctrl.onClipboardError(e)" data-clipboard-text="{{ctrl.error.details}}" uib-tooltip="Copied!" tooltip-trigger="{'copied': 'mouseleave'}" tooltip-placement="top">
+            <i class="fa fa-clipboard"></i> Copy stacktrace
+        </a>
+    </p>
+    <div ng-if="ctrl.error.details" tb-stack-viewer trace="ctrl.error.details" vendors="ctrl.vendors" uib-collapse="!ctrl.viewStackTrace"></div>
+    <div class="text-right">
+        <button ng-if="ctrl.error" ng-click="ctrl.investigate()" class="btn btn-default" title="Investigate issue"><i class="fa fa-search-minus" aria-hidden="true" aria-label="Investigate issue"></i></button>
+        <button ng-if="ctrl.error" ng-click="ctrl.close()" class="btn btn-danger">Dismiss</button>
+    </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.html b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.html
new file mode 100644
index 0000000..4e74a85
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.html
@@ -0,0 +1,27 @@
+<!--
+  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.
+-->
+<section class="spec-parent">
+    <a ng-href="{{getParentLink()}}">{{getParentLinkLabel()}}</a>
+</section>
+
+<div class="panel-group"><div class="panel"><div class="panel-body">
+
+    <catalog-selector family="family" on-select="onTypeSelected(item)" items-per-page="catalogItemsPerPage"></catalog-selector>
+
+</div></div></div>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.js b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.js
new file mode 100644
index 0000000..1d23ce1
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.js
@@ -0,0 +1,120 @@
+/*
+ * 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 {graphicalEditEntityState} from '../entity/edit.entity.controller';
+import {graphicalEditSpecState} from '../spec/edit.spec.controller';
+import {graphicalEditPolicyState} from '../policy/edit.policy.controller';
+import {graphicalEditEnricherState} from '../enricher/edit.enricher.controller';
+import {Entity, EntityFamily} from '../../../../../components/util/model/entity.model';
+import template from './add.html';
+
+export const graphicalEditAddState = {
+    name: 'main.graphical.edit.add',
+    url: '/add/:family?configKey',
+    template: template,
+    controller: ['$scope', '$filter', '$state', '$stateParams', 'blueprintService', GraphicalEditAddController],
+    controllerAs: 'vm',
+};
+
+export function GraphicalEditAddController($scope, $filter, $state, $stateParams, blueprintService) {
+    switch ($stateParams.family) {
+        case EntityFamily.ENTITY.id.toLowerCase():
+            $scope.family = EntityFamily.ENTITY;
+            break;
+        case EntityFamily.SPEC.id.toLowerCase():
+            $scope.family = EntityFamily.SPEC;
+            $scope.configKey = $stateParams.configKey;
+            break;
+        case EntityFamily.POLICY.id.toLowerCase():
+            $scope.family = EntityFamily.POLICY;
+            break;
+        case EntityFamily.ENRICHER.id.toLowerCase():
+            $scope.family = EntityFamily.ENRICHER;
+            break;
+        case EntityFamily.LOCATION.id.toLowerCase():
+            $scope.family = EntityFamily.LOCATION;
+            break;
+    }
+
+    $scope.getParentLink = ()=> {
+        let state = graphicalEditEntityState;
+        let params = {entityId: $scope.entity.hasParent() ? $scope.entity.parent._id : $scope.entity._id};
+        if ($scope.entity.hasParent() && $scope.entity.parent.family === EntityFamily.SPEC) {
+            state = graphicalEditSpecState;
+            params = {entityId: $scope.entity.parent.parent._id, specId: $scope.entity.parent._id};
+        }
+
+        // We have a family, this means we are currently displaying the catalog selector
+        if ($scope.family) {
+            params = {entityId: $scope.entity._id};
+            if ($scope.entity.family === EntityFamily.SPEC) {
+                state = graphicalEditSpecState;
+                params = {entityId: $scope.entity.parent._id, specId: $scope.entity._id};
+            }
+        }
+
+        return $state.href(state, params);
+    };
+
+    $scope.getParentLinkLabel = ()=> {
+        let label = $filter('entityName')($scope.entity.parent) || 'New application';
+
+        if ($scope.family) {
+            label = $filter('entityName')($scope.entity) || 'New application';
+        }
+
+        return label;
+    };
+
+    $scope.onTypeSelected = (type)=> {
+        switch ($scope.family) {
+            case EntityFamily.ENTITY:
+                let newEntity = blueprintService.populateEntityFromApi(new Entity(), type);
+                $scope.entity.addChild(newEntity);
+                blueprintService.refreshEntityMetadata(newEntity, EntityFamily.ENTITY).then(() => {
+                    $state.go(graphicalEditEntityState, {entityId: newEntity._id});
+                });
+                break;
+            case EntityFamily.SPEC:
+                let newSpec = blueprintService.populateEntityFromApi(new Entity(), type);
+                $scope.entity.setClusterMemberspecEntity($scope.configKey, newSpec);
+                blueprintService.refreshEntityMetadata(newSpec, EntityFamily.ENTITY).then(() => {
+                    $state.go(graphicalEditSpecState, {specId: newSpec._id});
+                });
+                break;
+            case EntityFamily.POLICY:
+                let newPolicy = blueprintService.populateEntityFromApi(new Entity(), type);
+                $scope.entity.addPolicy(newPolicy);
+                blueprintService.refreshEntityMetadata(newPolicy, EntityFamily.POLICY).then(() => {
+                    $state.go(graphicalEditPolicyState, {entityId: $scope.entity._id, policyId: newPolicy._id});
+                });
+                break;
+            case EntityFamily.ENRICHER:
+                let newEnricher = blueprintService.populateEntityFromApi(new Entity(), type);
+                $scope.entity.addEnricher(newEnricher);
+                blueprintService.refreshEntityMetadata(newEnricher, EntityFamily.ENRICHER).then(() => {
+                    $state.go(graphicalEditEnricherState, {entityId: $scope.entity._id, enricherId: newEnricher._id});
+                });
+                break;
+            case EntityFamily.LOCATION:
+                blueprintService.populateLocationFromApi($scope.entity, type);
+                $state.go(graphicalEditEntityState, {entityId: $scope.entity._id});
+                break;
+        }
+    };
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.controller.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.controller.js b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.controller.js
new file mode 100644
index 0000000..97188ce
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.controller.js
@@ -0,0 +1,187 @@
+/*
+ * 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 template from './edit.dsl.template.html';
+import {Dsl, KIND} from '../../../../../components/util/model/dsl.model';
+
+function EditDslController($scope, $state, $stateParams, objectCache, state) {
+    $scope.state = state;
+
+    let dsl = state.rootDsl;
+    $scope.breadcrumbs = state.levels.map((level, index) => {
+        let ret = {
+            index: level + 1,
+            dsl: dsl,
+            view: {
+                name: graphicalEditDslState.name,
+                params: {
+                    levels: state.levels.slice(0, index).join(',')
+                }
+            }
+        };
+        dsl = dsl.params[level + 1];
+        return ret;
+    });
+
+    $scope.$on('brooklyn.components.dsl-editor.select', (event, data) => {
+        let viewName = graphicalEditDslState.name;
+        let viewParams = $stateParams;
+
+        updateRootDsl(data.dsl);
+
+        if ($scope.state.levels.length === 0) {
+            if ($stateParams.index) {
+                $scope.state.entity.config.get($stateParams.for)[$stateParams.index] = $scope.state.rootDsl;
+            } else {
+                $scope.state.entity.addConfig($stateParams.for, $scope.state.rootDsl);
+            }
+            objectCache.removeAll();
+            viewName = $scope.state.view.name;
+            viewParams = $scope.state.view.params;
+        } else {
+            let levels = angular.copy($scope.state.levels);
+            levels.pop();
+            viewParams = {levels: levels.join(',')};
+        }
+
+        $state.go(viewName, viewParams);
+    });
+
+    $scope.$on('brooklyn.components.dsl-editor.nest', (event, data) => {
+        if ($scope.state.levels.length === 0 ) {
+            $scope.state.rootDsl = data.dsl;
+            objectCache.put(`${data.definition.name}.dsl`, $scope.state.rootDsl);
+        } else {
+            updateRootDsl(data.dsl);
+        }
+
+        let levels = angular.copy($scope.state.levels);
+        levels.push(data.index);
+
+        $state.go(graphicalEditDslState.name, {levels: levels.join(',')});
+    });
+
+    $scope.backUrl = () => {
+        return $state.href($scope.state.view.name, $scope.state.view.params);
+    };
+
+    function updateRootDsl(dsl) {
+        let cachedLevels = angular.copy($scope.state.levels);
+        let cachedIndex = cachedLevels.pop();
+
+        if (angular.isDefined(cachedIndex)) {
+            cachedLevels
+                .reduce((dsl, level) => (dsl.params[level + 1]), $scope.state.rootDsl)
+                .params.splice((cachedIndex || 0) + 1, 1, dsl);
+        } else {
+            $scope.state.rootDsl = dsl;
+        }
+    }
+}
+
+export function dslParamLabelFilter($filter) {
+    return function(input) {
+        if (input instanceof Dsl) {
+            switch (input.kind) {
+                case KIND.ENTITY:
+                    return 'Entity';
+                case KIND.UTILITY:
+                case KIND.METHOD:
+                    return `Fn: ${input.name}`;
+                case KIND.NUMBER:
+                    return 'Number';
+                case KIND.PORT:
+                    return 'Port';
+                case KIND.STRING:
+                    return 'String';
+                case KIND.TARGET:
+                    let ret = [];
+                    if (input.next) {
+                        ret.push($filter('dslParamLabel')(input.next), '@');
+                    }
+                    ret.push(`Ref: ${input.params[0].name}`);
+                    return ret.join(' ');
+                default:
+                    return input.toString();
+            }
+        }
+        return input;
+    }
+}
+
+export const graphicalEditDslState = {
+    name: 'main.graphical.edit.dsl',
+    url: '/dsl/:for/:index?levels',
+    params: {
+        index: {
+            value: '',
+            squash: true
+        }
+    },
+    template: template,
+    controller: ['$scope', '$state', '$stateParams', 'objectCache', 'state', EditDslController],
+    controllerAs: 'vm',
+    resolve: {
+        state: ['$state', '$stateParams', 'entity', 'brSnackbar', 'objectCache', ($state, $stateParams, entity, brSnackbar, objectCache) => {
+            let definition = entity.miscData.get('config').find(config => config.name === $stateParams.for);
+            if (!definition) {
+                brSnackbar.create(`Config key ${$stateParams.for} does not exist.`);
+            }
+
+            let rootDsl = objectCache.get(`${definition.name}.dsl`);
+            if (!rootDsl) {
+                let config = entity.config.get(definition.name);
+                if ($stateParams.index) {
+                    config = config[$stateParams.index];
+                }
+                rootDsl = config instanceof Dsl
+                    ? config.clone()
+                    : new Dsl(KIND.STRING, '');
+                objectCache.put(`${definition.name}.dsl`, rootDsl);
+            }
+
+            let view = objectCache.get(`${definition.name}.view`);
+            if (!view) {
+                view = {
+                    name: $state.current.name,
+                    params: $state.params
+                };
+                objectCache.put(`${definition.name}.view`, view);
+            }
+
+            let state = {
+                entity: entity,
+                levels: $stateParams.levels ? $stateParams.levels.split(',').map(num => parseInt(num)) : [],
+                rootDsl: rootDsl,
+                view: view,
+                index: $stateParams.index
+            };
+
+            state.dsl = state.levels.length > 0
+                ? state.levels.reduce((dsl, level) => (dsl.params[parseInt(level) + 1]), state.rootDsl)
+                : state.rootDsl;
+
+            state.definition = Object.assign({}, definition);
+            if (state.levels.length > 0 || $stateParams.index) {
+                state.definition.type = '.*';
+            }
+
+            return state;
+        }]
+    }
+};

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.less b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.less
new file mode 100644
index 0000000..2294ac5
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.less
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+.spec-dsl {
+  .breadcrumb {
+    overflow-x: scroll;
+    white-space: nowrap;
+    padding: 1em 1em 0.5em 1em;
+    border-radius: 0;
+
+    &::-webkit-scrollbar {
+      height: 0.5em;
+    }
+    &::-webkit-scrollbar-track {
+      background: @breadcrumb-bg;
+    }
+    &::-webkit-scrollbar-thumb {
+      background: @gray-lighter;
+    }
+    &::-webkit-scrollbar-thumb:hover {
+      background: @gray-light;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.template.html b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.template.html
new file mode 100644
index 0000000..a17536e
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/dsl/edit.dsl.template.html
@@ -0,0 +1,34 @@
+<!--
+  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.
+-->
+<section class="spec-parent">
+    <a ui-sref="{{state.view.name}}({{state.view.params | json}})">{{(state.entity | entityName) || 'New application'}}</a>
+</section>
+
+<section class="spec-dsl">
+    <ol class="breadcrumb">
+        <li>Editing DSL for: <span ng-if="state.index"><code>{{state.index}}</code> in&nbsp;</span><code>{{state.definition.name}}</code></li>
+        <li ng-repeat="breadcrumb in breadcrumbs track by $index" ng-class="{'active': $last}">
+            <a ui-sref="{{breadcrumb.view.name}}({{breadcrumb.view.params | json}})">
+                formatString(<span ng-repeat="(paramIndex, param) in breadcrumb.dsl.params track by param._id" ><code ng-if="breadcrumb.index === paramIndex">{{param | dslParamLabel}}</code><span ng-if="breadcrumb.index !== paramIndex">{{param | dslParamLabel}}</span><span ng-if="!$last">,&nbsp;</span></span>)
+            </a>
+        </li>
+    </ol>
+
+    <dsl-editor definition="state.definition" entity="state.entity" dsl="state.dsl"></dsl-editor>
+</section>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/edit.controller.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/edit.controller.js b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/edit.controller.js
new file mode 100644
index 0000000..96aa299
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/edit.controller.js
@@ -0,0 +1,45 @@
+/*
+ * 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 {HIDE_INTERSTITIAL_SPINNER_EVENT} from 'brooklyn-ui-utils/interstitial-spinner/interstitial-spinner';
+
+export function GraphicalEditController($scope, entity) {
+    $scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
+    $scope.entity = entity;
+}
+
+export const graphicalEditState = {
+    abstract: true,
+    name: 'main.graphical.edit',
+    url: '/:entityId',
+    template: '<ui-view></ui-view>',
+    controller: ['$scope', 'entity', GraphicalEditController],
+    controllerAs: 'vm',
+    resolve: {
+        entity: ['$q', '$stateParams', 'blueprintService', function ($q, $stateParams, blueprintService) {
+            let defer = $q.defer();
+            let entity = blueprintService.find($stateParams.entityId);
+            if (entity) {
+                defer.resolve(entity);
+            } else {
+                defer.reject('No entity with id [' + $stateParams.entityId + '] found');
+            }
+            return defer.promise;
+        }]
+    }
+};

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/edit.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/edit.less b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/edit.less
new file mode 100644
index 0000000..7b547f7
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/edit.less
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+.container-edit {
+  width: 100vw;
+  height: 100vh;
+  background-color: #fff;
+  padding: 15px;
+
+  .container-edit-actions {
+    padding: 15px 0 15px 0;
+    border-top: 1px solid @gray-lighter;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/enricher/edit.enricher.controller.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/enricher/edit.enricher.controller.js b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/enricher/edit.enricher.controller.js
new file mode 100644
index 0000000..8739ef9
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/enricher/edit.enricher.controller.js
@@ -0,0 +1,36 @@
+/*
+ * 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 template from './edit.enricher.template.html';
+
+export function EditEnricherController($scope, enricher) {
+    $scope.enricher = enricher;
+}
+
+export const graphicalEditEnricherState = {
+    name: 'main.graphical.edit.enricher',
+    url: '/enricher/:enricherId',
+    template: template,
+    controller: ['$scope', 'enricher', EditEnricherController],
+    controllerAs: 'vm',
+    resolve: {
+        enricher: ['$stateParams', 'entity', function($stateParams, entity) {
+            return entity.enrichers.get($stateParams.enricherId);
+        }]
+    }
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/enricher/edit.enricher.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/enricher/edit.enricher.template.html b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/enricher/edit.enricher.template.html
new file mode 100644
index 0000000..7160dcc
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/enricher/edit.enricher.template.html
@@ -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.
+-->
+<section class="spec-parent">
+    <a ui-sref="main.graphical.edit.entity({entityId: enricher.parent._id})">{{(enricher.parent | entityName) || 'New application'}}</a>
+</section>
+
+<spec-editor model="enricher"></spec-editor>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/entity/edit.entity.controller.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/entity/edit.entity.controller.js b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/entity/edit.entity.controller.js
new file mode 100644
index 0000000..793e1a9
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/entity/edit.entity.controller.js
@@ -0,0 +1,25 @@
+/*
+ * 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 template from './edit.entity.template.html';
+
+export const graphicalEditEntityState = {
+    name: 'main.graphical.edit.entity',
+    url: '/entity',
+    template: template,
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/entity/edit.entity.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/entity/edit.entity.template.html b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/entity/edit.entity.template.html
new file mode 100644
index 0000000..1115c26
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/entity/edit.entity.template.html
@@ -0,0 +1,19 @@
+<!--
+  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.
+-->
+<spec-editor model="entity"></spec-editor>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/policy/edit.policy.controller.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/policy/edit.policy.controller.js b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/policy/edit.policy.controller.js
new file mode 100644
index 0000000..aaad7fe
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/policy/edit.policy.controller.js
@@ -0,0 +1,36 @@
+/*
+ * 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 template from './edit.policy.template.html';
+
+export function EditPolicyController($scope, policy) {
+    $scope.policy = policy;
+}
+
+export const graphicalEditPolicyState = {
+    name: 'main.graphical.edit.policy',
+    url: '/policy/:policyId',
+    template: template,
+    controller: ['$scope', 'policy', EditPolicyController],
+    controllerAs: 'vm',
+    resolve: {
+        policy: ['$stateParams', 'entity', function($stateParams, entity) {
+            return entity.policies.get($stateParams.policyId);
+        }]
+    }
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/policy/edit.policy.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/policy/edit.policy.template.html b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/policy/edit.policy.template.html
new file mode 100644
index 0000000..d7d163a
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/policy/edit.policy.template.html
@@ -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.
+-->
+<section class="spec-parent">
+    <a ui-sref="main.graphical.edit.entity({entityId: policy.parent._id})">{{(policy.parent | entityName) || 'New application'}}</a>
+</section>
+
+<spec-editor model="policy"></spec-editor>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/spec/edit.spec.controller.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/spec/edit.spec.controller.js b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/spec/edit.spec.controller.js
new file mode 100644
index 0000000..fc9ef02
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/spec/edit.spec.controller.js
@@ -0,0 +1,36 @@
+/*
+ * 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 template from './edit.spec.template.html';
+
+export function EditSpecController($scope, spec) {
+    $scope.spec = spec;
+}
+
+export const graphicalEditSpecState = {
+    name: 'main.graphical.edit.spec',
+    url: '/spec/:specId',
+    template: template,
+    controller: ['$scope', 'spec', EditSpecController],
+    controllerAs: 'vm',
+    resolve: {
+        spec: ['$stateParams', 'entity', function($stateParams, entity) {
+            return entity.getClusterMemberspecEntity((config, entity)=>(entity._id === $stateParams.specId));
+        }]
+    }
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/spec/edit.spec.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/spec/edit.spec.template.html b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/spec/edit.spec.template.html
new file mode 100644
index 0000000..d98981a
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/edit/spec/edit.spec.template.html
@@ -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.
+-->
+<section class="spec-parent">
+    <a ui-sref="main.graphical.edit.entity({entityId: spec.parent._id})">{{(spec.parent | entityName) || 'New application'}}</a>
+</section>
+
+<spec-editor model="spec"></spec-editor>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html b/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html
new file mode 100644
index 0000000..b87c38c
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html
@@ -0,0 +1,52 @@
+<!--
+  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.
+-->
+<designer></designer>
+
+<div class="left-panel">
+    <ui-view>
+        <br-collapsible state="true">
+            <heading>
+                Entities
+            </heading>
+            <catalog-selector family="EntityFamily.ENTITY" on-select="onTypeSelected(item)" items-per-page="catalogItemsPerPage"></catalog-selector>
+        </br-collapsible>
+
+        <br-collapsible>
+            <heading>
+                Policies
+            </heading>
+            <catalog-selector family="EntityFamily.POLICY" on-select="onTypeSelected(item)" items-per-page="catalogItemsPerPage"></catalog-selector>
+        </br-collapsible>
+
+        <br-collapsible>
+            <heading>
+                Enrichers
+            </heading>
+            <catalog-selector family="EntityFamily.ENRICHER" on-select="onTypeSelected(item)" items-per-page="catalogItemsPerPage"></catalog-selector>
+        </br-collapsible>
+
+        <br-collapsible>
+            <heading>
+                Locations
+            </heading>
+            <catalog-selector family="EntityFamily.LOCATION" on-select="onTypeSelected(item)" items-per-page="catalogItemsPerPage"></catalog-selector>
+        </br-collapsible>
+    </ui-view>
+</div>
+

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js b/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js
new file mode 100644
index 0000000..00e8180
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js
@@ -0,0 +1,68 @@
+/*
+ * 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 {graphicalEditEntityState} from './edit/entity/edit.entity.controller';
+import {graphicalEditPolicyState} from './edit/policy/edit.policy.controller';
+import {graphicalEditEnricherState} from './edit/enricher/edit.enricher.controller';
+import {Entity, EntityFamily} from '../../../components/util/model/entity.model';
+import template from './graphical.state.html';
+
+export const graphicalState = {
+    name: 'main.graphical',
+    url: 'graphical',
+    template: template,
+    controller: ['$scope', '$state', 'blueprintService', graphicalController],
+    data: {
+        label: 'Graphical Designer'
+    }
+};
+
+function graphicalController($scope, $state, blueprintService) {
+    $scope.EntityFamily = EntityFamily;
+    $scope.catalogItemsPerPage = 24;
+
+    $scope.onTypeSelected = (selectedType)=> {
+        let rootEntity = blueprintService.get();
+
+        if (selectedType.supertypes.includes(EntityFamily.ENTITY.superType)) {
+            let newEntity = blueprintService.populateEntityFromApi(new Entity(), selectedType);
+            rootEntity.addChild(newEntity);
+            blueprintService.refreshEntityMetadata(newEntity, EntityFamily.ENTITY).then(() => {
+                $state.go(graphicalEditEntityState, {entityId: newEntity._id});
+            })
+        }
+        else if (selectedType.supertypes.includes(EntityFamily.POLICY.superType)) {
+            let newPolicy = blueprintService.populateEntityFromApi(new Entity(), selectedType);
+            rootEntity.addPolicy(newPolicy);
+            blueprintService.refreshEntityMetadata(newPolicy, EntityFamily.POLICY).then(() => {
+                $state.go(graphicalEditPolicyState, {entityId: rootEntity._id, policyId: newPolicy._id});
+            });
+        }
+        else if (selectedType.supertypes.includes(EntityFamily.ENRICHER.superType)) {
+            let newEnricher = blueprintService.populateEntityFromApi(new Entity(), selectedType);
+            rootEntity.addEnricher(newEnricher);
+            blueprintService.refreshEntityMetadata(newEnricher, EntityFamily.ENRICHER).then(() => {
+                $state.go(graphicalEditEnricherState, {entityId: rootEntity._id, enricherId: newEnricher._id});
+            });
+        }
+        else if (selectedType.supertypes.includes(EntityFamily.LOCATION.superType)) {
+            blueprintService.populateLocationFromApi(rootEntity, selectedType);
+            $state.go(graphicalEditEntityState, {entityId: rootEntity._id});
+        }
+    };
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.less b/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.less
new file mode 100644
index 0000000..0288439
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.less
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+.left-panel {
+  display: block;
+  position: absolute;
+  left: 20px;
+  top: 20px;
+  width: 440px;
+  padding-top: 6px;
+  z-index: 200;
+  overflow-y: scroll;
+  height: calc(~"100vh - 95px");
+  box-shadow: 0 0 rgba(0, 0, 0, 0.2), 0 1px 18px 1px rgba(0, 0, 0, 0.11);
+  background-color: #fff;
+  border: 1px solid @navbar-default-border;
+
+  .container-fluid {
+    padding: 0;
+  }
+  
+  .spec-parent {
+    margin-top: -6px;
+    a {
+      background-color: @gray-lighter;
+      padding: 15px 45px 15px 15px;
+      position: relative;
+      color: @gray-light;
+      font-style: italic;
+      display: block;
+      transition: color 0.3s ease;
+      .text-overflow();
+
+      &:after{
+        content: '\f112';
+        font-family: FontAwesome;
+        position: absolute;
+        right: 15px;
+        color: @gray-light;
+        transition: color 0.3s ease;
+      }
+      &:hover {
+        color: @gray;
+        &:after {
+          color: @gray;
+        }
+      }
+    }
+  }
+
+  // panel classes are added by use of accordion toggle in palette
+  // we also use these classes for spec editor and others
+  // (would be nice to refactor so we use our own classes instead)
+  .panel-group {
+    margin-bottom: 0;
+    .panel {
+      border-bottom: 1px solid @gray-lighter;
+      margin: 0 12px;
+      border-radius: 0;
+      box-shadow: none;
+      .panel-heading {
+        padding: 15px 3px 15px 3px;
+      }
+      .panel-title {
+        font-size: 16px;
+        font-weight: 400;
+      }
+      .panel-body {
+        padding: 0;
+        border: none !important;
+        margin-bottom: 24px;
+      }
+    }
+    .panel-title > a,
+    .panel-title > a:hover,
+    .panel-title > a:focus:hover {
+      background: none;
+      color: @gray-light;
+
+      .collapsible-action {
+        padding-left: 2px;
+        padding-right: 6px;
+        margin-top: 1px;
+      }
+      .collapsible-action, a.collapsible-action {
+        color: @gray-light;
+      }
+      .collapsible-action:active, a.collapsible-action:active,
+      .collapsible-action:hover, a.collapsible-action:hover,
+      .collapsible-action:focus, a.collapsible-action:focus {
+        color: @gray;
+      }
+    }
+  }
+
+  br-collapsible catalog-selector {
+    margin-top: 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/main.controller.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/main.controller.js b/new/ui-modules/blueprint-composer/app/views/main/main.controller.js
new file mode 100644
index 0000000..401b8f6
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/main.controller.js
@@ -0,0 +1,270 @@
+/*
+ * 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 {HIDE_INTERSTITIAL_SPINNER_EVENT} from 'brooklyn-ui-utils/interstitial-spinner/interstitial-spinner';
+import template from './main.template.html';
+import {EntityFamily} from '../../components/util/model/entity.model';
+import {graphicalState} from './graphical/graphical.state';
+import {yamlState} from './yaml/yaml.state';
+import {graphicalEditEntityState} from './graphical/edit/entity/edit.entity.controller';
+import {graphicalEditPolicyState} from './graphical/edit/policy/edit.policy.controller';
+import {graphicalEditEnricherState} from './graphical/edit/enricher/edit.enricher.controller';
+import {graphicalEditSpecState} from './graphical/edit/spec/edit.spec.controller';
+import bottomSheetTemplate from './bottom-sheet.template.html';
+import {ISSUE_LEVEL} from '../../components/util/model/issue.model';
+
+const layers = [
+    {
+        id: 'locations',
+        label: 'Locations',
+        selector: '.node-location',
+        active: true
+    },
+    {
+        id: 'adjuncts',
+        label: 'Adjuncts',
+        selector: '.node-adjuncts',
+        active: true
+    },
+    {
+        id: 'memberspecs',
+        label: 'Member specs',
+        selector: '.spec-node-group',
+        active: true
+    },
+    {
+        id: 'relationships',
+        label: 'Relationships',
+        selector: '.relation-group',
+        active: true
+    }
+];
+const layerCacheKey = 'blueprint-composer.layers';
+
+export function MainController($scope, $log, $state, $stateParams, brBrandInfo, blueprintService, actionService, catalogApi, applicationApi, brSnackbar, brBottomSheet, edit, yaml) {
+    $scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
+    let vm = this;
+
+    if (edit && yaml) {
+        throw 'Cannot supply both YAML source and a catalog item to edit';
+    }
+
+    vm.mode = $state.current;
+    $scope.$on('$stateChangeSuccess', (event, toState)=>{
+        vm.mode = toState;
+    });
+
+    vm.layers = localStorage && localStorage.getItem(layerCacheKey) !== null ?
+        JSON.parse(localStorage.getItem(layerCacheKey)) :
+        layers;
+    $scope.$watch('vm.layers', ()=> {
+        vm.layers.forEach(layer => {
+            document.querySelectorAll(layer.selector).forEach(node => {
+                angular.element(node).css('display', layer.active ? 'block' : 'none');
+            });
+        });
+        if (localStorage) {
+            try {
+                localStorage.setItem(layerCacheKey, JSON.stringify(vm.layers));
+            } catch (ex) {
+                $log.error('Cannot save layers preferences: ' + ex.message);
+            }
+        }
+    }, true);
+
+    vm.parseError = false;
+    $scope.$on('yaml.lint', (event, error)=>{
+        $scope.$applyAsync(()=> {
+            vm.parseError = error;
+        });
+    });
+
+    $scope.$on('blueprint.reset', () => {
+        vm.saveToCatalogConfig = {};
+        blueprintService.reset();
+        $state.go($state.includes(yamlState) ? yamlState : graphicalState, {}, {inherit: false, reload: true});
+    });
+    $scope.$on('blueprint.deploy', () => {
+        vm.deployApplication();
+    });
+
+    vm.saveToCatalogConfig = {};
+    if (edit) {
+        vm.saveToCatalogConfig = Object.assign(vm.saveToCatalogConfig, {
+            bundle: edit.bundle.symbolicName.replace(/^catalog-bom-/, ''),
+            version: edit.type.version,
+            symbolicName: edit.type.symbolicName,
+            itemType: edit.type.template ? 'template' : 'entity',
+            name: edit.type.displayName,
+            description: edit.type.description,
+            iconUrl: edit.type.iconUrl,
+            versions: edit.versions,
+        });
+        yaml = edit.type.plan.data;
+    }
+
+    if (yaml) {
+        blueprintService.setFromYaml(yaml);
+    } else {
+        blueprintService.reset();
+    }
+
+    vm.deployApplication = function () {
+        if (blueprintService.hasIssues()) {
+            displayIssues();
+            return;
+        }
+
+        deployApplication();
+    };
+
+    vm.isGraphicalMode = () => {
+        return $state.includes(graphicalState.name);
+    };
+
+    vm.getAllActions = () => {
+        return actionService.getActions();
+    }
+
+    function displayIssues() {
+        let isOnlyWarnings = blueprintService.getIssues().length === blueprintService.getIssues().filter(issue => issue.level === ISSUE_LEVEL.WARN).length;
+        let message = '';
+        let options = {};
+        if (isOnlyWarnings) {
+            message = 'Blueprint has some warnings, deployment might fail. Do you wish to proceed anyway?';
+            options = {
+                label: 'Deploy',
+                callback: deployApplication
+            }
+        } else {
+            message = 'Blueprint is not valid: there ' + (
+                blueprintService.getIssues().filter(issue => issue.level === ISSUE_LEVEL.ERROR).length > 1 ?
+                    'are ' + blueprintService.getIssues().filter(issue => issue.level === ISSUE_LEVEL.ERROR).length + ' problems' :
+                    'is 1 problem'
+            ) + ' to fix';
+        }
+
+        brSnackbar.create(message, options);
+
+        let firstEntityWithIssue = blueprintService.getIssues().filter(issue => {
+            if (isOnlyWarnings) {
+                return issue.level === ISSUE_LEVEL.WARN;
+            } else {
+                return issue.level === ISSUE_LEVEL.ERROR;
+            }
+        })[0].entity;
+        switch (firstEntityWithIssue.family) {
+            case EntityFamily.ENTITY:
+                $state.go(graphicalEditEntityState, {entityId: firstEntityWithIssue._id});
+                return;
+            case EntityFamily.POLICY:
+                $state.go(graphicalEditPolicyState, {entityId: firstEntityWithIssue.parent._id, policyId: firstEntityWithIssue._id});
+                return;
+            case EntityFamily.ENRICHER:
+                $state.go(graphicalEditEnricherState, {entityId: firstEntityWithIssue.parent._id, enricherId: firstEntityWithIssue._id});
+                return;
+            case EntityFamily.SPEC:
+                $state.go(graphicalEditSpecState, {entityId: firstEntityWithIssue.parent._id, specId: firstEntityWithIssue._id});
+                return;
+        }
+    }
+
+    function deployApplication() {
+        let bottomSheetOpts = {
+            controller: ['$log', 'brBrandInfo', 'brBottomSheetInstance', 'blueprintService', 'applicationApi', bottomSheetController],
+            controllerAs: 'ctrl',
+            template: bottomSheetTemplate
+        };
+
+        brBottomSheet.open(bottomSheetOpts);
+
+        function bottomSheetController($log, brBrandInfo, brBottomSheetInstance, blueprintService, applicationApi) {
+            this.loading = true;
+            this.vendors = brBrandInfo.getVendorPackages();
+             
+            this.close = ()=> {
+                brBottomSheetInstance.close('Closing deployment error reporting');
+            };
+            this.investigate = ()=> {
+                brBottomSheetInstance.updateMode('inset');
+            };
+            this.onClipboardSuccess = (e)=> {
+                angular.element(e.trigger).triggerHandler('copied');
+                e.clearSelection();
+            };
+            this.onClipboardError = (e)=> {
+                let message = '';
+                let actionKey = e.action === 'cut' ? 'X' : 'C';
+                if(/iPhone|iPad/i.test(navigator.userAgent)) {
+                    message = 'No support :(';
+                }
+                else if(/Mac/i.test(navigator.userAgent)) {
+                    message = 'Press ⌘-' + actionKey + ' to ' + e.action;
+                }
+                else {
+                    message = 'Press Ctrl-' + actionKey + ' to ' + e.action;
+                }
+                brSnackbar.create(message);
+            };
+
+            let applicationToDeploy = blueprintService.getAsYaml();
+            applicationApi.createApplication(applicationToDeploy).then((success)=> {
+                $log.info('Application deployment ... success', success);
+                window.location.href = brBrandInfo.getAppDeployedUrl(success.entityId, success.entityId);
+            }).catch((error)=> {
+                $log.error('Application deployment ... failed', error);
+                this.error = {
+                    title: 'Application deployment failed',
+                    message: error.error.message,
+                    details: error.error.details
+                };
+            }).finally(()=> {
+                this.loading = false;
+            });
+        }
+    }
+}
+
+export const mainState = {
+    name: 'main',
+    url: '/?bundleSymbolicName&bundleVersion&typeSymbolicName&typeVersion&yaml',
+    abstract: true,
+    template: template,
+    controller: ['$scope', '$log', '$state', '$stateParams', 'brBrandInfo', 'blueprintService', 'actionService', 'catalogApi', 'applicationApi', 'brSnackbar', 'brBottomSheet', 'edit', 'yaml', MainController],
+    controllerAs: 'vm',
+    resolve: {
+        edit: ['$stateParams', '$q', 'paletteApi', ($stateParams, $q, paletteApi) => {
+            let deferred = $q.defer();
+            if (!($stateParams.bundleSymbolicName && $stateParams.bundleVersion && $stateParams.typeSymbolicName && $stateParams.typeVersion)) {
+                deferred.resolve(null);
+            } else if ($stateParams.bundleSymbolicName && $stateParams.bundleVersion && $stateParams.typeSymbolicName && $stateParams.typeVersion) {
+                $q.all([
+                    paletteApi.getBundle($stateParams.bundleSymbolicName, $stateParams.bundleVersion),
+                    paletteApi.getBundleType($stateParams.bundleSymbolicName, $stateParams.bundleVersion, $stateParams.typeSymbolicName, $stateParams.typeVersion),
+                    paletteApi.getTypeVersions($stateParams.typeSymbolicName)
+                ]).then(responses => {
+                    deferred.resolve({bundle: responses[0], type: responses[1], versions: responses[2].map(item => item.version)});
+                }).catch(response => deferred.reject(response.error.message));
+            } else {
+                deferred.reject('Both bundle and type information must be supplied');
+            }
+            return deferred.promise;
+        }],
+        yaml: ['$stateParams', $stateParams => $stateParams.yaml]
+    }
+};

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/main.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/main.less b/new/ui-modules/blueprint-composer/app/views/main/main.less
new file mode 100644
index 0000000..fdba549
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/main.less
@@ -0,0 +1,69 @@
+/*
+ * 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 "./bottom-sheet.less";
+
+.page-main-area {
+  position: relative;
+
+  .modes {
+    position: absolute;
+    top: 20px;
+    right: 20px;
+    z-index: 100;
+    .btn-primary {
+      border: 1px solid transparent;
+    }
+    a[disabled] {
+      pointer-events: none;
+    }
+  }
+  .layer a {
+    cursor: pointer;
+    color: @gray-lighter;
+    transition: color 0.3s ease;
+    &:hover {
+      color: @gray-light;
+    }
+  }
+  .layer.active a {
+    color: @brand-primary;
+    &:hover {
+      color: @brand-primary;
+    }
+  }
+  .tab-content {
+    width: 100%;
+    background: transparent;
+    border: none;
+    margin: 0;
+
+    .tab-pane {
+      padding: 0;
+    }
+  }
+  .actions {
+    position: absolute;
+    bottom: 20px;
+    right: 20px;
+    z-index: 100;
+    custom-action {
+      margin-left: 5px;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/main.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/main.template.html b/new/ui-modules/blueprint-composer/app/views/main/main.template.html
new file mode 100644
index 0000000..d9c894e
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/main.template.html
@@ -0,0 +1,54 @@
+<!--
+  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="btn-group modes" role="group" aria-label="...">
+    <div class="btn-group" uib-dropdown keyboard-nav auto-close="outsideClick">
+        <button type="button" class="btn btn-default disabled">
+            {{vm.mode.data.label}}
+        </button>
+        <button type="button" class="btn btn-success dropdown-toggle" ng-disabled="!vm.isGraphicalMode()" uib-dropdown-toggle aria-haspopup="true" aria-expanded="false">
+            <span class="caret"></span>
+            <span class="sr-only">Toggle layers selection</span>
+        </button>
+        <ul class="dropdown-menu dropdown-menu-right" uib-dropdown-menu role="menu">
+            <li ng-if="vm.isGraphicalMode()" class="dropdown-header">Layers</li>
+            <li role="menuitem" class="layer"
+                ng-if="vm.isGraphicalMode()"
+                ng-repeat="layer in vm.layers track by layer.id"
+                ng-class="{'active': layer.active}">
+                <a ng-click="layer.active = !layer.active"><i class="fa fa-fw fa-circle"></i> {{layer.label}}</a>
+            </li>
+        </ul>
+    </div>
+    <a ui-sref="main.graphical" ng-if="!vm.isGraphicalMode()" class="btn btn-primary" ng-disabled="vm.parseError">
+        <i class="fa fa-fw fa-exchange"></i>
+        <span class="sr-only">Switch to Graphical Designer</span>
+    </a>
+    <a ui-sref="main.yaml" ng-if="vm.isGraphicalMode()" class="btn btn-primary">
+        <i class="fa fa-fw fa-exchange"></i>
+        <span class="sr-only">Switch to YAML Editor</span>
+    </a>
+</div>
+
+<ui-view></ui-view>
+
+
+<div class="actions">
+    <custom-action ng-repeat="(id, action) in vm.getAllActions() track by id" action="action"></custom-action>
+    <blueprint-data-manager></blueprint-data-manager>
+</div>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/yaml/yaml.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/yaml/yaml.less b/new/ui-modules/blueprint-composer/app/views/main/yaml/yaml.less
new file mode 100644
index 0000000..8a78b17
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/yaml/yaml.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.
+ */
+br-yaml-editor {
+  .CodeMirror {
+    height: calc(~"100vh - 55px");
+  }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/views/main/yaml/yaml.state.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/views/main/yaml/yaml.state.js b/new/ui-modules/blueprint-composer/app/views/main/yaml/yaml.state.js
new file mode 100644
index 0000000..e6cbea1
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/views/main/yaml/yaml.state.js
@@ -0,0 +1,62 @@
+/*
+ * 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 CodeMirror from 'codemirror';
+import {YAMLException} from 'js-yaml';
+
+export const yamlState = {
+    name: 'main.yaml',
+    url: 'yaml',
+    template: '<br-yaml-editor value="vm.yaml" type="blueprint"></br-yaml-editor>',
+    controller: ['$scope', '$rootScope', '$timeout', 'blueprintService', 'brSnackbar', yamlStateController],
+    controllerAs: 'vm',
+    data: {
+        label: 'YAML Editor'
+    }
+};
+
+function yamlStateController($scope, $rootScope, $timeout, blueprintService, brSnackbar) {
+    let vm = this;
+
+    try {
+        vm.yaml = blueprintService.getAsYaml();
+    } catch (ex) {
+        brSnackbar.create(`Cannot load blueprint: ${ex.message}`);
+        vm.yaml = '';
+    }
+
+    if (!CodeMirror.lint.hasOwnProperty('yaml-composer')) {
+        CodeMirror.registerGlobalHelper('lint', 'yaml-composer', mode => mode.name === 'yaml', (text, options, cm) => {
+            let issues = [];
+
+            try {
+                blueprintService.setFromYaml(cm.getValue(), true);
+            } catch (err) {
+                if (!(err instanceof YAMLException)) {
+                    issues.push({
+                        from: CodeMirror.Pos(0, 0),
+                        to: CodeMirror.Pos(0, 0),
+                        message: err.message
+                    });
+                }
+            }
+
+            return issues;
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/config/build/webpack.config.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/config/build/webpack.config.js b/new/ui-modules/blueprint-composer/config/build/webpack.config.js
new file mode 100644
index 0000000..a050fa9
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/config/build/webpack.config.js
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+eval(require('fs').readFileSync('../shared/config/build/webpack.config.js')+'');

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/config/server.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/config/server.js b/new/ui-modules/blueprint-composer/config/server.js
new file mode 100644
index 0000000..dfba7dc
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/config/server.js
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+eval(require('fs').readFileSync('../shared/config/server.js')+'');