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:37 UTC

[20/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/app-inspector/app/views/main/main.controller.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/app/views/main/main.controller.js b/new/ui-modules/app-inspector/app/views/main/main.controller.js
new file mode 100644
index 0000000..07280f8
--- /dev/null
+++ b/new/ui-modules/app-inspector/app/views/main/main.controller.js
@@ -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 {HIDE_INTERSTITIAL_SPINNER_EVENT} from 'brooklyn-ui-utils/interstitial-spinner/interstitial-spinner';
+import template from "./main.template.html";
+
+export const mainState = {
+    name: 'main',
+    url: '/',
+    template: template,
+    controller: ['$scope', '$q', 'brWebNotifications', mainController],
+    controllerAs: 'ctrl'
+};
+
+export function mainController($scope, $q, brWebNotifications) {
+    $scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
+
+    let ctrl = this;
+
+    brWebNotifications.supported.then(() => {
+        ctrl.isNotificationsSupported = true;
+    }).catch(() => {
+        ctrl.isNotificationsSupported = false;
+    });
+
+    brWebNotifications.isEnabled().then(() => {
+        ctrl.isNotificationsEnabled = true;
+    }).catch(() => {
+        ctrl.isNotificationsEnabled = false;
+    });
+
+    brWebNotifications.getPermission().then(permission => {
+        ctrl.isNotificationsBlocked = permission === 'denied';
+    });
+
+    ctrl.toggleNotifications = () => {
+        brWebNotifications.isEnabled().then(() => {
+            return brWebNotifications.setEnable(false);
+        }).then(enable => {
+            ctrl.isNotificationsEnabled = enable;
+        }).catch(() => {
+            brWebNotifications.requestPermission().then(permission => {
+                ctrl.isNotificationsBlocked = permission === 'denied';
+
+                if (ctrl.isNotificationsBlocked) {
+                    return $q.reject();
+                }
+                return brWebNotifications.setEnable(permission === 'granted');
+            }).then(enable => {
+                ctrl.isNotificationsEnabled = enable;
+            });
+        });
+    };
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/app/views/main/main.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/app/views/main/main.less b/new/ui-modules/app-inspector/app/views/main/main.less
new file mode 100644
index 0000000..d14fed7
--- /dev/null
+++ b/new/ui-modules/app-inspector/app/views/main/main.less
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+.entity-tree {
+
+  .entity-tree-header {
+    display: flex;
+
+    .entity-tree-title {
+      flex-grow: 1;
+    }
+    .entity-tree-action {
+      flex-shrink: 1;
+      vertical-align: middle;
+      margin-left: 0.5em;
+      display: inline-table;
+    }
+    .notifications {
+      color: @gray-light;
+      transform: rotate(0);
+      transition: 0.3s all ease;
+      &.active {
+        color: @brand-primary;
+        transform: rotate(30deg);
+      }
+    }
+  }
+
+  //.entity-tree-heading {
+  //  .clearfix()
+  //}
+
+  .entity-tree-title {
+    font-size: 1.8rem;
+    font-weight: bold;
+    color: @brand-primary;
+  }
+
+  .entity-tree-action-bar {
+    margin-right: -0.5rem;
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/app/views/main/main.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/app/views/main/main.template.html b/new/ui-modules/app-inspector/app/views/main/main.template.html
new file mode 100644
index 0000000..0911647
--- /dev/null
+++ b/new/ui-modules/app-inspector/app/views/main/main.template.html
@@ -0,0 +1,59 @@
+<!--
+  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="container-fluid">
+    <div class="row">
+        <div class="col-md-4">
+            <br-card>
+                <br-card-content class="entity-tree">
+                    <div class="entity-tree-header">
+                        <h2 class="entity-tree-title">Applications</h2>
+
+                        <button class="btn btn-link entity-tree-action"
+                                ng-class="{'btn-sm': !ctrl.isNotificationsBlocked, 'btn-xs': ctrl.isNotificationsBlocked}"
+                                ng-if="ctrl.isNotificationsSupported"
+                                ng-disabled="ctrl.isNotificationsBlocked"
+                                ng-click="ctrl.toggleNotifications()">
+                            <i class="fa fa-bell notifications" ng-if="!ctrl.isNotificationsBlocked"
+                                ng-class="{'active': ctrl.isNotificationsEnabled}"></i>
+                            <span class="fa-stack" ng-if="ctrl.isNotificationsBlocked"
+                                  uib-tooltip="Notifications are currently blocked. You can allow them in your browser settings"
+                                  tooltip-placement="left">
+                                <i class="fa fa-bell fa-stack-1x"></i>
+                                <i class="fa fa-ban fa-stack-2x text-danger"></i>
+                            </span>
+                        </button>
+
+                        <a href="/brooklyn-ui-blueprint-composer" class="btn btn-sm btn-default entity-tree-action">
+                            <i class="fa fa-plus"></i>
+                        </a>
+                    </div>
+
+                    <entity-tree></entity-tree>
+                </br-card-content>
+            </br-card>
+
+        </div>
+
+        <div class="col-md-8">
+            <ui-view>
+                <h3 class="text-center text-muted">Select an entity on the left</h3>
+            </ui-view>
+        </div>
+    </div>
+</div>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/config/build/webpack.config.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/config/build/webpack.config.js b/new/ui-modules/app-inspector/config/build/webpack.config.js
new file mode 100644
index 0000000..a050fa9
--- /dev/null
+++ b/new/ui-modules/app-inspector/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/app-inspector/config/server.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/config/server.js b/new/ui-modules/app-inspector/config/server.js
new file mode 100644
index 0000000..dfba7dc
--- /dev/null
+++ b/new/ui-modules/app-inspector/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')+'');

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/config/test/karma.config.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/config/test/karma.config.js b/new/ui-modules/app-inspector/config/test/karma.config.js
new file mode 100644
index 0000000..6fc6558
--- /dev/null
+++ b/new/ui-modules/app-inspector/config/test/karma.config.js
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+const PRODUCTION_BUILD = process.env.NODE_ENV === 'production';
+const webpackConfig = require('./../build/webpack.config.js');
+
+module.exports = function (config) {
+    const conf = {
+        basePath: '',
+        frameworks: ['jasmine'],
+        files: [
+            {pattern: './karma.test.shim.js', watched: false}
+        ],
+        preprocessors: {
+            './karma.test.shim.js': ['webpack', 'sourcemap']
+        },
+        webpack: Object.assign({}, webpackConfig, {entry: undefined, plugins: undefined}),
+        webpackMiddleware: {
+            stats: 'errors-only'
+        },
+        webpackServer: {
+            noInfo: true
+        },
+        exclude: [],
+        colors: !PRODUCTION_BUILD,
+        logLevel: config.LOG_INFO,
+        autoWatch: !PRODUCTION_BUILD,
+        singleRun: PRODUCTION_BUILD,
+        reporters: ['progress'],
+        browsers: ['PhantomJS'],
+        plugins: [
+            require('karma-webpack'),
+            require('karma-sourcemap-loader'),
+            'karma-jasmine',
+            'karma-phantomjs-launcher'
+        ],
+        failOnEmptyTestSuite: false
+    };
+    config.set(conf);
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/config/test/karma.test.shim.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/config/test/karma.test.shim.js b/new/ui-modules/app-inspector/config/test/karma.test.shim.js
new file mode 100644
index 0000000..3d926c0
--- /dev/null
+++ b/new/ui-modules/app-inspector/config/test/karma.test.shim.js
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+Error.stackTraceLimit = Infinity;
+
+require('angular');
+require('angular-mocks');
+require('babel-polyfill');
+
+var testsContext = require.context('./../../app', true, /\.spec\.js$/);
+testsContext.keys().forEach(testsContext);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/config/test/protractor.config.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/config/test/protractor.config.js b/new/ui-modules/app-inspector/config/test/protractor.config.js
new file mode 100644
index 0000000..5274c81
--- /dev/null
+++ b/new/ui-modules/app-inspector/config/test/protractor.config.js
@@ -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.
+ */
+exports.config = {
+    framework: 'jasmine2',
+    seleniumAddress: 'http://localhost:4444/wd/hub',
+    specs: ['../../app/**/*.e2e-spec.js'],
+    baseUrl: 'http://admin:password@localhost:8080',
+    capabilities: {
+        'browserName': 'chrome',
+        'chromeOptions': {
+            'args': ['--window-size=1440,1000']
+        }
+    },
+    onPrepare: function () {
+        require('babel-register');
+    },
+    jasmineNodeOpts: {
+        // If true, print colors to the terminal.
+        showColors: true,
+        // Default time to wait in ms before a test fails.
+        defaultTimeoutInterval: 30000
+    }
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/package.json
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/package.json b/new/ui-modules/app-inspector/package.json
new file mode 100644
index 0000000..fd0cfa2
--- /dev/null
+++ b/new/ui-modules/app-inspector/package.json
@@ -0,0 +1,109 @@
+{
+  "__license__": [
+    "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."
+  ],
+  "name": "brooklyn-ui-app-inspector",
+  "versionBuildNote": "BROOKLYN_VERSION_BELOW this exists so scripts update this automatically",
+  "version": "1.0.0-SNAPSHOT",
+  "description": "Brooklyn UI :: Modules - App inspector",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/apache/brooklyn-ui.git"
+  },
+  "author": "Apache Software Foundation",
+  "license": "Apache-2.0",
+  "scripts": {
+    "build": "rimraf dist && webpack --config ./config/build/webpack.config.js --bail",
+    "start": "node ./config/server.js",
+    "test": "karma start ./config/test/karma.config.js",
+    "lint": "./node_modules/.bin/eslint app/**/*.js",
+    "e2e": "protractor ./config/test/protractor.config.js"
+  },
+  "engines": {
+    "node": ">=8.0.0",
+    "npm": ">=5.0.0"
+  },
+  "brooklyn": {
+    "appname": "App inspector"
+  },
+  "dependencies": {
+    "brooklyn-ui-utils": "file:../utils",
+    "angular": "^1.6.1",
+    "angular-animate": "^1.6.1",
+    "angular-cookies": "^1.6.1",
+    "angular-resource": "^1.6.1",
+    "angular-sanitize": "^1.6.1",
+    "angular-ui-bootstrap": "^2.5.0",
+    "angular-ui-router": "^0.3.1",
+    "angular-xeditable": "^0.4.0",
+    "bootstrap": "^3.3.7",
+    "d3": "4.10.0",
+    "es6-promise": "^4.0.5",
+    "font-awesome": "^4.6.3",
+    "isomorphic-fetch": "^2.2.1",
+    "lodash": "^4.16.4",
+    "moment": "^2.15.1",
+    "ngclipboard": "^1.1.1",
+    "normalizr": "^2.2.1"
+  },
+  "devDependencies": {
+    "angular-elastic": "^2.5.1",
+    "angular-mocks": "^1.6.1",
+    "autoprefixer": "^6.5.1",
+    "babel-core": "^6.17.0",
+    "babel-loader": "^6.2.5",
+    "babel-polyfill": "^6.20.0",
+    "babel-preset-env": "^1.6.0",
+    "babel-preset-stage-0": "^6.16.0",
+    "css-loader": "^0.25.0",
+    "dotenv": "^2.0.0",
+    "ejs": "^2.5.2",
+    "ejs-html-loader": "^2.0.2",
+    "ejs-loader": "^0.3.0",
+    "eslint": "^3.8.1",
+    "exports-loader": "^0.6.3",
+    "expose-loader": "^0.7.1",
+    "express": "^4.14.0",
+    "extract-text-webpack-plugin": "^1.0.1",
+    "file-loader": "^0.9.0",
+    "html-loader": "^0.4.4",
+    "html-webpack-plugin": "^2.22.0",
+    "image-webpack-loader": "^3.0.0",
+    "imports-loader": "^0.6.5",
+    "jasmine-core": "^2.5.2",
+    "karma": "^1.3.0",
+    "karma-jasmine": "^1.0.2",
+    "karma-phantomjs-launcher": "^1.0.2",
+    "karma-sourcemap-loader": "^0.3.7",
+    "karma-webpack": "^1.8.0",
+    "less": "^2.7.1",
+    "less-loader": "^2.2.3",
+    "less-vars-to-js": "^1.1.2",
+    "phantomjs-prebuilt": "^2.1.13",
+    "postcss-loader": "^1.0.0",
+    "properties-reader": "0.0.15",
+    "proxy-middleware": "^0.15.0",
+    "raw-loader": "^0.5.1",
+    "resolve-url-loader": "^1.6.0",
+    "rimraf": "^2.5.4",
+    "style-loader": "^0.13.1",
+    "url": "^0.11.0",
+    "url-loader": "^0.5.7",
+    "webpack": "^1.13.2",
+    "webpack-dev-middleware": "^1.8.4",
+    "webpack-hot-middleware": "^2.13.0"
+  }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/pom.xml
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/pom.xml b/new/ui-modules/app-inspector/pom.xml
new file mode 100644
index 0000000..251b79e
--- /dev/null
+++ b/new/ui-modules/app-inspector/pom.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+         
+    <parent>
+        <artifactId>brooklyn-ui-modules-parent</artifactId>
+        <groupId>org.apache.brooklyn.ui.modules</groupId>
+        <version>${revision}</version>
+    </parent>
+    
+    <artifactId>brooklyn-ui-app-inspector</artifactId>
+    <packaging>war</packaging>
+    <name>Brooklyn UI :: Modules - App inspector</name>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <artifactId>maven-clean-plugin</artifactId>
+                <configuration>
+                    <filesets>
+                        <fileset>
+                            <directory>dist</directory>
+                            <followSymlinks>false</followSymlinks>
+                        </fileset>
+                    </filesets>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>com.github.eirslett</groupId>
+                <artifactId>frontend-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-resources</id>
+                        <!-- here the phase you need -->
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>
+                                ${project.build.directory}/${project.artifactId}-${project.version}
+                            </outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${project.basedir}/dist</directory>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-war-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <!-- configure plugin to generate MANIFEST.MF
+                     adapted from http://blog.knowhowlab.org/2010/06/osgi-tutorial-from-project-structure-to.html -->
+                <executions>
+                    <execution>
+                        <id>bundle-manifest</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <supportedProjectTypes>
+                        <supportedProjectType>war</supportedProjectType>
+                        <supportedProjectType>jar</supportedProjectType>
+                        <supportedProjectType>bundle</supportedProjectType>
+                    </supportedProjectTypes>
+                    <instructions>
+                        <Bundle-ManifestVersion>${project.version}</Bundle-ManifestVersion>
+                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+                        <Import-Package>
+                            org.apache.brooklyn.ui.modularity.module.api,
+                            org.eclipse.jetty.servlets,
+                            *
+                        </Import-Package>
+                        <Export-Package>
+                            !*
+                        </Export-Package>
+                        <Web-ContextPath>/${project.artifactId}</Web-ContextPath>
+                        <_wab>app</_wab>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/src/main/resources/ui-module/config.yaml
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/src/main/resources/ui-module/config.yaml b/new/ui-modules/app-inspector/src/main/resources/ui-module/config.yaml
new file mode 100644
index 0000000..133da49
--- /dev/null
+++ b/new/ui-modules/app-inspector/src/main/resources/ui-module/config.yaml
@@ -0,0 +1,22 @@
+#
+# 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.
+name: App inspector
+slug: ${project.artifactId}
+icon: fa-search
+types:
+- home-ui-module
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/app-inspector/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/new/ui-modules/app-inspector/src/main/webapp/WEB-INF/web.xml b/new/ui-modules/app-inspector/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..9e99c5f
--- /dev/null
+++ b/new/ui-modules/app-inspector/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,64 @@
+<!--
+  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.
+-->
+<web-app version="2.4"
+         xmlns="http://java.sun.com/xml/ns/j2ee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+                             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+    <description>Brooklyn App inspector</description>
+    <display-name>Brooklyn App inspector</display-name>
+    <welcome-file-list>
+        <welcome-file>index.html</welcome-file>
+    </welcome-file-list>
+
+    <listener>
+        <listener-class>org.apache.brooklyn.ui.modularity.module.api.UiModuleListener</listener-class>
+    </listener>
+
+    <!--FILTERS :: START-->
+    <filter>
+        <filter-name>ui-module-filter</filter-name>
+        <filter-class>org.apache.brooklyn.ui.modularity.module.api.UiModuleFilter</filter-class>
+        <init-param>
+            <param-name>Cache-Control</param-name>
+            <param-value>public, max-age=604800</param-value><!--Cache static content for 1 week-->
+        </init-param>
+    </filter>
+    <filter>
+        <filter-name>GzipFilter</filter-name>
+        <filter-class>org.eclipse.jetty.servlets.GzipFilter</filter-class>
+        <init-param>
+            <param-name>mimeTypes</param-name>
+            <param-value>text/html,text/plain,text/xml,application/xhtml+xml,text/css,application/javascript,image/svg+xml</param-value>
+        </init-param>
+    </filter>
+
+    <filter-mapping>
+        <filter-name>ui-module-filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+    <filter-mapping>
+        <filter-name>GzipFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+    <!--FILTERS :: END-->
+
+
+</web-app>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/.babelrc
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/.babelrc b/new/ui-modules/blueprint-composer/.babelrc
new file mode 100644
index 0000000..222c2ba
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/.babelrc
@@ -0,0 +1,6 @@
+{
+  "presets": [
+    "env",
+    "stage-0"
+  ]
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/.eslintrc.json
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/.eslintrc.json b/new/ui-modules/blueprint-composer/.eslintrc.json
new file mode 100644
index 0000000..c4fe470
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/.eslintrc.json
@@ -0,0 +1,52 @@
+{
+  "__license__": [
+    "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."
+  ],
+  "extends": "google",
+  "rules": {
+    "indent": [
+      "error",
+      4
+    ],
+    "max-len": [
+      "warn",
+      120
+    ],
+    "space-before-function-paren": [
+      "error",
+      "always"
+    ],
+    "sort-imports": [
+      "error",
+      {
+        "ignoreCase": false,
+        "ignoreMemberSort": false,
+        "memberSyntaxSortOrder": [
+          "none",
+          "all",
+          "multiple",
+          "single"
+        ]
+      }
+    ],
+    "quotes": [
+      "error",
+      "single"
+    ]
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/.npmrc
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/.npmrc b/new/ui-modules/blueprint-composer/.npmrc
new file mode 100644
index 0000000..c595d19
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/.npmrc
@@ -0,0 +1,18 @@
+#
+# 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.
+package-lock=false
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/Makefile
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/Makefile b/new/ui-modules/blueprint-composer/Makefile
new file mode 100644
index 0000000..c7cb424
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/Makefile
@@ -0,0 +1,49 @@
+#
+# 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.
+default: dev
+
+build:
+	@echo "Building production bundle..."
+	NODE_ENV="production" npm run build
+
+clean:
+	@echo "Cleaning modules..."
+	@rm -rf ./node_modules
+
+dev:
+	@echo "Starting dev web server..."
+	@npm start
+
+server: build
+	@echo "Starting api proxy server..."
+	NODE_ENV="production" npm start
+
+install:
+	@echo "Installing npm modules..."
+	@npm install
+
+test:
+	@echo "Running tests..."
+	@npm test
+
+setup: clean install
+
+war:
+	@mvn clean install
+
+.PHONY: build clean deploy dev install server setup test war

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.directive.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.directive.js b/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.directive.js
new file mode 100644
index 0000000..b3e0eac
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.directive.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 template from "./blueprint-data-manager.template.html";
+import {saveAs} from "file-saver/FileSaver";
+const VALID_FILENAME_REGEX = /^.*\.ya?ml$/
+const FILETYPE_TOKEN_REGEX = /^.*\.(.*)$/
+
+export function blueprintDataManagerDirective() {
+    return {
+        restrict: 'E',
+        template: template,
+        controller: ['$rootScope', '$scope', '$element', '$document', 'blueprintService', 'brSnackbar', controller]
+    };
+
+    function controller($rootScope, $scope, $element, $document, blueprintService, brSnackbar) {
+        var fileInputElement = null;
+        $scope.fileInputId = 'hidden-load-file-input-' + Math.random().toString(36).slice(2);
+        $scope.dragging = false;
+        $scope.targetActive = false;
+        $scope.openFileDialog = openFileDialog;
+        $scope.saveBlueprint = saveBlueprint;
+
+        $document.bind('drag dragstart dragend dragover dragenter dragleave drop', (event)=> {
+            event.preventDefault();
+            event.stopPropagation();
+        }).bind('drag dragstart dragover dragenter', (event)=> {
+            $element.addClass('drag-active');
+        }).bind('mouseout dragend drop', (event)=> {
+            $element.removeClass('drag-active');
+            $element.removeClass('drag-target-active');
+        });
+
+        $element.bind('drag dragstart dragend dragover dragenter dragleave drop', (event)=> {
+            event.preventDefault();
+            event.stopPropagation();
+        }).bind('dragstart dragover dragenter', (event)=> {
+            event.dataTransfer.dropEffect = 'copy';
+            $element.addClass('drag-active');
+            $element.addClass('drag-target-active');
+        }).bind('dragleave dragend drop', (event)=> {
+            $element.removeClass('drag-active');
+            $element.removeClass('drag-target-active');
+        }).bind('drop', (event)=> {
+            readFile(event.dataTransfer.files[0]);
+        });
+
+        $document.bind('keydown', (event)=> {
+            if (event.keyCode === 83 && (event.ctrlKey || event.metaKey)) {
+                event.preventDefault();
+                saveBlueprint();
+            }
+        });
+
+        function notify(message) {
+            $rootScope.$apply(brSnackbar.create(message));
+        }
+
+        function saveBlueprint() {
+            try {
+                var yaml = blueprintService.getAsYaml();
+                if (yaml.length > 0) {
+                    var blob = new Blob([yaml], {type: 'text/plain;charset=' + document.inputEncoding || 'UTF-8'});
+                    saveAs(blob, 'blueprint-' + Date.now() + '.yaml');
+                } else {
+                    notify('Blueprint not saved ... empty');
+                }
+            } catch (error) {
+                notify('Blueprint saved');
+            }
+        }
+
+        function readFile(file) {
+            if (VALID_FILENAME_REGEX.test(file.name)) {
+                var reader = new FileReader();
+                reader.addEventListener("load", function () {
+                    try {
+                        var yaml = reader.result;
+                        blueprintService.setFromYaml(yaml, true);
+                        $rootScope.$broadcast('d3.redraw', true);
+                        notify('Blueprint loaded successfully');
+                    } catch (error) {
+                        notify('Failed to load blueprint ' + error.message);
+                    }
+                }, false);
+                reader.readAsText(file);
+            } else {
+                var match = file.name.match(FILETYPE_TOKEN_REGEX);
+                notify('Incorrect file type [' +
+                    ((match.length === 2) ? match[1] : match[0] ) + '] ... supported file types [yml, yaml]');
+            }
+        }
+
+        function openFileDialog() {
+            fileInputElement.click();
+        }
+
+        setTimeout(()=> {
+            fileInputElement = document.getElementById($scope.fileInputId);
+            fileInputElement.addEventListener('change', (e)=> {
+                readFile(e.target.files[0]);
+            }, false);
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.style.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.style.less b/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.style.less
new file mode 100644
index 0000000..f7c4ec9
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.style.less
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+blueprint-data-manager {
+  .file-drop-container {
+    display: none;
+    transition: all linear 0.3s;
+    position: fixed;
+    top: 30vh;
+    left: 30vw;
+    width: 40vw;
+    height: 40vh;
+    border: 10px dashed @gray-lighter;
+    color: @gray-lighter;
+    .file-drop-text {
+      font-size: 50px;
+      text-align: center;
+      margin-top: 15vh;
+    }
+
+  }
+  .file-drop-container.blur {
+    backdrop-filter: blur(10px);
+    -webkit-backdrop-filter: blur(10px);
+  }
+}
+
+blueprint-data-manager.drag-active {
+  .file-drop-container {
+    display: block;
+    opacity: 1;
+  }
+}
+
+blueprint-data-manager.drag-target-active {
+  .file-drop-container {
+    border-color: @gray-light;
+    color: @gray-light;
+    .file-drag-icon {
+      color: #5cb85c;
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.template.html b/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.template.html
new file mode 100644
index 0000000..6a962d4
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.template.html
@@ -0,0 +1,32 @@
+<!--
+  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="file-drop-container blur">
+    <div class="file-drop-text">
+        <h1>Drop file to load blueprint</h1>
+        <i class="file-drag-icon fa fa-plus-square" aria-hidden="true"></i>
+    </div>
+</div>
+<div class="file-drop-container"></div><!--2nd file-drop-container intentional (due to how JS events propogate)-->
+<button class="btn btn-primary" ng-click="saveBlueprint()">
+    <i class="fa fa-download" aria-hidden="true" title="Download the blueprint"></i>
+</button>
+<button class="btn btn-primary" ng-click="openFileDialog()">
+    <i class="fa fa-folder-open-o" aria-hidden="true" title="Import a blueprint"></i>
+</button>
+<input id="{{fileInputId}}" type="file" style="display:none">
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.directive.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.directive.js b/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.directive.js
new file mode 100644
index 0000000..72b6ea8
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.directive.js
@@ -0,0 +1,48 @@
+/*
+ * 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 './breadcrumbs.template.html';
+
+export function breadcrumbsDirective() {
+    return {
+        restrict: 'E',
+        scope: {
+            entity: '<',
+            current: '<'
+        },
+        template: template,
+        link: link
+    }
+}
+
+function link(scope) {
+    if (scope.entity) {
+        scope.breadcrumbs = [];
+        if (scope.current) {
+            scope.breadcrumbs.push(scope.current);
+        }
+
+        let currentEntity = scope.entity;
+        while (currentEntity.hasParent()) {
+            scope.breadcrumbs.push(currentEntity);
+            currentEntity = currentEntity.parent;
+        }
+        scope.breadcrumbs.push(currentEntity);
+        scope.breadcrumbs.reverse();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.less b/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.less
new file mode 100644
index 0000000..d910198
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.less
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+breadcrumbs {
+  display: block;
+
+  li:nth-child(2):before {
+    content: '\f061';
+    font-family: FontAwesome;
+  }
+
+  .active.attached:before {
+    content: '::';
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.template.html b/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.template.html
new file mode 100644
index 0000000..4047bd4
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.template.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.
+-->
+<ol class="breadcrumb" ng-if="breadcrumbs">
+    <li>You are editing</li>
+    <li ng-repeat="breadcrumb in breadcrumbs track by $index" ng-switch="breadcrumb" ng-class="{'active': $index === breadcrumbs.length - 1, 'attached': breadcrumb.family.id !== 'ENTITY'}">
+        <a ng-if="$index < breadcrumbs.length - 1" ui-sref="main.graphical.edit.node.entity({entityId: breadcrumb._id})">{{breadcrumb.name || 'Unnamed entity'}}</a>
+        <span ng-if="$index === breadcrumbs.length - 1">
+            {{breadcrumb.name || 'Unnamed ' + breadcrumb.family.displayName}}
+        </span>
+    </li>
+</ol>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js b/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js
new file mode 100644
index 0000000..33bda2e
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js
@@ -0,0 +1,181 @@
+/*
+ * 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 angularAnimate from 'angular-animate';
+import uibModal from 'angular-ui-bootstrap/src/modal/index-nocss';
+import template from './catalog-saver.template.html';
+import modalTemplate from './catalog-saver.modal.template.html';
+import jsYaml from 'js-yaml';
+import brUtils from 'brooklyn-ui-utils/utils/general';
+
+const MODULE_NAME = 'brooklyn.components.catalog-saver';
+const REASONS = {
+    new: 0,
+    deploy: 1
+};
+const VIEWS = {
+    form: 0,
+    saved: 1
+};
+const TYPES = [
+    {id: 'template', label: 'Application'},
+    {id: 'entity', label: 'Entity'}
+];
+
+angular.module(MODULE_NAME, [angularAnimate, uibModal, brUtils])
+    .directive('catalogSaver', ['$rootScope', '$uibModal', saveToCatalogModalDirective])
+    .directive('catalogVersion', ['$parse', catalogVersionDirective]);
+
+export default MODULE_NAME;
+
+export function saveToCatalogModalDirective($rootScope, $uibModal) {
+    return {
+        restrict: 'E',
+        template: template,
+        scope: {
+            config: '='
+        },
+        link: link
+    };
+
+    function link($scope) {
+        $scope.buttonText = Object.keys($scope.config).length > 0 ? `Update ${$scope.config.name || $scope.config.symbolicName}` : 'Add to catalog';
+        $scope.activateModal = () => {
+            let modalInstance = $uibModal.open({
+                template: modalTemplate,
+                size: 'save',
+                controller: ['$scope', 'blueprintService', 'paletteApi', 'brUtilsGeneral', CatalogItemModalController],
+                scope: $scope,
+            });
+
+            // Promise is resolved when the modal is closed. We expect the modal to pass back the action to perform thereafter
+            modalInstance.result.then(reason => {
+                switch (reason) {
+                    case REASONS.new:
+                        $rootScope.$broadcast('blueprint.reset');
+                        break;
+                    case REASONS.deploy:
+                        $rootScope.$broadcast('blueprint.deploy');
+                        break;
+                }
+            });
+        };
+    }
+}
+
+export function CatalogItemModalController($scope, blueprintService, paletteApi, brUtilsGeneral) {
+    $scope.REASONS = REASONS;
+    $scope.VIEWS = VIEWS;
+    $scope.TYPES = TYPES;
+    $scope.state = {
+        pattern: '[\\w\\.\\-\\_]+',
+        view: VIEWS.form,
+        saving: false,
+        force: false,
+        isUpdate: Object.keys($scope.config).length > 0
+    };
+
+    $scope.getTitle = () => {
+        switch ($scope.state.view) {
+            case VIEWS.form:
+                return $scope.state.isUpdate ? `Update ${$scope.config.name || $scope.config.symbolicName}` : 'Add to catalog';
+            case VIEWS.saved:
+                return `${$scope.config.name || $scope.config.symbolicName} ${$scope.state.isUpdate ? 'updated' : 'saved'}`;
+        }
+    };
+
+    $scope.save = () => {
+        $scope.state.saving = true;
+        $scope.state.error = undefined;
+
+        let bom = createBom();
+        paletteApi.create(bom, {forceUpdate: $scope.state.force}).then((savedItem) => {
+            if (!angular.isArray($scope.config.versions)) {
+                $scope.config.versions = [];
+            }
+            $scope.config.versions.push($scope.config.version);
+            $scope.state.view = VIEWS.saved;
+        }).catch(error => {
+            $scope.state.error = error.error.message;
+        }).finally(() => {
+            $scope.state.saving = false;
+        });
+    };
+
+    function createBom() {
+        let blueprint = blueprintService.getAsJson();
+
+        let bomItem = {
+            id: $scope.config.symbolicName,
+            itemType: $scope.config.itemType,
+            item: blueprint
+        };
+        if (brUtilsGeneral.isNonEmpty($scope.config.name)) {
+            bomItem.name = $scope.config.name;
+        }
+        if (brUtilsGeneral.isNonEmpty($scope.config.description)) {
+            bomItem.description = $scope.config.description;
+        }
+        if (brUtilsGeneral.isNonEmpty($scope.config.iconUrl)) {
+            bomItem.iconUrl = $scope.config.iconUrl;
+        }
+
+        return jsYaml.dump({
+            'brooklyn.catalog': {
+                bundle: `catalog-bom-${$scope.config.bundle}`,
+                version: $scope.config.version,
+                items: [bomItem]
+            }
+        });
+    }
+}
+
+export function catalogVersionDirective($parse) {
+    return {
+        restrict: 'A',
+        require: 'ngModel',
+        link: link
+    };
+
+    function link (scope, elm, attr, ctrl) {
+        if (!ctrl) {
+            return;
+        }
+
+        let matches;
+        let force;
+
+        scope.$watch(attr.catalogVersion, value => {
+            if (matches !== value) {
+                matches = value;
+                ctrl.$validate();
+            }
+        });
+        scope.$watch(attr.catalogVersionForce, value => {
+            if (force !== value) {
+                force = value;
+                ctrl.$validate();
+            }
+        });
+
+        ctrl.$validators.exist = (modelValue, viewValue) => {
+            return !angular.isDefined(matches) || ctrl.$isEmpty(viewValue) || viewValue.endsWith('SNAPSHOT') || force === true || matches.indexOf(viewValue) === -1;
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.less b/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.less
new file mode 100644
index 0000000..a7faa18
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.less
@@ -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.
+ */
+.next-actions {
+  margin-top: 2em;
+}
+
+@media (min-width: 768px) {
+  .modal-save {
+    width: 400px;
+  }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.modal.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.modal.template.html b/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.modal.template.html
new file mode 100644
index 0000000..39b203a
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.modal.template.html
@@ -0,0 +1,113 @@
+<!--
+  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="modal-header">
+    <br-svg type="close" class="pull-right" ng-click="$dismiss('Close modal')"></br-svg>
+    <h3 class="modal-title">{{getTitle()}}</h3>
+</div>
+
+<div class="modal-body add-to-catalog-modal">
+    <form ng-show="state.view === VIEWS.form" name="form" novalidate>
+        <div class="form-group" ng-class="{'has-error': form.bundle.$invalid}">
+            <label class="control-label">Bundle ID</label>
+            <div class="input-group">
+                <span class="input-group-addon">catalog-bom-</span>
+                <input ng-model="config.bundle" ng-disabled="state.saving" class="form-control" placeholder="E.g my-bundle" name="bundle" required ng-pattern="state.pattern" autofocus />
+            </div>
+            <p class="help-block" ng-show="form.bundle.$invalid">
+                <span ng-if="form.bundle.$error.required">You must specify a bundle ID</span>
+                <span ng-if="form.bundle.$error.pattern">The bundle ID can contains only letters, numbers as well a the following characters: <code>.</code>, <code>-</code> and <code>_</code></span>
+            </p>
+        </div>
+        <div class="form-group" ng-class="{'has-error': form.version.$invalid}">
+            <label class="control-label">Version</label>
+            <div class="input-group">
+                <input ng-model="config.version" ng-disabled="state.saving" class="form-control" placeholder="E.g. 1.0.0-SNAPSHOT" name="version" type="text" required catalog-version="config.versions" catalog-version-force="state.force" ng-pattern="state.pattern" />
+                <span class="input-group-btn">
+                    <button class="btn btn-default" ng-class="{'btn-primary active': state.force}" ng-click="state.force = !state.force"
+                            uib-tooltip="Force override of existing bundle at this version." tooltip-placement="top-right">
+                        <i class="fa fa-fire"></i>
+                    </button>
+                </span>
+            </div>
+            <p class="help-block">
+                <span ng-if="form.version.$error.required">You must specify a version</span>
+                <span ng-if="form.version.$error.pattern">The version can contains only letters, numbers as well a the following characters: <code>.</code>, <code>-</code> and <code>_</code></span>
+                <span ng-if="form.version.$error.exist">This version has already been used</span>
+            </p>
+        </div>
+        <div class="form-group" ng-class="{'has-error': form.symbolicName.$invalid}">
+            <label class="control-label">Blueprint symbolic name</label>
+            <input ng-model="config.symbolicName" ng-disabled="state.saving" class="form-control" placeholder="E.g my-catalog-id" name="symbolicName" required ng-pattern="state.pattern" autofocus />
+            <p class="help-block" ng-show="form.symbolicName.$invalid">
+                <span ng-if="form.symbolicName.$error.required">You must specify a blueprint symbolic name</span>
+                <span ng-if="form.symbolicName.$error.pattern">The blueprint symbolic name can contains only letters, numbers as well a the following characters: <code>.</code>, <code>-</code> and <code>_</code></span>
+            </p>
+        </div>
+
+        <div class="form-group" ng-class="{'has-error': form.itemType.$invalid}">
+            <label class="control-label">Blueprint type</label>
+            <select class="form-control" name="itemType" ng-options="type.id as type.label for type in TYPES" ng-model="config.itemType" ng-disabled="state.saving" required >
+            </select>
+            <p class="help-block" ng-show="form.itemType.$invalid">
+                <span ng-if="form.itemType.$error.required">You must specify a blueprint type</span>
+            </p>
+        </div>
+        <div class="form-group">
+            <label class="control-label">Blueprint display name</label>
+            <input ng-model="config.name" ng-disabled="state.saving" class="form-control" name="name" type="text" />
+        </div>
+        <div class="form-group">
+            <label class="control-label">Blueprint description</label>
+            <textarea ng-model="config.description" ng-disabled="state.saving" class="form-control" name="description" rows="3"></textarea>
+        </div>
+        <div class="form-group">
+            <label class="control-label">Blueprint icon url</label>
+            <input ng-model="config.iconUrl" ng-disabled="state.saving" class="form-control" name="iconUrl" type="text" />
+        </div>
+    </form>
+
+    <div class="text-center" ng-show="state.view === VIEWS.saved">
+        <div class="fa-stack fa-3x text-success">
+            <i class="fa fa-circle-thin fa-stack-2x"></i>
+            <i class="fa fa-check fa-stack-1x"></i>
+        </div>
+
+        <h3>You're all set!</h3>
+
+        <div class="next-actions">
+            <p>What would you like to do next?</p>
+            <button class="btn btn-default btn-block" ng-click="$dismiss('Continue editing')">Continue to edit this blueprint</button>
+            <button class="btn btn-info btn-block" ng-click="$close(REASONS.new)">Create a new blueprint</button>
+            <a class="btn btn-primary btn-block" ng-href="/brooklyn-ui-catalog/#!/bundles/catalog-bom-{{config.bundle}}/{{config.version}}/types/{{config.symbolicName}}/{{config.version}}">View in catalog</a>
+            <button ng-if="['template', 'entity'].indexOf(config.itemType) > -1" class="btn btn-link btn-block" ng-click="$close(REASONS.deploy)">Or deploy</button>
+        </div>
+    </div>
+
+    <div uib-alert class="alert-danger" ng-if="state.error" dismiss-on-timeout="10000" close="state.error = undefined">
+        <h4>Failed to save</h4>
+        <p>{{state.error}}</p>
+    </div>
+</div>
+
+<div class="modal-footer" ng-if="state.view === VIEWS.form">
+    <button class="btn btn-success" ng-disabled="form.$invalid || state.saving" ng-click="save()">
+        <span ng-if="!state.saving">Save</span>
+        <i ng-if="state.saving" class="fa fa-spinner fa-spin"></i>
+    </button>
+</div>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.template.html
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.template.html b/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.template.html
new file mode 100644
index 0000000..4a216af
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.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.
+-->
+<button class="btn btn-info" ng-click="activateModal()">{{buttonText}}</button>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.directive.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.directive.js b/new/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.directive.js
new file mode 100644
index 0000000..e19b1a2
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.directive.js
@@ -0,0 +1,226 @@
+/*
+ * 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 {EntityFamily} from '../util/model/entity.model';
+import template from './catalog-selector.template.html';
+
+const ITEMS_PER_PAGE = 30;
+// fields in either bundle or type record:
+const FIELDS_TO_SEARCH = ['name', 'displayName', 'symbolicName', 'version', 'type', 'supertypes', 'containingBundle', 'description', 'displayTags'];
+
+export function catalogSelectorDirective() {
+    return {
+        restrict: 'E',
+        scope: {
+            family: '<',
+            onSelect: '&',
+            itemsPerPage: '<',
+            reservedKeys: '<?'
+        },
+        template: template,
+        controller: ['$scope', '$q', '$uibModal', '$log', 'paletteApi', 'paletteDragAndDropService', 'iconGenerator', controller]
+    };
+}
+
+export function catalogSelectorSearchFilter() {
+    return function (items, search) {
+        if (search) {
+            return items.filter(function (item) {
+                return search.toLowerCase().split(' ').reduce( (found, part) => 
+                    found &&
+                    FIELDS_TO_SEARCH
+                        .filter(field => item.hasOwnProperty(field) && item[field])
+                        .reduce((match, field) => {
+                            if (match) return true;
+                            let text = item[field];
+                            if (!text.toLowerCase) {
+                                text = JSON.stringify(text).toLowerCase();
+                            } else {
+                                text = text.toLowerCase();
+                            }
+                            return match || text.indexOf(part) > -1;
+                        }, false)
+                , true);
+            });
+        } else {
+            return items;
+        }
+    }
+}
+
+export function catalogSelectorSortFilter($filter) {
+    return function (items, family) {
+        return items.sort(function (left, right) {
+            let nameLeft;
+            let nameRight;
+            if (family) {
+                switch (family) {
+                    case EntityFamily.ENTITY:
+                    case EntityFamily.SPEC:
+                    case EntityFamily.POLICY:
+                    case EntityFamily.ENRICHER:
+                    case EntityFamily.LOCATION:
+                        nameLeft = $filter('entityName')(left);
+                        nameRight = $filter('entityName')(right);
+                        break;
+                }
+            }
+
+            if (!nameLeft || !nameRight) {
+                return 0;
+            }
+            let nameCompare = nameLeft.localeCompare(nameRight);
+            if (nameCompare !== 0) {
+                return nameCompare;
+            }
+            let versionCompare = right.version.localeCompare(left.version);
+            if (versionCompare !== 0) {
+                return versionCompare
+            }
+            // TODO should symbolic name be the sorted field?
+            let symNameCompare = left.symbolicName.localeCompare(right.symbolicName);
+            if (symNameCompare !== 0) {
+                return symNameCompare
+            }            
+            let containingBundleCompare = right.containingBundle.localeCompare(left.containingBundle);
+            if (containingBundleCompare !== 0) {
+                return containingBundleCompare
+            }            
+            return 0;
+        });
+    }
+}
+
+function controller($scope, $q, $uibModal, $log, paletteApi, paletteDragAndDropService, iconGenerator) {
+    $scope.pagination = {
+        page: 1,
+        itemsPerPage: $scope.itemsPerPage || ITEMS_PER_PAGE
+    };
+    $scope.state = {
+        orders: ['name', 'type', 'id'],
+        currentOrder: 'name'
+    };
+
+    $scope.getPlaceHolder = function () {
+        return 'Search';
+    };
+
+    $scope.isLoading = true;
+
+    $scope.$watch('search', () => {
+        $scope.freeFormTile = {
+            symbolicName: $scope.search,
+            name: $scope.search,
+            displayName: $scope.search,
+            supertypes: [$scope.family.superType]
+        };
+    });
+
+    $scope.getItems = function (search) {
+        let defer = $q.resolve([]);
+
+        switch ($scope.family) {
+            case EntityFamily.ENTITY:
+            case EntityFamily.SPEC:
+                defer = paletteApi.getTypes({params: {supertype: 'entity', fragment: search}});
+                break;
+            case EntityFamily.POLICY:
+                defer = paletteApi.getTypes({params: {supertype: 'policy', fragment: search}});
+                break;
+            case EntityFamily.ENRICHER:
+                defer = paletteApi.getTypes({params: {supertype: 'enricher', fragment: search}});
+                break;
+            case EntityFamily.LOCATION:
+                defer = paletteApi.getLocations();
+                break;
+        }
+
+        return defer.then(data => {
+            return data;
+        }).catch(error => {
+            return [];
+        }).finally(() => {
+            $scope.isLoading = false;
+        });
+    };
+    $scope.onSelectItem = function (item) {
+        if (angular.isFunction($scope.onSelect)) {
+            $scope.onSelect({item: item});
+        }
+        $scope.search = '';
+    };
+    $scope.onDragItem = function (item, event) {
+        let frame = document.createElement('div');
+        frame.classList.add('drag-frame');
+        event.target.appendChild(frame);
+        setTimeout(function() {
+            // can remove at end of this cycle, browser will have grabbed its drag image
+            frame.parentNode.removeChild(frame);
+        }, 0);
+        /* have tried many other ways to get a nice drag image;
+           this seems to work best, adding an empty div which forces the size to be larger,
+           so when grabbing the image it grabs the drop-shadow.
+           things that _didn't_ work include:
+           - styling event.target now then unstyling (normally this would work, in posts on the web, but it doesn't here; angular?)
+           - make a restyled cloned copy offscreen (this comes so close but remote img srcs aren't loaded
+         */
+        
+        paletteDragAndDropService.dragStart(item);
+    };
+    $scope.onDragEnd = function (item, event) {
+        paletteDragAndDropService.dragEnd();
+    };
+    $scope.allowFreeForm = function () {
+        return [
+            EntityFamily.LOCATION
+        ].indexOf($scope.family) > -1;
+    };
+    $scope.isReserved = function () {
+        if (!$scope.reservedKeys || !angular.isArray($scope.reservedKeys)) {
+            return false;
+        }
+        return $scope.reservedKeys.indexOf($scope.search) > -1;
+    };
+    $scope.onImageError = (scope, el, attrs) => {
+        $log.warn("Icon for "+attrs.itemId+" at "+angular.element(el).attr("src")+" could not be loaded; generating icon");
+        angular.element(el).attr("src", iconGenerator(attrs.itemId));
+    };
+
+    // Init
+    $scope.items = [];
+    function getDisplayTags(tags) {
+        if (!tags || !tags.length || !tags.reduce) return tags;
+        return tags.reduce((result, tag) => { 
+            if (!(/[=:\[\]()]/.exec(tag))) {
+                result.push(tag);
+            }
+            return result; 
+        }, []);
+    }
+    $scope.getItems().then((items)=> {
+        // add displayTags, as any tag that doesn't contain = : or ( ) [ ]
+        // any tag that is an object will be eliminated as it is toStringed to make [ object object ]
+        items.forEach(item => { 
+            if (item.tags) {
+                item.displayTags = getDisplayTags(item.tags); 
+            } 
+        });
+        $scope.items = items;
+    });
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29927b73/new/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less b/new/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less
new file mode 100644
index 0000000..e0bdb73
--- /dev/null
+++ b/new/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less
@@ -0,0 +1,213 @@
+/*
+ * 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.
+ */
+catalog-selector {
+  @duration: 0.3s;
+  @duration-item: 0.1s;
+  
+  display: block;
+  margin-top: 15px;
+
+  .grid {
+    margin-bottom: 15px;
+  }
+  .pagination {
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+
+  .catalog-palette {
+    padding-left: 10px;
+    padding-right: 10px;
+  }
+  .catalog-palette-item {
+    padding-left: 5px;
+    padding-right: 5px;
+  }
+  
+  .item {
+    margin-top: 9px;
+    padding: 3px;
+    cursor: copy;
+    transition: all @duration-item ease;
+    position: relative;
+    background: white; //useful when dragging
+    box-shadow: 0 0 rgba(0, 0, 0, 0.1), 0 1px 2px 1px rgba(0, 0, 0, 0.2);
+    &:hover {
+      box-shadow: 0 0 rgba(0, 0, 0, 0.2), 0 2px 4px 1px rgba(0, 0, 0, 0.2);
+      transform: scale(1.1);
+
+      .fa-info-circle {
+        opacity: 1;
+      }
+    }
+    .drag-frame {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        transform: scale(1.15);
+    }
+
+    .fa-info-circle {
+      position: absolute;
+      top: 2px;
+      right: 2px;
+      padding: 3px 3px 3px 4px;  // this seems to center it better than 3px all around
+      opacity: 0;
+      transition: opacity @duration ease, color @duration ease;
+      cursor: pointer;
+      color: mix(@gray-light, @gray-lighter, 50%);
+      background-color: rgba(255, 255, 255, 0.75);
+      border-radius: 10px;
+      &:hover {
+        color: @gray;
+      }
+    }
+
+    .item-logo {
+      position: relative;
+      width: 100%;
+      height: 60px;
+      overflow: hidden;
+
+      & > img {
+        -webkit-user-drag: none;
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        margin: auto;
+        max-width: 90%;
+        max-height: 100%;
+      }
+    }
+
+    .item-content {
+      &.deprecated {
+        color: @gray-light;
+      }
+      padding-left: 2px;
+      padding-right: 2px;
+      padding-top: 2px;
+      padding-bottom: 4px;
+      overflow: hidden;
+
+      h3 {
+        font-size: 0.75em;
+        text-align: center;
+        margin: 0;
+        .text-overflow();
+      }
+    }
+  }
+  .form-group {
+      margin-top: 3px;
+      margin-bottom: 3px;
+  }
+  
+  .spinner-area {
+    height: 300px;
+    position: relative;
+
+    .spinner {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%,-50%);
+
+      .svg-container {
+        width: 50px;
+        height: 50px;
+        transform-origin: 50% 50%;
+        animation: interstitial-spinner-animation 1.0s infinite linear;
+
+        circle {
+          fill: none;
+          stroke-width: 7;
+          &.foreground {
+            stroke: @brand-primary;
+            stroke-dasharray: 105 360;
+          }
+          &.background {
+            stroke: rgba(0, 0, 0, 0.05);
+          }
+        }
+
+        @keyframes interstitial-spinner-animation {
+          100% {
+            transform: rotate(360deg);
+          }
+        }
+      }
+    }
+  }
+}
+
+.catalog-selector-popover {
+  // these numbers are extremely brittle, for instance -5px or -8px causes it to shift up 100px or so
+  // old discussion in history
+  margin-top: -6px;
+  max-width: 400px;
+    
+  .palette-item-quick-info {
+    word-wrap: break-word;
+    word-break: break-word;
+    .quick-info-metadata {
+      .text-muted();
+      .text-overflow();
+      white-space: normal;
+      word-break: break-all;
+      p {
+        margin-bottom: 0;
+        margin-left: 1.5em;
+        font-size: 90%;
+        .type-symbolic-name {
+            font-weight: bold;
+        }
+      }
+      .mini-icon {
+        width: 1.5em;
+        overflow-x: visible;
+        margin-left: -1.5em;
+        text-align: center;
+      }
+    }
+    .deprecated-marker {
+      float: right;
+      color: @brand-warning;
+    }
+    .quick-info-description {
+        border-top: 1px solid @popover-border-color;
+        border-bottom: 1px solid @popover-border-color;
+        padding-top: 10px;
+        padding-bottom: 10px;
+        margin-top: 10px;
+        margin-bottom: 10px;
+    }
+    .palette-item-tag {
+        margin-right: 4px;
+    }
+  }
+
+  p:last-child {
+    margin-bottom: 0;
+  }
+}