diff --git a/awx/ui/client/lib/angular-breadcrumb/.bower.json b/awx/ui/client/lib/angular-breadcrumb/.bower.json
new file mode 100644
index 0000000000..3895c9f0a9
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/.bower.json
@@ -0,0 +1,44 @@
+{
+ "name": "angular-breadcrumb",
+ "description": "AngularJS module that generates a breadcrumb from ui-router's states",
+ "version": "0.4.1",
+ "main": "release/angular-breadcrumb.js",
+ "ignore": [
+ "sample",
+ "src",
+ "test",
+ ".bowerrc",
+ ".coveralls.yml",
+ ".gitignore",
+ ".jshintrc",
+ ".travis.yml",
+ "gruntfile.js",
+ "bower.json",
+ "karma.conf.js",
+ "libpeerconnection.log",
+ "package.json",
+ "README.md"
+ ],
+ "dependencies": {
+ "angular": ">=1.0.8",
+ "angular-ui-router": ">=0.2.0"
+ },
+ "devDependencies": {
+ "bootstrap": "~2.3.2",
+ "angular-ui-bootstrap-bower": "~0.8.0",
+ "underscore": "~1.5.1",
+ "angular-mocks": ">=1.0.8",
+ "angular-sanitize": ">=1.0.8"
+ },
+ "homepage": "https://github.com/ncuillery/angular-breadcrumb",
+ "_release": "0.4.1",
+ "_resolution": {
+ "type": "version",
+ "tag": "v0.4.1",
+ "commit": "b291e06f4010ebebbb41ea2c14e73e236aa70930"
+ },
+ "_source": "git://github.com/ncuillery/angular-breadcrumb.git",
+ "_target": "~0.4.1",
+ "_originalSource": "angular-breadcrumb",
+ "_direct": true
+}
\ No newline at end of file
diff --git a/awx/ui/client/lib/angular-breadcrumb/.editorconfig b/awx/ui/client/lib/angular-breadcrumb/.editorconfig
new file mode 100644
index 0000000000..16480f1870
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/.editorconfig
@@ -0,0 +1,16 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/awx/ui/client/lib/angular-breadcrumb/.npmignore b/awx/ui/client/lib/angular-breadcrumb/.npmignore
new file mode 100644
index 0000000000..86b98729db
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/.npmignore
@@ -0,0 +1,15 @@
+sample
+src
+test
+.idea
+bower_components
+coverage
+testDependencies
+.bowerrc
+.coveralls.yml
+.gitignore
+.jshintrc
+.travis.yml
+gruntfile.js
+karma.conf.js
+libpeerconnection.log
diff --git a/awx/ui/client/lib/angular-breadcrumb/CHANGELOG.md b/awx/ui/client/lib/angular-breadcrumb/CHANGELOG.md
new file mode 100644
index 0000000000..6bec7b2db5
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/CHANGELOG.md
@@ -0,0 +1,151 @@
+
+### 0.4.1 (2015-08-09)
+
+
+#### Features
+
+* add the scope-based ncyBreadcrumbIgnore flag ([934c5523](http://github.com/ncuillery/angular-breadcrumb/commit/934c5523208a9615d7cfa3abcb397bbe131332ac), closes [#42](http://github.com/ncuillery/angular-breadcrumb/issues/42), [#62](http://github.com/ncuillery/angular-breadcrumb/issues/42))
+
+
+
+### 0.4.0 (2015-05-17)
+
+
+#### Bug Fixes
+
+* **$breadcrumb:** Handle parents provided by StateObject references ([f4288d37](http://github.com/ncuillery/angular-breadcrumb/commit/f4288d375fd1090ffec1d67e85c6300d74d86d37), closes [#82](http://github.com/ncuillery/angular-breadcrumb/issues/82))
+* **ncyBreadcrumb:**
+ * Prevent memory leak when label is a binding ([264e10f6](http://github.com/ncuillery/angular-breadcrumb/commit/264e10f680e1bbb8d1e00cf500de39cac4222cfd), closes [#88](http://github.com/ncuillery/angular-breadcrumb/issues/88))
+ * Removed trailing spaces from breadcrumb items([bc276ed5](http://github.com/ncuillery/angular-breadcrumb/commit/bc276ed5351a586d4a6dc83ada0687e6ca485344), closes [#77](http://github.com/ncuillery/angular-breadcrumb/issues/77))
+
+#### Features
+
+* Add force to ncyBreadcrumb options ([31125a38](http://github.com/ncuillery/angular-breadcrumb/commit/31125a386d706dd76df807b3b02e1fccea38fb59), closes [#77](http://github.com/ncuillery/angular-breadcrumb/issues/78))
+* **ncyBreadcrumbText:** Add ncyBreadcrumbText directive ([82b2b443](http://github.com/ncuillery/angular-breadcrumb/commit/82b2b443fab220cd9ac7d3a8c90c1edc4291e54a), closes [#71](http://github.com/ncuillery/angular-breadcrumb/issues/71), [#83](http://github.com/ncuillery/angular-breadcrumb/issues/83))
+
+
+
+### 0.3.3 (2014-12-16)
+
+
+#### Bug Fixes
+
+* **ncyBreadcrumb:** define `$$templates` with var instead of attaching it to `window` ([c35c9d25](http://github.com/ncuillery/angular-breadcrumb/commit/c35c9d255b5e2585d225a961d1efdb51d18f6a55), closes [#55](http://github.com/ncuillery/angular-breadcrumb/issues/55))
+
+
+
+### 0.3.2 (2014-11-15)
+
+* **npm:** nothing, it's only a blank release due to a network problem during the last `npm publish` (f...ing npm doesn't allow a republish with the same version number [npm-registry-couchapp#148](https://github.com/npm/npm-registry-couchapp/issues/148)).
+
+
+### 0.3.1 (2014-11-15)
+
+
+#### Bug Fixes
+
+* **npm:** update package.json after (unclean) npm publish ([ab8161c2](http://github.com/ncuillery/angular-breadcrumb/commit/ab8161c25f98613f725b5e5ff8fe147acd60b365), closes [#52](http://github.com/ncuillery/angular-breadcrumb/issues/52))
+* **sample:** Send correct url params for the room link in booking view ([876de49a](http://github.com/ncuillery/angular-breadcrumb/commit/876de49a9c5d6e2d75714a606238e9041ed49baf))
+
+
+
+## 0.3.0 (2014-10-29)
+
+
+#### Bug Fixes
+
+* organize state-level options in `ncyBreadcrumb` key instead of `data` ([1ea436d3](http://github.com/ncuillery/angular-breadcrumb/commit/1ea436d3f6d5470b7ae3e71e71259dbd2422bc00), closes [#30](http://github.com/ncuillery/angular-breadcrumb/issues/30))
+* curly braces appearing on title of sample app ([855e76cb](http://github.com/ncuillery/angular-breadcrumb/commit/855e76cb33fda607fa3caa230564b77b48262c40))
+
+
+#### Features
+
+* Add a global option to include abstract states ([6f0461ea](http://github.com/ncuillery/angular-breadcrumb/commit/6f0461ea7db36d8e10c29ed10de1f1c08d215a19), closes [#35](http://github.com/ncuillery/angular-breadcrumb/issues/35), [#28](http://github.com/ncuillery/angular-breadcrumb/issues/28))
+* **$breadcrumb:**
+ * Support url params when using `ncyBreadcrumb.parent` property ([55730045](http://github.com/ncuillery/angular-breadcrumb/commit/55730045dcf3b4fb1048c67f1e18953505563ed4), closes [#46](http://github.com/ncuillery/angular-breadcrumb/issues/46))
+ * add the customization of the parent state with a function ([ada09015](http://github.com/ncuillery/angular-breadcrumb/commit/ada09015c49f05a94349dabf078f1ed621811aaa), closes [#32](http://github.com/ncuillery/angular-breadcrumb/issues/32))
+* **ncyBreadcrumbLast:** Add a new directive rendering the last step ([1eef24fb](http://github.com/ncuillery/angular-breadcrumb/commit/1eef24fbe862a1e3308181c38f50755843cf4426), closes [#37](http://github.com/ncuillery/angular-breadcrumb/issues/37))
+
+
+#### Breaking Changes
+
+* state-level options has been moved under the custom key
+`ncyBreadcrumb` in state's configuration.
+
+To migrate the code follow the example below:
+```
+// Before
+$stateProvider.state('A', {
+ url: '/a',
+ data: {
+ ncyBreadcrumbLabel: 'State A'
+ }
+});
+```
+
+```
+// After
+$stateProvider.state('A', {
+ url: '/a',
+ ncyBreadcrumb: {
+ label: 'State A'
+ }
+});
+```
+See [API reference](https://github.com/ncuillery/angular-breadcrumb/wiki/API-Reference) for more informations.
+ ([1ea436d3](http://github.com/ncuillery/angular-breadcrumb/commit/1ea436d3f6d5470b7ae3e71e71259dbd2422bc00))
+
+
+
+### 0.2.3 (2014-07-26)
+
+
+#### Bug Fixes
+
+* **$breadcrumb:** use `$stateParams` in case of unhierarchical states ([1c3c05e0](http://github.com/ncuillery/angular-breadcrumb/commit/1c3c05e0acac191fe2e76db2ef18da339caefaaa), closes [#29](http://github.com/ncuillery/angular-breadcrumb/issues/29))
+
+
+
+### 0.2.2 (2014-06-23)
+
+
+#### Bug Fixes
+
+* catch the `$viewContentLoaded` earlier ([bb47dd54](http://github.com/ncuillery/angular-breadcrumb/commit/bb47dd54deb5efc579ccb9b1575e686803dee1c5), closes [#14](http://github.com/ncuillery/angular-breadcrumb/issues/14))
+* **sample:**
+ * make the CRU(D) about rooms working ([3ca89ec7](http://github.com/ncuillery/angular-breadcrumb/commit/3ca89ec771fd20dc4ab2d733612bdcfb96ced703))
+ * prevent direct URL access to a day disabled in the datepicker ([95236916](http://github.com/ncuillery/angular-breadcrumb/commit/95236916e00b19464a3dfe3584ef1b18da9ffb25), closes [#17](http://github.com/ncuillery/angular-breadcrumb/issues/17))
+ * use the same variable in the datepicker and from url params for state `booking.day` ([646f7060](http://github.com/ncuillery/angular-breadcrumb/commit/646f70607e494f0e5e3c2483ed69f689684b2742), closes [#16](http://github.com/ncuillery/angular-breadcrumb/issues/16))
+
+
+#### Features
+
+* **ncyBreadcrumb:** watch every expression founded in labels ([1363515e](http://github.com/ncuillery/angular-breadcrumb/commit/1363515e20977ce2f39a1f5e5e1d701f0d7af296), closes [#20](http://github.com/ncuillery/angular-breadcrumb/issues/20))
+
+
+
+### 0.2.1 (2014-05-16)
+
+
+#### Bug Fixes
+
+* **$breadcrumb:** check if a state has a parent when looking for an inheritated property ([77e668b5](http://github.com/ncuillery/angular-breadcrumb/commit/77e668b5eb759570a64c2a885e81580953af3201), closes [#11](http://github.com/ncuillery/angular-breadcrumb/issues/11))
+
+
+
+### 0.2.0 (2014-05-08)
+
+
+#### Bug Fixes
+
+* **$breadcrumb:** remove abstract states from breadcrumb ([8a06c5ab](http://github.com/ncuillery/angular-breadcrumb/commit/8a06c5abce749027d48f7309d1aabea1e447dfd5), closes [#8](http://github.com/ncuillery/angular-breadcrumb/issues/8))
+* **ncyBreadcrumb:** display the correct breadcrumb in case of direct access ([e1f455ba](http://github.com/ncuillery/angular-breadcrumb/commit/e1f455ba4def97d3fc76b53772867b5f9daf4232), closes [#10](http://github.com/ncuillery/angular-breadcrumb/issues/10))
+
+
+#### Features
+
+* **$breadcrumb:**
+ * add a configuration property for skipping a state in the breadcrumb ([dd255d90](http://github.com/ncuillery/angular-breadcrumb/commit/dd255d906c4231f44b48f066d4db197a9c6b9e27), closes [#9](http://github.com/ncuillery/angular-breadcrumb/issues/9))
+ * allow chain of states customization ([028e493a](http://github.com/ncuillery/angular-breadcrumb/commit/028e493a1ebcae5ae60b8a9d42b949262000d7df), closes [#7](http://github.com/ncuillery/angular-breadcrumb/issues/7))
+* **ncyBreadcrumb:** add 'Element' declaration style '' ([b51441ea](http://github.com/ncuillery/angular-breadcrumb/commit/b51441eafb1659b782fea1f8668c7f455e1d6b4d))
+
diff --git a/awx/ui/client/lib/angular-breadcrumb/CONTRIBUTING.md b/awx/ui/client/lib/angular-breadcrumb/CONTRIBUTING.md
new file mode 100644
index 0000000000..720c4f142a
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/CONTRIBUTING.md
@@ -0,0 +1,52 @@
+# Contributing to angular-breadcrumb
+
+I am very glad to see this project living with PR from contributors who trust in it. Here is some guidelines to keep the contributions useful and efficient.
+
+## Development hints
+
+### Installation
+- Checkout the repository
+- Run `npm install`
+- Run `bower install`
+
+### Test running
+This module uses the classic AngularJS stack with:
+
+- Karma (test runner)
+- Jasmine (assertion framework)
+- angular-mocks (AngularJS module for testing)
+
+Run the test with the grunt task `grunt test`. It runs the tests with different versions of AngularJS.
+
+### Test developing
+Tests are build around modules with a specific `$stateProvider` configuration:
+
+- [Basic configuration](https://github.com/ncuillery/angular-breadcrumb/blob/master/test/mock/test-modules.js#L6): Basic definitions (no template, no controller)
+- [Interpolation configuration](https://github.com/ncuillery/angular-breadcrumb/blob/master/test/mock/test-modules.js#L21): States with bindings in `ncyBreadcrumbLabel`
+- [HTML configuration](https://github.com/ncuillery/angular-breadcrumb/blob/master/test/mock/test-modules.js#L36): States with HTML in `ncyBreadcrumbLabel`
+- [Sample configuration](https://github.com/ncuillery/angular-breadcrumb/blob/master/test/mock/test-modules.js#L41): Bridge towards the sample app configuration for using in tests
+- [UI-router's configuration](https://github.com/ncuillery/angular-breadcrumb/blob/master/test/mock/test-ui-router-sample.js#L9): Clone of the UI-router sample app (complemented with breadcrumb configuration)
+
+Theses modules are loaded by Karma and they are available in test specifications.
+
+Specifications are generally related to the directive `ncyBreadcrumb` or the service `$breadcrumb`.
+
+### Sample
+If you are not familiar with JS testing. You can run the [sample](http://ncuillery.github.io/angular-breadcrumb/#/sample) locally for testing purposes by using `grunt sample`. Sources are live-reloaded after each changes.
+
+## Submitting a Pull Request
+- Fork the [repository](https://github.com/ncuillery/angular-breadcrumb/)
+- Make your changes in a new git branch following the coding rules below.
+- Run the grunt default task (by typing `grunt` or `grunt default`): it will run the tests and build the module in `dist` directory)
+- Commit the changes (including the `dist` directory) by using the commit conventions explained below.
+- Push and make the PR
+
+
+## Coding rules
+- When making changes on the source file, please check that your changes are covered by the tests. If not, create a new test case.
+
+
+## Commit conventions
+angular-breadcrumb uses the same strict conventions as AngularJS and UI-router. These conventions are explained [here](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines).
+
+It is very important to fit these conventions especially for types `fix` and `feature` which are used by the CHANGELOG.md generation (it uses the [grunt-conventional-changelog](https://github.com/btford/grunt-conventional-changelog)).
diff --git a/awx/ui/client/lib/angular-breadcrumb/Gruntfile.js b/awx/ui/client/lib/angular-breadcrumb/Gruntfile.js
new file mode 100644
index 0000000000..744af4d211
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/Gruntfile.js
@@ -0,0 +1,258 @@
+'use strict';
+
+var LIVERELOAD_PORT = 35729;
+var lrSnippet = require('connect-livereload')({ port: LIVERELOAD_PORT });
+var mountFolder = function (connect, dir) {
+ return connect.static(require('path').resolve(dir));
+};
+
+module.exports = function (grunt) {
+
+ // Project configuration.
+ grunt.initConfig({
+ // Metadata.
+ pkg: grunt.file.readJSON('package.json'),
+ headerDev: '/*! <%= pkg.name %> - v<%= pkg.version %>-dev-<%= grunt.template.today("yyyy-mm-dd") %>\n',
+ headerRelease: '/*! <%= pkg.name %> - v<%= pkg.version %>\n',
+ banner: '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
+ '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
+ ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
+ // Task configuration.
+ concat: {
+ dev: {
+ options: {
+ banner: '<%= headerDev %><%= banner %>\n(function (window, angular, undefined) {\n',
+ footer: '})(window, window.angular);\n',
+ stripBanners: true
+ },
+ src: ['src/<%= pkg.name %>.js'],
+ dest: 'dist/<%= pkg.name %>.js'
+ },
+ release: {
+ options: {
+ banner: '<%= headerRelease %><%= banner %>\n(function (window, angular, undefined) {\n',
+ footer: '})(window, window.angular);\n',
+ stripBanners: true
+ },
+ src: ['src/<%= pkg.name %>.js'],
+ dest: 'release/<%= pkg.name %>.js'
+ }
+ },
+ uglify: {
+ dev: {
+ options: {
+ banner: '<%= headerDev %><%= banner %>'
+ },
+ src: '<%= concat.dev.dest %>',
+ dest: 'dist/<%= pkg.name %>.min.js'
+ },
+ release: {
+ options: {
+ banner: '<%= headerRelease %><%= banner %>'
+ },
+ src: '<%= concat.release.dest %>',
+ dest: 'release/<%= pkg.name %>.min.js'
+ }
+ },
+ karma: {
+ unit: {
+ configFile: 'karma.conf.js'
+ }
+ },
+ jshint: {
+ options: {
+ jshintrc: '.jshintrc'
+ },
+ gruntfile: {
+ src: 'Gruntfile.js'
+ },
+ sources: {
+ options: {
+ jshintrc: 'src/.jshintrc'
+ },
+ src: ['src/**/*.js']
+ },
+ test: {
+ src: ['test/**/*.js']
+ }
+ },
+ watch: {
+ gruntfile: {
+ files: '<%= jshint.gruntfile.src %>',
+ tasks: ['jshint:gruntfile']
+ },
+ sources: {
+ files: '<%= jshint.sources.src %>',
+ tasks: ['jshint:sources', 'karma']
+ },
+ test: {
+ files: '<%= jshint.test.src %>',
+ tasks: ['jshint:test', 'karma']
+ },
+ sample: {
+ options: {
+ livereload: LIVERELOAD_PORT
+ },
+ tasks: 'copy:breadcrumb',
+ files: [
+ 'sample/*.{css,js,html}',
+ 'sample/controllers/*.{css,js,html}',
+ 'sample/views/*.{css,js,html}',
+ 'src/*.js'
+ ]
+ }
+ },
+ copy: {
+ breadcrumb: {
+ files: [
+ {
+ flatten: true,
+ expand: true,
+ src: [
+ 'src/angular-breadcrumb.js'
+ ],
+ dest: 'sample/asset/'
+ }
+ ]
+ },
+ asset: {
+ files: [
+ {
+ flatten: true,
+ expand: true,
+ src: [
+ 'dist/angular-breadcrumb.js',
+ 'bower_components/angular/angular.js',
+ 'bower_components/angular-ui-router/release/angular-ui-router.js',
+ 'bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.js',
+ 'bower_components/bootstrap/docs/assets/css/bootstrap.css',
+ 'bower_components/underscore/underscore.js'
+ ],
+ dest: 'sample/asset/'
+ }
+ ]
+ },
+ img: {
+ files: [
+ {
+ flatten: true,
+ expand: true,
+ src: [
+ 'bower_components/bootstrap.css/img/glyphicons-halflings.png'
+ ],
+ dest: 'sample/img/'
+ }
+ ]
+ }
+ },
+ connect: {
+ options: {
+ port: 9000,
+ hostname: 'localhost'
+ },
+ livereload: {
+ options: {
+ middleware: function (connect) {
+ return [
+ lrSnippet,
+ mountFolder(connect, 'sample')
+ ];
+ }
+ }
+ }
+ },
+ open: {
+ server: {
+ url: 'http://localhost:<%= connect.options.port %>/index.html'
+ }
+ },
+ bump: {
+ options: {
+ files: ['package.json', 'bower.json'],
+ updateConfigs: ['pkg']
+ }
+ },
+ clean: {
+ release: ["sample/*.zip"],
+ test: ["testDependencies/*"]
+ },
+ compress: {
+ release: {
+ options: {
+ archive: 'sample/<%= pkg.name %>-<%= pkg.version %>.zip'
+ },
+ files: [
+ {expand: true, cwd: 'release/', src: ['*.js']}
+ ]
+ }
+ },
+ replace: {
+ release: {
+ src: ['sample/views/home.html'],
+ overwrite: true,
+ replacements: [{
+ from: /angular-breadcrumb-[0-9]+\.[0-9]+\.[0-9]+\.zip/g,
+ to: "angular-breadcrumb-<%= pkg.version %>.zip"
+ },
+ {
+ from: /\([0-9]+\.[0-9]+\.[0-9]+\)/g,
+ to: "(<%= pkg.version %>)"
+ }]
+ }
+ },
+ shell: {
+ testMinimal: {
+ command: 'bower install angular#=1.0.8 angular-mocks#=1.0.8 angular-sanitize#=1.0.8 angular-ui-router#=0.2.0 --config.directory=. --config.cwd=testDependencies'
+ },
+ test1dot2: {
+ command: 'bower install angular#=1.2.18 angular-mocks#=1.2.18 angular-sanitize#=1.2.18 angular-ui-router#=0.2.15 --config.directory=. --config.cwd=testDependencies'
+ },
+ testLatest: {
+ command: 'bower install angular angular-mocks angular-sanitize angular-ui-router --config.directory=. --config.cwd=testDependencies'
+ }
+ }
+
+ });
+
+ // These plugins provide necessary tasks.
+ grunt.loadNpmTasks('grunt-bump');
+ grunt.loadNpmTasks('grunt-contrib-clean');
+ grunt.loadNpmTasks('grunt-contrib-compress');
+ grunt.loadNpmTasks('grunt-contrib-concat');
+ grunt.loadNpmTasks('grunt-contrib-uglify');
+ grunt.loadNpmTasks('grunt-contrib-jshint');
+ grunt.loadNpmTasks('grunt-contrib-watch');
+ grunt.loadNpmTasks('grunt-contrib-copy');
+ grunt.loadNpmTasks('grunt-contrib-connect');
+ grunt.loadNpmTasks('grunt-conventional-changelog');
+ grunt.loadNpmTasks('grunt-karma');
+ grunt.loadNpmTasks('grunt-open');
+ grunt.loadNpmTasks('grunt-shell');
+ grunt.loadNpmTasks('grunt-text-replace');
+
+ grunt.registerTask('test', ['jshint', 'testMin', 'test1dot2', 'testLatest']);
+ grunt.registerTask('testMin', ['clean:test', 'shell:testMinimal', 'karma']);
+ grunt.registerTask('test1dot2', ['clean:test', 'shell:test1dot2', 'karma']);
+ grunt.registerTask('testLatest', ['clean:test', 'shell:testLatest', 'karma']);
+
+ grunt.registerTask('default', ['test', 'concat:dev', 'uglify:dev']);
+
+ grunt.registerTask('sample', ['concat:dev', 'copy:asset', 'copy:img', 'connect:livereload', 'open', 'watch']);
+
+ grunt.registerTask('release-prepare', 'Update all files for a release', function(target) {
+ if(!target) {
+ target = 'patch';
+ }
+ grunt.task.run(
+ 'bump-only:' + target, // Version update
+ 'test', // Tests
+ 'concat:release', // Concat with release banner
+ 'uglify:release', // Minify with release banner
+ 'changelog', // Changelog update
+ 'clean:release', // Delete old version download file
+ 'compress:release', // New version download file
+ 'replace:release' // Update version in download button (link & label)
+ );
+ });
+
+};
diff --git a/awx/ui/client/lib/angular-breadcrumb/LICENSE b/awx/ui/client/lib/angular-breadcrumb/LICENSE
new file mode 100644
index 0000000000..f0774fcf13
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/LICENSE
@@ -0,0 +1,24 @@
+The MIT License
+
+Copyright (c) 2013 Nicolas Cuillery
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/awx/ui/client/lib/angular-breadcrumb/bower.json b/awx/ui/client/lib/angular-breadcrumb/bower.json
new file mode 100644
index 0000000000..3a0a2f944c
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/bower.json
@@ -0,0 +1,33 @@
+{
+ "name": "angular-breadcrumb",
+ "description": "AngularJS module that generates a breadcrumb from ui-router's states",
+ "version": "0.4.1",
+ "main": "release/angular-breadcrumb.js",
+ "ignore": [
+ "sample",
+ "src",
+ "test",
+ ".bowerrc",
+ ".coveralls.yml",
+ ".gitignore",
+ ".jshintrc",
+ ".travis.yml",
+ "gruntfile.js",
+ "bower.json",
+ "karma.conf.js",
+ "libpeerconnection.log",
+ "package.json",
+ "README.md"
+ ],
+ "dependencies": {
+ "angular": ">=1.0.8",
+ "angular-ui-router": ">=0.2.0"
+ },
+ "devDependencies": {
+ "bootstrap": "~2.3.2",
+ "angular-ui-bootstrap-bower": "~0.8.0",
+ "underscore": "~1.5.1",
+ "angular-mocks": ">=1.0.8",
+ "angular-sanitize": ">=1.0.8"
+ }
+}
diff --git a/awx/ui/client/lib/angular-breadcrumb/dist/angular-breadcrumb.js b/awx/ui/client/lib/angular-breadcrumb/dist/angular-breadcrumb.js
new file mode 100644
index 0000000000..e20ca158ec
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/dist/angular-breadcrumb.js
@@ -0,0 +1,369 @@
+/*! angular-breadcrumb - v0.4.0-dev-2015-08-07
+* http://ncuillery.github.io/angular-breadcrumb
+* Copyright (c) 2015 Nicolas Cuillery; Licensed MIT */
+
+(function (window, angular, undefined) {
+'use strict';
+
+function isAOlderThanB(scopeA, scopeB) {
+ if(angular.equals(scopeA.length, scopeB.length)) {
+ return scopeA > scopeB;
+ } else {
+ return scopeA.length > scopeB.length;
+ }
+}
+
+function parseStateRef(ref) {
+ var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
+ if (!parsed || parsed.length !== 4) { throw new Error("Invalid state ref '" + ref + "'"); }
+ return { state: parsed[1], paramExpr: parsed[3] || null };
+}
+
+function $Breadcrumb() {
+
+ var $$options = {
+ prefixStateName: null,
+ template: 'bootstrap3',
+ templateUrl: null,
+ includeAbstract : false
+ };
+
+ this.setOptions = function(options) {
+ angular.extend($$options, options);
+ };
+
+ this.$get = ['$state', '$stateParams', '$rootScope', function($state, $stateParams, $rootScope) {
+
+ var $lastViewScope = $rootScope;
+
+ // Early catch of $viewContentLoaded event
+ $rootScope.$on('$viewContentLoaded', function (event) {
+ // With nested views, the event occur several times, in "wrong" order
+ if(!event.targetScope.ncyBreadcrumbIgnore &&
+ isAOlderThanB(event.targetScope.$id, $lastViewScope.$id)) {
+ $lastViewScope = event.targetScope;
+ }
+ });
+
+ // Get the parent state
+ var $$parentState = function(state) {
+ // Check if state has explicit parent OR we try guess parent from its name
+ var parent = state.parent || (/^(.+)\.[^.]+$/.exec(state.name) || [])[1];
+ var isObjectParent = typeof parent === "object";
+ // if parent is a object reference, then extract the name
+ return isObjectParent ? parent.name : parent;
+ };
+
+ // Add the state in the chain if not already in and if not abstract
+ var $$addStateInChain = function(chain, stateRef) {
+ var conf,
+ parentParams,
+ ref = parseStateRef(stateRef),
+ force = false,
+ skip = false;
+
+ for(var i=0, l=chain.length; i' +
+ '' +
+ '{{step.ncyBreadcrumbLabel}}' +
+ '{{step.ncyBreadcrumbLabel}}' +
+ '/' +
+ '' +
+ '',
+ bootstrap3: '' +
+ '- ' +
+ '{{step.ncyBreadcrumbLabel}}' +
+ '{{step.ncyBreadcrumbLabel}}' +
+ '
' +
+ '
'
+ };
+
+ return {
+ restrict: 'AE',
+ replace: true,
+ scope: {},
+ template: $breadcrumb.getTemplate($$templates),
+ templateUrl: $breadcrumb.getTemplateUrl(),
+ link: {
+ post: function postLink(scope) {
+ var labelWatchers = [];
+
+ var renderBreadcrumb = function() {
+ deregisterWatchers(labelWatchers);
+ labelWatchers = [];
+
+ var viewScope = $breadcrumb.$getLastViewScope();
+ scope.steps = $breadcrumb.getStatesChain();
+ angular.forEach(scope.steps, function (step) {
+ if (step.ncyBreadcrumb && step.ncyBreadcrumb.label) {
+ var parseLabel = $interpolate(step.ncyBreadcrumb.label);
+ step.ncyBreadcrumbLabel = parseLabel(viewScope);
+ // Watcher for further viewScope updates
+ registerWatchers(labelWatchers, parseLabel, viewScope, step);
+ } else {
+ step.ncyBreadcrumbLabel = step.name;
+ }
+ });
+ };
+
+ $rootScope.$on('$viewContentLoaded', function (event) {
+ if(!event.targetScope.ncyBreadcrumbIgnore) {
+ renderBreadcrumb();
+ }
+ });
+
+ // View(s) may be already loaded while the directive's linking
+ renderBreadcrumb();
+ }
+ }
+ };
+}
+BreadcrumbDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];
+
+function BreadcrumbLastDirective($interpolate, $breadcrumb, $rootScope) {
+
+ return {
+ restrict: 'A',
+ scope: {},
+ template: '{{ncyBreadcrumbLabel}}',
+ compile: function(cElement, cAttrs) {
+
+ // Override the default template if ncyBreadcrumbLast has a value
+ var template = cElement.attr(cAttrs.$attr.ncyBreadcrumbLast);
+ if(template) {
+ cElement.html(template);
+ }
+
+ return {
+ post: function postLink(scope) {
+ var labelWatchers = [];
+
+ var renderLabel = function() {
+ deregisterWatchers(labelWatchers);
+ labelWatchers = [];
+
+ var viewScope = $breadcrumb.$getLastViewScope();
+ var lastStep = $breadcrumb.getLastStep();
+ if(lastStep) {
+ scope.ncyBreadcrumbLink = lastStep.ncyBreadcrumbLink;
+ if (lastStep.ncyBreadcrumb && lastStep.ncyBreadcrumb.label) {
+ var parseLabel = $interpolate(lastStep.ncyBreadcrumb.label);
+ scope.ncyBreadcrumbLabel = parseLabel(viewScope);
+ // Watcher for further viewScope updates
+ // Tricky last arg: the last step is the entire scope of the directive !
+ registerWatchers(labelWatchers, parseLabel, viewScope, scope);
+ } else {
+ scope.ncyBreadcrumbLabel = lastStep.name;
+ }
+ }
+ };
+
+ $rootScope.$on('$viewContentLoaded', function (event) {
+ if(!event.targetScope.ncyBreadcrumbIgnore) {
+ renderLabel();
+ }
+ });
+
+ // View(s) may be already loaded while the directive's linking
+ renderLabel();
+ }
+ };
+
+ }
+ };
+}
+BreadcrumbLastDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];
+
+function BreadcrumbTextDirective($interpolate, $breadcrumb, $rootScope) {
+
+ return {
+ restrict: 'A',
+ scope: {},
+ template: '{{ncyBreadcrumbChain}}',
+
+ compile: function(cElement, cAttrs) {
+ // Override the default template if ncyBreadcrumbText has a value
+ var template = cElement.attr(cAttrs.$attr.ncyBreadcrumbText);
+ if(template) {
+ cElement.html(template);
+ }
+
+ var separator = cElement.attr(cAttrs.$attr.ncyBreadcrumbTextSeparator) || ' / ';
+
+ return {
+ post: function postLink(scope) {
+ var labelWatchers = [];
+
+ var registerWatchersText = function(labelWatcherArray, interpolationFunction, viewScope) {
+ angular.forEach(getExpression(interpolationFunction), function(expression) {
+ var watcher = viewScope.$watch(expression, function(newValue, oldValue) {
+ if (newValue !== oldValue) {
+ renderLabel();
+ }
+ });
+ labelWatcherArray.push(watcher);
+ });
+ };
+
+ var renderLabel = function() {
+ deregisterWatchers(labelWatchers);
+ labelWatchers = [];
+
+ var viewScope = $breadcrumb.$getLastViewScope();
+ var steps = $breadcrumb.getStatesChain();
+ var combinedLabels = [];
+ angular.forEach(steps, function (step) {
+ if (step.ncyBreadcrumb && step.ncyBreadcrumb.label) {
+ var parseLabel = $interpolate(step.ncyBreadcrumb.label);
+ combinedLabels.push(parseLabel(viewScope));
+ // Watcher for further viewScope updates
+ registerWatchersText(labelWatchers, parseLabel, viewScope);
+ } else {
+ combinedLabels.push(step.name);
+ }
+ });
+
+ scope.ncyBreadcrumbChain = combinedLabels.join(separator);
+ };
+
+ $rootScope.$on('$viewContentLoaded', function (event) {
+ if(!event.targetScope.ncyBreadcrumbIgnore) {
+ renderLabel();
+ }
+ });
+
+ // View(s) may be already loaded while the directive's linking
+ renderLabel();
+ }
+ };
+
+ }
+ };
+}
+BreadcrumbTextDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];
+
+angular.module('ncy-angular-breadcrumb', ['ui.router.state'])
+ .provider('$breadcrumb', $Breadcrumb)
+ .directive('ncyBreadcrumb', BreadcrumbDirective)
+ .directive('ncyBreadcrumbLast', BreadcrumbLastDirective)
+ .directive('ncyBreadcrumbText', BreadcrumbTextDirective);
+})(window, window.angular);
diff --git a/awx/ui/client/lib/angular-breadcrumb/dist/angular-breadcrumb.min.js b/awx/ui/client/lib/angular-breadcrumb/dist/angular-breadcrumb.min.js
new file mode 100644
index 0000000000..1da045d879
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/dist/angular-breadcrumb.min.js
@@ -0,0 +1,4 @@
+/*! angular-breadcrumb - v0.4.0-dev-2015-08-07
+* http://ncuillery.github.io/angular-breadcrumb
+* Copyright (c) 2015 Nicolas Cuillery; Licensed MIT */
+!function(a,b,c){"use strict";function d(a,c){return b.equals(a.length,c.length)?a>c:a.length>c.length}function e(a){var b=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/);if(!b||4!==b.length)throw new Error("Invalid state ref '"+a+"'");return{state:b[1],paramExpr:b[3]||null}}function f(){var a={prefixStateName:null,template:"bootstrap3",templateUrl:null,includeAbstract:!1};this.setOptions=function(c){b.extend(a,c)},this.$get=["$state","$stateParams","$rootScope",function(b,f,g){var h=g;g.$on("$viewContentLoaded",function(a){!a.targetScope.ncyBreadcrumbIgnore&&d(a.targetScope.$id,h.$id)&&(h=a.targetScope)});var i=function(a){var b=a.parent||(/^(.+)\.[^.]+$/.exec(a.name)||[])[1],c="object"==typeof b;return c?b.name:b},j=function(c,d){for(var g,i,j=e(d),k=!1,l=!1,m=0,n=c.length;n>m;m+=1)if(c[m].name===j.state)return;g=b.get(j.state),g.ncyBreadcrumb&&(g.ncyBreadcrumb.force&&(k=!0),g.ncyBreadcrumb.skip&&(l=!0)),g["abstract"]&&!a.includeAbstract&&!k||l||(j.paramExpr&&(i=h.$eval(j.paramExpr)),g.ncyBreadcrumbLink=b.href(j.state,i||f||{}),c.unshift(g))},k=function(a){var c=e(a),d=b.get(c.state);if(d.ncyBreadcrumb&&d.ncyBreadcrumb.parent){var f="function"==typeof d.ncyBreadcrumb.parent,g=f?d.ncyBreadcrumb.parent(h):d.ncyBreadcrumb.parent;if(g)return g}return i(d)};return{getTemplate:function(b){return a.templateUrl?null:b[a.template]?b[a.template]:a.template},getTemplateUrl:function(){return a.templateUrl},getStatesChain:function(c){for(var d=[],e=b.$current.self.name;e;e=k(e))if(j(d,e),c&&d.length)return d;return a.prefixStateName&&j(d,a.prefixStateName),d},getLastStep:function(){var a=this.getStatesChain(!0);return a.length?a[0]:c},$getLastViewScope:function(){return h}}}]}function g(a,c,d){var e={bootstrap2:'',bootstrap3:'- {{step.ncyBreadcrumbLabel}}{{step.ncyBreadcrumbLabel}}
'};return{restrict:"AE",replace:!0,scope:{},template:c.getTemplate(e),templateUrl:c.getTemplateUrl(),link:{post:function(e){var f=[],g=function(){l(f),f=[];var d=c.$getLastViewScope();e.steps=c.getStatesChain(),b.forEach(e.steps,function(b){if(b.ncyBreadcrumb&&b.ncyBreadcrumb.label){var c=a(b.ncyBreadcrumb.label);b.ncyBreadcrumbLabel=c(d),k(f,c,d,b)}else b.ncyBreadcrumbLabel=b.name})};d.$on("$viewContentLoaded",function(a){a.targetScope.ncyBreadcrumbIgnore||g()}),g()}}}}function h(a,b,c){return{restrict:"A",scope:{},template:"{{ncyBreadcrumbLabel}}",compile:function(d,e){var f=d.attr(e.$attr.ncyBreadcrumbLast);return f&&d.html(f),{post:function(d){var e=[],f=function(){l(e),e=[];var c=b.$getLastViewScope(),f=b.getLastStep();if(f)if(d.ncyBreadcrumbLink=f.ncyBreadcrumbLink,f.ncyBreadcrumb&&f.ncyBreadcrumb.label){var g=a(f.ncyBreadcrumb.label);d.ncyBreadcrumbLabel=g(c),k(e,g,c,d)}else d.ncyBreadcrumbLabel=f.name};c.$on("$viewContentLoaded",function(a){a.targetScope.ncyBreadcrumbIgnore||f()}),f()}}}}}function i(a,c,d){return{restrict:"A",scope:{},template:"{{ncyBreadcrumbChain}}",compile:function(e,f){var g=e.attr(f.$attr.ncyBreadcrumbText);g&&e.html(g);var h=e.attr(f.$attr.ncyBreadcrumbTextSeparator)||" / ";return{post:function(e){var f=[],g=function(a,c,d){b.forEach(j(c),function(b){var c=d.$watch(b,function(a,b){a!==b&&i()});a.push(c)})},i=function(){l(f),f=[];var d=c.$getLastViewScope(),i=c.getStatesChain(),j=[];b.forEach(i,function(b){if(b.ncyBreadcrumb&&b.ncyBreadcrumb.label){var c=a(b.ncyBreadcrumb.label);j.push(c(d)),g(f,c,d)}else j.push(b.name)}),e.ncyBreadcrumbChain=j.join(h)};d.$on("$viewContentLoaded",function(a){a.targetScope.ncyBreadcrumbIgnore||i()}),i()}}}}}var j=function(a){if(a.expressions)return a.expressions;var c=[];return b.forEach(a.parts,function(a){b.isFunction(a)&&c.push(a.exp)}),c},k=function(a,c,d,e){b.forEach(j(c),function(b){var f=d.$watch(b,function(){e.ncyBreadcrumbLabel=c(d)});a.push(f)})},l=function(a){b.forEach(a,function(a){a()})};g.$inject=["$interpolate","$breadcrumb","$rootScope"],h.$inject=["$interpolate","$breadcrumb","$rootScope"],i.$inject=["$interpolate","$breadcrumb","$rootScope"],b.module("ncy-angular-breadcrumb",["ui.router.state"]).provider("$breadcrumb",f).directive("ncyBreadcrumb",g).directive("ncyBreadcrumbLast",h).directive("ncyBreadcrumbText",i)}(window,window.angular);
\ No newline at end of file
diff --git a/awx/ui/client/lib/angular-breadcrumb/release/angular-breadcrumb.js b/awx/ui/client/lib/angular-breadcrumb/release/angular-breadcrumb.js
new file mode 100644
index 0000000000..ba6e5dcd39
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/release/angular-breadcrumb.js
@@ -0,0 +1,369 @@
+/*! angular-breadcrumb - v0.4.1
+* http://ncuillery.github.io/angular-breadcrumb
+* Copyright (c) 2015 Nicolas Cuillery; Licensed MIT */
+
+(function (window, angular, undefined) {
+'use strict';
+
+function isAOlderThanB(scopeA, scopeB) {
+ if(angular.equals(scopeA.length, scopeB.length)) {
+ return scopeA > scopeB;
+ } else {
+ return scopeA.length > scopeB.length;
+ }
+}
+
+function parseStateRef(ref) {
+ var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
+ if (!parsed || parsed.length !== 4) { throw new Error("Invalid state ref '" + ref + "'"); }
+ return { state: parsed[1], paramExpr: parsed[3] || null };
+}
+
+function $Breadcrumb() {
+
+ var $$options = {
+ prefixStateName: null,
+ template: 'bootstrap3',
+ templateUrl: null,
+ includeAbstract : false
+ };
+
+ this.setOptions = function(options) {
+ angular.extend($$options, options);
+ };
+
+ this.$get = ['$state', '$stateParams', '$rootScope', function($state, $stateParams, $rootScope) {
+
+ var $lastViewScope = $rootScope;
+
+ // Early catch of $viewContentLoaded event
+ $rootScope.$on('$viewContentLoaded', function (event) {
+ // With nested views, the event occur several times, in "wrong" order
+ if(!event.targetScope.ncyBreadcrumbIgnore &&
+ isAOlderThanB(event.targetScope.$id, $lastViewScope.$id)) {
+ $lastViewScope = event.targetScope;
+ }
+ });
+
+ // Get the parent state
+ var $$parentState = function(state) {
+ // Check if state has explicit parent OR we try guess parent from its name
+ var parent = state.parent || (/^(.+)\.[^.]+$/.exec(state.name) || [])[1];
+ var isObjectParent = typeof parent === "object";
+ // if parent is a object reference, then extract the name
+ return isObjectParent ? parent.name : parent;
+ };
+
+ // Add the state in the chain if not already in and if not abstract
+ var $$addStateInChain = function(chain, stateRef) {
+ var conf,
+ parentParams,
+ ref = parseStateRef(stateRef),
+ force = false,
+ skip = false;
+
+ for(var i=0, l=chain.length; i' +
+ '' +
+ '{{step.ncyBreadcrumbLabel}}' +
+ '{{step.ncyBreadcrumbLabel}}' +
+ '/' +
+ '' +
+ '',
+ bootstrap3: '' +
+ '- ' +
+ '{{step.ncyBreadcrumbLabel}}' +
+ '{{step.ncyBreadcrumbLabel}}' +
+ '
' +
+ '
'
+ };
+
+ return {
+ restrict: 'AE',
+ replace: true,
+ scope: {},
+ template: $breadcrumb.getTemplate($$templates),
+ templateUrl: $breadcrumb.getTemplateUrl(),
+ link: {
+ post: function postLink(scope) {
+ var labelWatchers = [];
+
+ var renderBreadcrumb = function() {
+ deregisterWatchers(labelWatchers);
+ labelWatchers = [];
+
+ var viewScope = $breadcrumb.$getLastViewScope();
+ scope.steps = $breadcrumb.getStatesChain();
+ angular.forEach(scope.steps, function (step) {
+ if (step.ncyBreadcrumb && step.ncyBreadcrumb.label) {
+ var parseLabel = $interpolate(step.ncyBreadcrumb.label);
+ step.ncyBreadcrumbLabel = parseLabel(viewScope);
+ // Watcher for further viewScope updates
+ registerWatchers(labelWatchers, parseLabel, viewScope, step);
+ } else {
+ step.ncyBreadcrumbLabel = step.name;
+ }
+ });
+ };
+
+ $rootScope.$on('$viewContentLoaded', function (event) {
+ if(!event.targetScope.ncyBreadcrumbIgnore) {
+ renderBreadcrumb();
+ }
+ });
+
+ // View(s) may be already loaded while the directive's linking
+ renderBreadcrumb();
+ }
+ }
+ };
+}
+BreadcrumbDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];
+
+function BreadcrumbLastDirective($interpolate, $breadcrumb, $rootScope) {
+
+ return {
+ restrict: 'A',
+ scope: {},
+ template: '{{ncyBreadcrumbLabel}}',
+ compile: function(cElement, cAttrs) {
+
+ // Override the default template if ncyBreadcrumbLast has a value
+ var template = cElement.attr(cAttrs.$attr.ncyBreadcrumbLast);
+ if(template) {
+ cElement.html(template);
+ }
+
+ return {
+ post: function postLink(scope) {
+ var labelWatchers = [];
+
+ var renderLabel = function() {
+ deregisterWatchers(labelWatchers);
+ labelWatchers = [];
+
+ var viewScope = $breadcrumb.$getLastViewScope();
+ var lastStep = $breadcrumb.getLastStep();
+ if(lastStep) {
+ scope.ncyBreadcrumbLink = lastStep.ncyBreadcrumbLink;
+ if (lastStep.ncyBreadcrumb && lastStep.ncyBreadcrumb.label) {
+ var parseLabel = $interpolate(lastStep.ncyBreadcrumb.label);
+ scope.ncyBreadcrumbLabel = parseLabel(viewScope);
+ // Watcher for further viewScope updates
+ // Tricky last arg: the last step is the entire scope of the directive !
+ registerWatchers(labelWatchers, parseLabel, viewScope, scope);
+ } else {
+ scope.ncyBreadcrumbLabel = lastStep.name;
+ }
+ }
+ };
+
+ $rootScope.$on('$viewContentLoaded', function (event) {
+ if(!event.targetScope.ncyBreadcrumbIgnore) {
+ renderLabel();
+ }
+ });
+
+ // View(s) may be already loaded while the directive's linking
+ renderLabel();
+ }
+ };
+
+ }
+ };
+}
+BreadcrumbLastDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];
+
+function BreadcrumbTextDirective($interpolate, $breadcrumb, $rootScope) {
+
+ return {
+ restrict: 'A',
+ scope: {},
+ template: '{{ncyBreadcrumbChain}}',
+
+ compile: function(cElement, cAttrs) {
+ // Override the default template if ncyBreadcrumbText has a value
+ var template = cElement.attr(cAttrs.$attr.ncyBreadcrumbText);
+ if(template) {
+ cElement.html(template);
+ }
+
+ var separator = cElement.attr(cAttrs.$attr.ncyBreadcrumbTextSeparator) || ' / ';
+
+ return {
+ post: function postLink(scope) {
+ var labelWatchers = [];
+
+ var registerWatchersText = function(labelWatcherArray, interpolationFunction, viewScope) {
+ angular.forEach(getExpression(interpolationFunction), function(expression) {
+ var watcher = viewScope.$watch(expression, function(newValue, oldValue) {
+ if (newValue !== oldValue) {
+ renderLabel();
+ }
+ });
+ labelWatcherArray.push(watcher);
+ });
+ };
+
+ var renderLabel = function() {
+ deregisterWatchers(labelWatchers);
+ labelWatchers = [];
+
+ var viewScope = $breadcrumb.$getLastViewScope();
+ var steps = $breadcrumb.getStatesChain();
+ var combinedLabels = [];
+ angular.forEach(steps, function (step) {
+ if (step.ncyBreadcrumb && step.ncyBreadcrumb.label) {
+ var parseLabel = $interpolate(step.ncyBreadcrumb.label);
+ combinedLabels.push(parseLabel(viewScope));
+ // Watcher for further viewScope updates
+ registerWatchersText(labelWatchers, parseLabel, viewScope);
+ } else {
+ combinedLabels.push(step.name);
+ }
+ });
+
+ scope.ncyBreadcrumbChain = combinedLabels.join(separator);
+ };
+
+ $rootScope.$on('$viewContentLoaded', function (event) {
+ if(!event.targetScope.ncyBreadcrumbIgnore) {
+ renderLabel();
+ }
+ });
+
+ // View(s) may be already loaded while the directive's linking
+ renderLabel();
+ }
+ };
+
+ }
+ };
+}
+BreadcrumbTextDirective.$inject = ['$interpolate', '$breadcrumb', '$rootScope'];
+
+angular.module('ncy-angular-breadcrumb', ['ui.router.state'])
+ .provider('$breadcrumb', $Breadcrumb)
+ .directive('ncyBreadcrumb', BreadcrumbDirective)
+ .directive('ncyBreadcrumbLast', BreadcrumbLastDirective)
+ .directive('ncyBreadcrumbText', BreadcrumbTextDirective);
+})(window, window.angular);
diff --git a/awx/ui/client/lib/angular-breadcrumb/release/angular-breadcrumb.min.js b/awx/ui/client/lib/angular-breadcrumb/release/angular-breadcrumb.min.js
new file mode 100644
index 0000000000..ceb49da17e
--- /dev/null
+++ b/awx/ui/client/lib/angular-breadcrumb/release/angular-breadcrumb.min.js
@@ -0,0 +1,4 @@
+/*! angular-breadcrumb - v0.4.1
+* http://ncuillery.github.io/angular-breadcrumb
+* Copyright (c) 2015 Nicolas Cuillery; Licensed MIT */
+!function(a,b,c){"use strict";function d(a,c){return b.equals(a.length,c.length)?a>c:a.length>c.length}function e(a){var b=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/);if(!b||4!==b.length)throw new Error("Invalid state ref '"+a+"'");return{state:b[1],paramExpr:b[3]||null}}function f(){var a={prefixStateName:null,template:"bootstrap3",templateUrl:null,includeAbstract:!1};this.setOptions=function(c){b.extend(a,c)},this.$get=["$state","$stateParams","$rootScope",function(b,f,g){var h=g;g.$on("$viewContentLoaded",function(a){!a.targetScope.ncyBreadcrumbIgnore&&d(a.targetScope.$id,h.$id)&&(h=a.targetScope)});var i=function(a){var b=a.parent||(/^(.+)\.[^.]+$/.exec(a.name)||[])[1],c="object"==typeof b;return c?b.name:b},j=function(c,d){for(var g,i,j=e(d),k=!1,l=!1,m=0,n=c.length;n>m;m+=1)if(c[m].name===j.state)return;g=b.get(j.state),g.ncyBreadcrumb&&(g.ncyBreadcrumb.force&&(k=!0),g.ncyBreadcrumb.skip&&(l=!0)),g["abstract"]&&!a.includeAbstract&&!k||l||(j.paramExpr&&(i=h.$eval(j.paramExpr)),g.ncyBreadcrumbLink=b.href(j.state,i||f||{}),c.unshift(g))},k=function(a){var c=e(a),d=b.get(c.state);if(d.ncyBreadcrumb&&d.ncyBreadcrumb.parent){var f="function"==typeof d.ncyBreadcrumb.parent,g=f?d.ncyBreadcrumb.parent(h):d.ncyBreadcrumb.parent;if(g)return g}return i(d)};return{getTemplate:function(b){return a.templateUrl?null:b[a.template]?b[a.template]:a.template},getTemplateUrl:function(){return a.templateUrl},getStatesChain:function(c){for(var d=[],e=b.$current.self.name;e;e=k(e))if(j(d,e),c&&d.length)return d;return a.prefixStateName&&j(d,a.prefixStateName),d},getLastStep:function(){var a=this.getStatesChain(!0);return a.length?a[0]:c},$getLastViewScope:function(){return h}}}]}function g(a,c,d){var e={bootstrap2:'',bootstrap3:'- {{step.ncyBreadcrumbLabel}}{{step.ncyBreadcrumbLabel}}
'};return{restrict:"AE",replace:!0,scope:{},template:c.getTemplate(e),templateUrl:c.getTemplateUrl(),link:{post:function(e){var f=[],g=function(){l(f),f=[];var d=c.$getLastViewScope();e.steps=c.getStatesChain(),b.forEach(e.steps,function(b){if(b.ncyBreadcrumb&&b.ncyBreadcrumb.label){var c=a(b.ncyBreadcrumb.label);b.ncyBreadcrumbLabel=c(d),k(f,c,d,b)}else b.ncyBreadcrumbLabel=b.name})};d.$on("$viewContentLoaded",function(a){a.targetScope.ncyBreadcrumbIgnore||g()}),g()}}}}function h(a,b,c){return{restrict:"A",scope:{},template:"{{ncyBreadcrumbLabel}}",compile:function(d,e){var f=d.attr(e.$attr.ncyBreadcrumbLast);return f&&d.html(f),{post:function(d){var e=[],f=function(){l(e),e=[];var c=b.$getLastViewScope(),f=b.getLastStep();if(f)if(d.ncyBreadcrumbLink=f.ncyBreadcrumbLink,f.ncyBreadcrumb&&f.ncyBreadcrumb.label){var g=a(f.ncyBreadcrumb.label);d.ncyBreadcrumbLabel=g(c),k(e,g,c,d)}else d.ncyBreadcrumbLabel=f.name};c.$on("$viewContentLoaded",function(a){a.targetScope.ncyBreadcrumbIgnore||f()}),f()}}}}}function i(a,c,d){return{restrict:"A",scope:{},template:"{{ncyBreadcrumbChain}}",compile:function(e,f){var g=e.attr(f.$attr.ncyBreadcrumbText);g&&e.html(g);var h=e.attr(f.$attr.ncyBreadcrumbTextSeparator)||" / ";return{post:function(e){var f=[],g=function(a,c,d){b.forEach(j(c),function(b){var c=d.$watch(b,function(a,b){a!==b&&i()});a.push(c)})},i=function(){l(f),f=[];var d=c.$getLastViewScope(),i=c.getStatesChain(),j=[];b.forEach(i,function(b){if(b.ncyBreadcrumb&&b.ncyBreadcrumb.label){var c=a(b.ncyBreadcrumb.label);j.push(c(d)),g(f,c,d)}else j.push(b.name)}),e.ncyBreadcrumbChain=j.join(h)};d.$on("$viewContentLoaded",function(a){a.targetScope.ncyBreadcrumbIgnore||i()}),i()}}}}}var j=function(a){if(a.expressions)return a.expressions;var c=[];return b.forEach(a.parts,function(a){b.isFunction(a)&&c.push(a.exp)}),c},k=function(a,c,d,e){b.forEach(j(c),function(b){var f=d.$watch(b,function(){e.ncyBreadcrumbLabel=c(d)});a.push(f)})},l=function(a){b.forEach(a,function(a){a()})};g.$inject=["$interpolate","$breadcrumb","$rootScope"],h.$inject=["$interpolate","$breadcrumb","$rootScope"],i.$inject=["$interpolate","$breadcrumb","$rootScope"],b.module("ncy-angular-breadcrumb",["ui.router.state"]).provider("$breadcrumb",f).directive("ncyBreadcrumb",g).directive("ncyBreadcrumbLast",h).directive("ncyBreadcrumbText",i)}(window,window.angular);
\ No newline at end of file
diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index e3ed9e423c..896a9eb211 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -186,7 +186,8 @@ var tower = angular.module('Tower', [
'features',
'longDateFilter',
'pendolytics',
- 'ui.router'
+ 'ui.router',
+ 'ncy-angular-breadcrumb'
])
.constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/')
@@ -773,15 +774,15 @@ var tower = angular.module('Tower', [
state('dashboard', {
url: '/home',
templateUrl: urlPrefix + 'partials/home.html',
- controller: Home,
- resolve: {
- graphData: ['$q', 'jobStatusGraphData', 'FeaturesService', function($q, jobStatusGraphData, FeaturesService) {
- return $q.all({
- jobStatus: jobStatusGraphData.get("month", "all"),
- features: FeaturesService.get()
- });
- }]
- }
+ controller: Home
+ // resolve: {
+ // graphData: ['$q', 'jobStatusGraphData', 'FeaturesService', function($q, jobStatusGraphData, FeaturesService) {
+ // return $q.all({
+ // jobStatus: jobStatusGraphData.get("month", "all"),
+ // features: FeaturesService.get()
+ // });
+ // }]
+ // }
}).
state('dashboardGroups', {