diff --git a/app/assets/stylesheets/katello/widgets/path_selector.scss b/app/assets/stylesheets/katello/widgets/path_selector.scss
index 6b3a6249c5a..80c6d76cdc0 100644
--- a/app/assets/stylesheets/katello/widgets/path_selector.scss
+++ b/app/assets/stylesheets/katello/widgets/path_selector.scss
@@ -116,8 +116,7 @@ $widget-background-color: #F9F8F8;
display: block;
font-weight: normal;
font-size: 10px;
- margin-bottom: 0;
- height: 37px;
+ margin: 0;
.checkbox_holder {
float: left;
diff --git a/app/controllers/katello/content_views_controller.rb b/app/controllers/katello/content_views_controller.rb
new file mode 100644
index 00000000000..0c18be69a6c
--- /dev/null
+++ b/app/controllers/katello/content_views_controller.rb
@@ -0,0 +1,36 @@
+#
+# Copyright 2013 Red Hat, Inc.
+#
+# This software is licensed to you under the GNU General Public
+# License as published by the Free Software Foundation; either version
+# 2 of the License (GPLv2) or (at your option) any later version.
+# There is NO WARRANTY for this software, express or implied,
+# including the implied warranties of MERCHANTABILITY,
+# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+# have received a copy of GPLv2 along with this software; if not, see
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+
+module Katello
+class ContentViewsController < Katello::ApplicationController
+
+ before_filter :authorize
+
+ def rules
+ index_rule = lambda { true }
+
+ {
+ :index => index_rule,
+ :all => index_rule
+ }
+ end
+
+ def index
+ render 'bastion/layouts/application', :layout => false
+ end
+
+ def all
+ redirect_to :action => 'index', :anchor => '/content_views'
+ end
+
+end
+end
diff --git a/app/lib/katello/navigation/items/content_views.rb b/app/lib/katello/navigation/items/content_views.rb
new file mode 100644
index 00000000000..c9a7ce14a90
--- /dev/null
+++ b/app/lib/katello/navigation/items/content_views.rb
@@ -0,0 +1,28 @@
+#
+# Copyright 2013 Red Hat, Inc.
+#
+# This software is licensed to you under the GNU General Public
+# License as published by the Free Software Foundation; either version
+# 2 of the License (GPLv2) or (at your option) any later version.
+# There is NO WARRANTY for this software, express or implied,
+# including the implied warranties of MERCHANTABILITY,
+# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+# have received a copy of GPLv2 along with this software; if not, see
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+
+module Katello
+ module Navigation
+ module Items
+ class ContentViews < Navigation::Item
+
+ def initialize(organization)
+ @key = :content_views
+ @display = _("Content Views (new)")
+ @authorization = lambda{ ContentView.any_readable?(organization) }
+ @url = content_views_path + '#/content_views'
+ end
+
+ end
+ end
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index d8925d066f1..2b7ad4b0067 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -65,6 +65,12 @@
end
end
+ resources :content_views, :only => [:index] do
+ collection do
+ get :all
+ end
+ end
+
resources :activation_keys do
collection do
get :auto_complete_search
diff --git a/engines/bastion/app/assets/javascripts/bastion/bastion.js b/engines/bastion/app/assets/javascripts/bastion/bastion.js
index 38dceddb9c6..52514d05df1 100644
--- a/engines/bastion/app/assets/javascripts/bastion/bastion.js
+++ b/engines/bastion/app/assets/javascripts/bastion/bastion.js
@@ -20,6 +20,7 @@
//= require "bastion/angular-gettext/angular-gettext"
//= require "bastion/angular-blocks/angular-blocks"
//= require_tree "../../../../vendor/assets/javascripts/bastion/angular-bootstrap"
+//= require "bastion/angular-animate/angular-animate"
//= require "bastion/alchemy/alchemy"
//= require "katello/common/katello.global"
@@ -59,6 +60,7 @@
//= require "bastion/content-views/content-views.module"
//= require_tree "./content-views"
+//= stub "bastion/content-views/content-view.factory"
//= require "bastion/errata/errata.module"
//= require_tree "./errata"
diff --git a/engines/bastion/app/assets/javascripts/bastion/bastion.module.js b/engines/bastion/app/assets/javascripts/bastion/bastion.module.js
index 3860c37d4cd..adc0941e9a3 100644
--- a/engines/bastion/app/assets/javascripts/bastion/bastion.module.js
+++ b/engines/bastion/app/assets/javascripts/bastion/bastion.module.js
@@ -26,6 +26,7 @@ angular.module('Bastion', [
'ngSanitize',
'ui.bootstrap',
'ui.bootstrap.tpls',
+ 'ngAnimate',
'angular-blocks',
'Bastion.i18n',
'Bastion.menu',
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/content-view-mock.factory.js b/engines/bastion/app/assets/javascripts/bastion/content-views/content-view-mock.factory.js
new file mode 100644
index 00000000000..c0cf7478464
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/content-view-mock.factory.js
@@ -0,0 +1,274 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ **/
+
+/**
+ * @ngdoc service
+ * @name Bastion.content-views.factory:ContentView
+ *
+ * @description
+ * Provides a $Resource for interacting with environments.
+ */
+angular.module('Bastion.content-views').factory('ContentView',
+ [function($resource, Routes, CurrentOrganization) {
+
+ var Resource = function(id, create) {
+ var name = id ? 'Content View ' + id : '',
+ label = id ? 'content_view_' + id : '',
+ generatedVersions = [],
+ counts = {
+ products: 0,
+ repositories: 0,
+ puppet_modules: 0
+ };
+
+ this.id = id;
+
+ if (id !== undefined && !create) {
+ versions.call(this, function(response) {
+ generatedVersions = response.results;
+ });
+
+ counts = {
+ products: id + id,
+ repositories: id * id,
+ puppet_modules: id * id - id
+ };
+ }
+
+ return {
+ id: id,
+ name: name,
+ label: label,
+ created: new Date(),
+ environments: [
+ 'Library',
+ 'Dev'
+ ],
+ counts: counts,
+ user: 'mister manager',
+ permissions: {
+ editable: true
+ },
+ organization: {
+ id: 1,
+ name: 'ACME_Corporation'
+ },
+ $save: save,
+ $versions: versions,
+ $version: version,
+ versions: generatedVersions,
+ repositories: [],
+ filters: [],
+ $publish: publish,
+ $puppetModules: puppetModules,
+ $filters: filters,
+ $addFilter: addFilter
+ };
+ };
+
+ var Version = function(id) {
+ return {
+ id: id,
+ name: 'Version ' + id,
+ label: 'version_' + id,
+ promoted: new Date(),
+ environments: [
+ {
+ id: 1,
+ name: 'Library'
+ },{
+ id: 2,
+ name: 'Dev'
+ }
+ ],
+ counts: {
+ products: id + id,
+ repositories: id * id,
+ packages: id^id,
+ puppet_modules: id * id - id,
+ errata: {
+ bugs: id,
+ security: id + id,
+ enhancements: id
+ }
+ },
+ user: 'mister manager'
+ };
+ };
+
+ var Filter = function(id) {
+ return {
+ id: id,
+ name: 'Version ' + id,
+ created: new Date(),
+ description: '',
+ contentType: '',
+ counts: {
+ products: 0,
+ repositories: 0,
+ packages: 0,
+ puppet_modules: id * id - id,
+ errata: {
+ bugs: 0,
+ security: 0,
+ enhancements: 0
+ }
+ }
+ };
+ };
+
+ var save = function(successCallback, errorCallback) {
+ var view = new Resource(results.length + 1, true);
+
+ view.name = this.name;
+
+ results.push(view);
+ successCallback(view);
+ };
+
+ var versions = function(callback) {
+ var versions = generateVersions(this.id);
+
+ if (this.id < 10) {
+ this.versions = versions;
+ }
+
+ callback({
+ offset: 0,
+ total: versions.length,
+ subtotal: versions.length,
+ limit: 25,
+ search: "",
+ sort: {by: "name", order: "ASC"},
+ results: this.versions
+ });
+ };
+
+ var version = function(params, callback) {
+ var found;
+
+ angular.forEach(this.versions, function(version) {
+ if (params.toString() === version.id.toString()) {
+ found = version;
+ }
+ });
+
+ callback(found);
+ };
+
+ var publish = function(params, callback) {
+ var version = new Version(this.versions.length + 1);
+
+ version.name = params.name;
+
+ this.versions.push(version);
+ callback(version);
+ };
+
+ var puppetModules = function(callback) {
+ var modules = generatePuppetModules(this.id);
+
+ if (this.modules.length === 0) {
+ this.modules = modules;
+ }
+
+ callback({
+ offset: 0,
+ total: modules.length,
+ subtotal: modules.length,
+ limit: 25,
+ search: "",
+ sort: {by: "name", order: "ASC"},
+ results: this.modules
+ });
+ };
+
+ var filters = function(callback) {
+ var filters = this.filters;
+
+ callback({
+ offset: 0,
+ total: filters.length,
+ subtotal: filters.length,
+ limit: 25,
+ search: "",
+ sort: {by: "name", order: "ASC"},
+ results: filters
+ });
+ };
+
+ var addFilter = function(params, callback) {
+ var filter = new Filter(this.filters.length + 1);
+
+ filter.name = params.name;
+ filter.description = params.description;
+ filter.contentType = params.contentType;
+
+ this.filters.push(filter);
+ callback(filter);
+ };
+
+ var results = generateViews(10, save, versions, publish, puppetModules);
+
+ Resource.query = function(params, callback) {
+ callback({
+ offset: 0,
+ total: 10,
+ subtotal: 10,
+ limit: 25,
+ search: "",
+ sort: {by: "name", order: "ASC"},
+ results: results
+ });
+ };
+
+ Resource.get = function(params, callback) {
+ var view;
+
+ angular.forEach(results, function(result) {
+ if (params.id.toString() === result.id.toString()) {
+ view = result;
+ }
+ });
+
+ callback(view);
+
+ return view;
+ };
+
+ function generateViews(numViews, save, versions, publish, puppetModules) {
+ var views = [],
+ i;
+
+ for(i = 1; i <= numViews; i += 1) {
+ views.push(new Resource(i));
+ }
+
+ return views;
+ }
+
+ function generateVersions(numVersions) {
+ var versions = [],
+ i;
+
+ for(i = 1; i <= numVersions; i += 1) {
+ versions.push(new Version(i));
+ }
+
+ return versions;
+ }
+
+ return Resource;
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/content-view.factory.js b/engines/bastion/app/assets/javascripts/bastion/content-views/content-view.factory.js
index f6366467672..25c0242e02f 100644
--- a/engines/bastion/app/assets/javascripts/bastion/content-views/content-view.factory.js
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/content-view.factory.js
@@ -26,11 +26,14 @@ angular.module('Bastion.content-views').factory('ContentView',
['$resource', 'Routes', 'CurrentOrganization',
function ($resource, Routes, CurrentOrganization) {
- return $resource(Routes.apiOrganizationContentViewsPath(CurrentOrganization) + '/:id/:action',
+ return $resource('/content_views/:id/:action',
{id: '@id'},
{
- update: { method: 'PUT' },
- query: { method: 'GET', isArray: false}
+ update: {method: 'PUT'},
+ query: {method: 'GET', isArray: false},
+ versions: {method: 'GET', isArray: false, params: {action: versions}},
+ publish: {method: 'POST', params: {action: publish}},
+ puppetModules: {method: 'GET', isArray: false, params: {action: puppet_modules}}
}
);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.controller.js
new file mode 100644
index 00000000000..22fddff06af
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.controller.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewsController
+ *
+ * @requires $scope
+ * @requires $location
+ * @requires Nutupane
+ * @requires ContentView
+ * @requires CurrentOrganization
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewsController',
+ ['$scope', '$location', 'Nutupane', 'ContentView', 'CurrentOrganization',
+ function($scope, $location, Nutupane, ContentView, CurrentOrganization) {
+
+ var nutupane = new Nutupane(ContentView, {
+ 'organization_id': CurrentOrganization,
+ 'sort_by': 'name',
+ 'sort_order': 'ASC',
+ 'enabled' : true
+ });
+
+ $scope.table = nutupane.table;
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.routes.js b/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.routes.js
new file mode 100644
index 00000000000..1acada793f2
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.routes.js
@@ -0,0 +1,119 @@
+/**
+ Copyright 2013 Red Hat, Inc.
+
+ This software is licensed to you under the GNU General Public
+ License as published by the Free Software Foundation; either version
+ 2 of the License (GPLv2) or (at your option) any later version.
+ There is NO WARRANTY for this software, express or implied,
+ including the implied warranties of MERCHANTABILITY,
+ NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ have received a copy of GPLv2 along with this software; if not, see
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ **/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.config
+ *
+ * @requires $stateProvider
+ *
+ * @description
+ * State routes defined for the content views module.
+ */
+angular.module('Bastion.content-views').config(['$stateProvider', function($stateProvider) {
+ $stateProvider.state('content-views', {
+ abstract: true,
+ controller: 'ContentViewsController',
+ templateUrl: 'content-views/views/content-views.html'
+ })
+ .state('content-views.index', {
+ url: '/content_views',
+ views: {
+ 'table': {
+ templateUrl: 'content-views/views/content-views-table-full.html'
+ }
+ }
+ })
+
+ .state('content-views-new', {
+ url: '/content_views/new',
+ controller: 'NewContentViewController',
+ templateUrl: 'content-views/new/views/content-view-new.html'
+ })
+
+ .state('content-views-details', {
+ abstract: true,
+ url: '/content_views/:contentViewId',
+ controller: 'ContentViewDetailsController',
+ templateUrl: 'content-views/details/views/content-view-details.html'
+ })
+ .state('content-views-details.versions', {
+ url: '/versions',
+ controller: 'ContentViewVersionsController',
+ templateUrl: 'content-views/details/views/content-view-details-versions.html'
+ })
+ .state('content-views-details.promotion', {
+ url: '/versions/:versionId/promotion',
+ controller: 'ContentViewPromotionController',
+ templateUrl: 'content-views/details/views/content-view-promotion.html'
+ })
+ .state('content-views-details.products', {
+ abstract: true,
+ template: '
'
+ })
+ .state('content-views-details.products.list', {
+ url: '/products',
+ controller: 'ContentViewProductsController',
+ templateUrl: 'content-views/details/views/content-view-details-products.html'
+ })
+ .state('content-views-details.products.available', {
+ url: '/products/available',
+ controller: 'ContentViewAvailableProductsController',
+ templateUrl: 'content-views/details/views/content-view-details-products.html'
+ })
+ .state('content-views-details.puppet-modules', {
+ url: '/puppet_modules',
+ controller: 'ContentViewPuppetModulesController',
+ templateUrl: 'content-views/details/views/content-view-details-puppet-modules.html'
+ })
+ .state('content-views-details.info', {
+ controller: 'ContentViewInfoController',
+ templateUrl: 'content-views/details/views/content-view-details-info.html'
+ })
+ .state('content-views-details.publish', {
+ url: '/publish',
+ controller: 'ContentViewPublishController',
+ templateUrl: 'content-views/details/views/content-view-details-publish.html'
+ })
+
+ .state('content-views-details.filters', {
+ abstract: true,
+ controller: 'ContentViewFiltersController',
+ template: ''
+ })
+ .state('content-views-details.filters.list', {
+ url: '/filters',
+ templateUrl: 'content-views/details/filters/views/content-view-details-filters.html'
+ })
+ .state('content-views-details.filters.new', {
+ url: '/filters/new',
+ controller: 'ContentViewFiltersNewController',
+ templateUrl: 'content-views/details/filters/views/content-view-details-filters-new.html'
+ })
+ .state('content-views-details.filters.details', {
+ abstract: true,
+ controller: 'ContentViewFilterDetailsController',
+ templateUrl: 'content-views/details/filters/views/content-view-filter-details.html'
+ })
+ .state('content-views-details.filters.details.packages', {
+ url: '/filters/:filterId/packages',
+ controller: 'ContentViewFilterDetailsPackageController',
+ templateUrl: 'content-views/details/filters/views/content-view-filter-details-packages.html'
+ })
+ .state('content-views-details.filters.details.errata', {
+ url: '/filters/:filterId/errata',
+ controller: 'ContentViewFilterDetailsErrataController',
+ templateUrl: 'content-views/details/filters/views/content-view-filter-details-errata.html'
+ })
+
+}]);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-available-products.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-available-products.controller.js
new file mode 100644
index 00000000000..36fe0be0ae1
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-available-products.controller.js
@@ -0,0 +1,127 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewAvailableProductsController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewAvailableProductsController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.repositories = (function() {
+ var repositories = [
+ {
+ product: {name: 'test', id: 1},
+ id: '1',
+ name: 'Repo Name 87',
+ content_type: 'test'
+ },
+ {
+ product: {name: 'test2', id: 2},
+ id: '2',
+ name: 'Repo Name 55',
+ content_type: 'test'
+ },
+ {
+ product: {name: 'test', id: 1},
+ id: '3',
+ name: 'Repo Name 66',
+ content_type: 'test'
+ },
+ {
+ product: {name: 'test3', id: 3},
+ id: '4',
+ name: 'Repo Name 32',
+ content_type: 'test'
+ }],
+ find = function(repository) {
+ var found = false;
+
+ angular.forEach($scope.contentView.repositories, function(addedRepository) {
+ if (repository.id === addedRepository.id) {
+ found = repository;
+ }
+ });
+
+ return found;
+ },
+ available = [];
+
+
+ angular.forEach(repositories, function(repository) {
+ var found = find(repository);
+
+ if (!found) {
+ available.push(repository);
+ }
+ });
+
+ return available;
+ })();
+
+ $scope.products = extractProducts();
+
+ $scope.repositoryFilter = function(repository) {
+ if ($scope.product !== undefined && $scope.product !== null) {
+ if (repository.product.id === $scope.product.id) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ };
+
+ $scope.addRepositories = function() {
+ var kept = [];
+
+ angular.forEach($scope.repositories, function(repository) {
+ if (!repository.selected) {
+ repository.selected = false;
+ kept.push(repository);
+ } else {
+ $scope.contentView.repositories.push(repository);
+ }
+ });
+
+ $scope.repositories = kept;
+ };
+
+ $scope.selectAll = function(selected) {
+ angular.forEach($scope.repositories, function(repository) {
+ repository.selected = selected;
+ });
+ };
+
+ function extractProducts() {
+ var products = {};
+
+ angular.forEach($scope.repositories, function(repository) {
+ products[repository.product.id] = repository.product;
+ });
+
+ return products;
+ }
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-details.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-details.controller.js
new file mode 100644
index 00000000000..cf3828e7197
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-details.controller.js
@@ -0,0 +1,35 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewDetailsController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewDetailsController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ ContentView.get({id: $scope.$stateParams.contentViewId}, function(view) {
+ $scope.contentView = view;
+ });
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-info.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-info.controller.js
new file mode 100644
index 00000000000..0b806d49142
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-info.controller.js
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewInfoController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewInfoController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-products.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-products.controller.js
new file mode 100644
index 00000000000..e8770197f2e
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-products.controller.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewProductsController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewProductsController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.repositories = $scope.contentView.repositories;
+
+ $scope.products = extractProducts();
+
+ $scope.removeRepositories = function() {
+ var kept = [];
+
+ angular.forEach($scope.contentView.repositories, function(repository) {
+ if (!repository.selected) {
+ repository.selected = false;
+ kept.push(repository);
+ }
+ });
+
+ $scope.contentView.repositories = kept;
+ $scope.repositories = $scope.contentView.repositories;
+ };
+
+ $scope.selectAll = function(selected) {
+ angular.forEach($scope.contentView.repositories, function(repository) {
+ repository.selected = selected;
+ });
+ };
+
+ $scope.repositoryFilter = function(repository) {
+ if ($scope.product !== undefined && $scope.product !== null) {
+ if (repository.product.id === $scope.product.id) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ };
+
+ function extractProducts() {
+ var products = {};
+
+ angular.forEach($scope.repositories, function(repository) {
+ products[repository.product.id] = repository.product;
+ });
+
+ return products;
+ }
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-promotion.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-promotion.controller.js
new file mode 100644
index 00000000000..a6722e09def
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-promotion.controller.js
@@ -0,0 +1,74 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewPromotionController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewPromotionController',
+ ['$scope', 'ContentView', 'CurrentOrganization', '$http',
+ function($scope, ContentView, CurrentOrganization, $http) {
+
+ $scope.promotion = {};
+
+ $http.get('/katello/organizations/' + CurrentOrganization + '/environments/registerable_paths')
+ .success(function(paths) {
+
+ angular.forEach($scope.version.environments, function(environment) {
+ angular.forEach(paths, function(path) {
+ angular.forEach(path, function(item, index) {
+ if (environment.id.toString() === item.id.toString()) {
+ if (index + 1 < path.length) {
+ path[index + 1].selectable = true;
+ }
+ }
+ });
+ });
+ });
+
+ $scope.availableEnvironments = paths;
+ });
+
+ $scope.contentView.$version($scope.$stateParams.versionId, function(version) {
+ $scope.version = version;
+ });
+
+ $scope.$watch('setupSelector', function(selector) {
+ if (selector !== undefined) {
+ selector().then(function () {
+ angular.forEach($scope.version.environments, function(environment) {
+ $scope.pathSelector.set_selected(environment.id);
+ });
+ });
+ }
+ });
+
+ $scope.promote = function() {
+ angular.forEach($scope.availableEnvironments, function(path) {
+ if (path.selectable) {
+ $scope.contentView.version.environments.push(path);
+ }
+ });
+
+ $scope.transitionTo('content-views-details.versions', {contentViewId: $scope.contentView.id});
+ };
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-publish.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-publish.controller.js
new file mode 100644
index 00000000000..6f619b86e79
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-publish.controller.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewPublishController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewPublishController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.version = {};
+
+ $scope.publish = function(version) {
+ $scope.contentView.$publish(version, function() {
+ $scope.transitionTo('content-views-details.versions', {contentViewId: $scope.contentView.id});
+ });
+ };
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-puppet-modules.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-puppet-modules.controller.js
new file mode 100644
index 00000000000..b13af09de4b
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-puppet-modules.controller.js
@@ -0,0 +1,37 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewPuppetModulesController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewPuppetModulesController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.table = {};
+ $scope.contentView.$versions(function(versions) {
+ $scope.contentView.versions = versions.results;
+ $scope.table.rows = $scope.contentView.versions;
+ });
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-versions.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-versions.controller.js
new file mode 100644
index 00000000000..0c0a6c47816
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-versions.controller.js
@@ -0,0 +1,37 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewVersionsController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewVersionsController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.table = {};
+ $scope.contentView.$versions(function(versions) {
+ $scope.contentView.versions = versions.results;
+ $scope.table.rows = $scope.contentView.versions;
+ });
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-errata.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-errata.controller.js
new file mode 100644
index 00000000000..d99dcd668b4
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-errata.controller.js
@@ -0,0 +1,50 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewFilterDetailsErrataController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewFilterDetailsErrataController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.filter = $scope.contentView.filters[$scope.$stateParams.filterId - 1];
+ $scope.filterBy = 'date';
+ $scope.rule = {
+ type: "included",
+ beginDate: new Date(),
+ endDate: new Date()
+ };
+
+ $scope.filterRules = [];
+
+ $scope.addRule = function(rule) {
+ rule.added = new Date();
+ rule.detail = 'all';
+ $scope.filterRules.push(rule);
+ $scope.rule = {
+ type: "included"
+ };
+ };
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-package.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-package.controller.js
new file mode 100644
index 00000000000..ea6e6f9732c
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-package.controller.js
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewFilterDetailsController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewFilterDetailsPackageController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.filter = $scope.contentView.filters[$scope.$stateParams.filterId - 1];
+ $scope.rule = {
+ type: "included"
+ };
+
+ $scope.filterRules = [];
+
+ $scope.addRule = function(rule) {
+ rule.added = new Date();
+ rule.detail = 'all';
+ $scope.filterRules.push(rule);
+ $scope.rule = {
+ type: "included"
+ };
+ };
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details.controller.js
new file mode 100644
index 00000000000..6641fd08ccf
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details.controller.js
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewFilterDetailsController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewFilterDetailsController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.filter = $scope.contentView.filters[$scope.$stateParams.filterId - 1];
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters-new.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters-new.controller.js
new file mode 100644
index 00000000000..aa253cd31bc
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters-new.controller.js
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewFiltersNewController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewFiltersNewController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.filter = {};
+
+ $scope.save = function(filter) {
+ $scope.contentView.$addFilter(filter, function() {
+ console.log(filter);
+ $scope.transitionTo('content-views-details.filters.list', {contentViewId: $scope.contentView.id});
+ });
+ };
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters.controller.js
new file mode 100644
index 00000000000..6a349a3048f
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters.controller.js
@@ -0,0 +1,37 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:ContentViewFiltersController
+ *
+ * @requires $scope
+ * @requires ContentView
+ *
+ * @description
+ * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern.
+ * Defines the columns to display and the transform function for how to generate each row
+ * within the table.
+ */
+angular.module('Bastion.content-views').controller('ContentViewFiltersController',
+ ['$scope', 'ContentView',
+ function($scope, ContentView) {
+
+ $scope.table = {};
+ $scope.contentView.$filters(function(filters) {
+ $scope.contentView.filters = filters.results;
+ $scope.table.rows = $scope.contentView.filters;
+ });
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-details-filters-new.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-details-filters-new.html
new file mode 100644
index 00000000000..70fcfa18447
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-details-filters-new.html
@@ -0,0 +1,48 @@
+
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-publish.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-publish.html
new file mode 100644
index 00000000000..8b32a5b2be5
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-publish.html
@@ -0,0 +1,37 @@
+
+
+
Publish New Version
+
+
+ This latest version of the '{{ contentView.name }}' content view will be published to the
+ library. You can see this new version in the '{{ contentView.name }}' views datatable. From there
+ you are able to promote through any promotion path.
+
+
+
+
Version Details
+
+
+
+
+
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-puppet-modules.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-puppet-modules.html
new file mode 100644
index 00000000000..7f8ed70e69a
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-puppet-modules.html
@@ -0,0 +1,21 @@
+
+
+
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-versions.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-versions.html
new file mode 100644
index 00000000000..1f3c902f503
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-versions.html
@@ -0,0 +1,54 @@
+
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/new/content-view-new.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/new/content-view-new.controller.js
new file mode 100644
index 00000000000..d970036b6ed
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/new/content-view-new.controller.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This software is licensed to you under the GNU General Public
+ * License as published by the Free Software Foundation; either version
+ * 2 of the License (GPLv2) or (at your option) any later version.
+ * There is NO WARRANTY for this software, express or implied,
+ * including the implied warranties of MERCHANTABILITY,
+ * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
+ * have received a copy of GPLv2 along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+*/
+
+/**
+ * @ngdoc object
+ * @name Bastion.content-views.controller:NewContentViewController
+ *
+ * @requires $scope
+ * @requires ContentView
+ * @requires FormUtils
+ *
+ * @description
+ */
+angular.module('Bastion.content-views').controller('NewContentViewController',
+ ['$scope', 'ContentView', 'FormUtils',
+ function($scope, ContentView, FormUtils) {
+
+ $scope.contentView = new ContentView();
+ $scope.createOption = 'new';
+ $scope.table = {};
+
+ ContentView.query({}, function(response) {
+ $scope.table.rows = response.results;
+ });
+
+ $scope.save = function(contentView) {
+ contentView.$save(success, error);
+ };
+
+ $scope.$watch('contentView.name', function() {
+ if ($scope.contentViewForm.name) {
+ $scope.contentViewForm.name.$setValidity('server', true);
+ $scope.contentView.label = $scope.contentView.name;
+ //FormUtils.labelize($scope.contentView, $scope.contentViewForm);
+ }
+ });
+
+ function success(response) {
+ $scope.transitionTo('content-views-details.products.available', {contentViewId: response.id});
+ }
+
+ function error(response) {
+ console.log('error');
+ }
+
+ }]
+);
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/new/views/content-view-new.html b/engines/bastion/app/assets/javascripts/bastion/content-views/new/views/content-view-new.html
new file mode 100644
index 00000000000..9e61b1a8603
--- /dev/null
+++ b/engines/bastion/app/assets/javascripts/bastion/content-views/new/views/content-view-new.html
@@ -0,0 +1,81 @@
+
+
+
+
+ *
+ * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's
+ * animation has completed.
+ *
+ *
CSS-defined Animations
+ * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
+ * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
+ * and can be used to play along with this naming structure.
+ *
+ * The following code below demonstrates how to perform animations using **CSS transitions** with Angular:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * The following code below demonstrates how to perform animations using **CSS animations** with Angular:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing.
+ *
+ * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add
+ * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically
+ * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be
+ * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end
+ * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element
+ * has no CSS transition/animation classes applied to it.
+ *
+ *
CSS Staggering Animations
+ * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
+ * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be
+ * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
+ * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
+ * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
+ *
+ *
+ * .my-animation.ng-enter {
+ * /* standard transition code */
+ * -webkit-transition: 1s linear all;
+ * transition: 1s linear all;
+ * opacity:0;
+ * }
+ * .my-animation.ng-enter-stagger {
+ * /* this will have a 100ms delay between each successive leave animation */
+ * -webkit-transition-delay: 0.1s;
+ * transition-delay: 0.1s;
+ *
+ * /* in case the stagger doesn't work then these two values
+ * must be set to 0 to avoid an accidental CSS inheritance */
+ * -webkit-transition-duration: 0s;
+ * transition-duration: 0s;
+ * }
+ * .my-animation.ng-enter.ng-enter-active {
+ * /* standard transition styles */
+ * opacity:1;
+ * }
+ *
+ *
+ * Staggering animations work by default in ngRepeat (so long as the CSS class is defiend). Outside of ngRepeat, to use staggering animations
+ * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
+ * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
+ * will also be reset if more than 10ms has passed after the last animation has been fired.
+ *
+ * The following code will issue the **ng-leave-stagger** event on the element provided:
+ *
+ *
+ *
+ * Stagger animations are currently only supported within CSS-defined animations.
+ *
+ *
JavaScript-defined Animations
+ * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not
+ * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
+ *
+ *
+ * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
+ * var ngModule = angular.module('YourApp', []);
+ * ngModule.animation('.my-crazy-animation', function() {
+ * return {
+ * enter: function(element, done) {
+ * //run the animation here and call done when the animation is complete
+ * return function(cancelled) {
+ * //this (optional) function will be called when the animation
+ * //completes or when the animation is cancelled (the cancelled
+ * //flag will be set to true if cancelled).
+ * }
+ * }
+ * leave: function(element, done) { },
+ * move: function(element, done) { },
+ *
+ * //animation that can be triggered before the class is added
+ * beforeAddClass: function(element, className, done) { },
+ *
+ * //animation that can be triggered after the class is added
+ * addClass: function(element, className, done) { },
+ *
+ * //animation that can be triggered before the class is removed
+ * beforeRemoveClass: function(element, className, done) { },
+ *
+ * //animation that can be triggered after the class is removed
+ * removeClass: function(element, className, done) { }
+ * }
+ * });
+ *
+ *
+ * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
+ * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
+ * the element's CSS class attribute value and then run the matching animation event function (if found).
+ * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function
+ * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported).
+ *
+ * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
+ * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run,
+ * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation
+ * or transition code that is defined via a stylesheet).
+ *
+ */
+
+angular.module('ngAnimate', ['ng'])
+
+ /**
+ * @ngdoc object
+ * @name ngAnimate.$animateProvider
+ * @description
+ *
+ * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module.
+ * When an animation is triggered, the $animate service will query the $animate service to find any animations that match
+ * the provided name value.
+ *
+ * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
+ *
+ * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
+ *
+ */
+ .config(['$provide', '$animateProvider', function($provide, $animateProvider) {
+ var noop = angular.noop;
+ var forEach = angular.forEach;
+ var selectors = $animateProvider.$$selectors;
+
+ var ELEMENT_NODE = 1;
+ var NG_ANIMATE_STATE = '$$ngAnimateState';
+ var NG_ANIMATE_CLASS_NAME = 'ng-animate';
+ var rootAnimateState = {running: true};
+
+ $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
+ function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
+
+ $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
+
+ // disable animations during bootstrap, but once we bootstrapped, enable animations
+ $rootScope.$$postDigest(function() {
+ rootAnimateState.running = false;
+ });
+
+ function lookup(name) {
+ if (name) {
+ var matches = [],
+ flagMap = {},
+ classes = name.substr(1).split('.');
+
+ //the empty string value is the default animation
+ //operation which performs CSS transition and keyframe
+ //animations sniffing. This is always included for each
+ //element animation procedure if the browser supports
+ //transitions and/or keyframe animations
+ if ($sniffer.transitions || $sniffer.animations) {
+ classes.push('');
+ }
+
+ for(var i=0; i < classes.length; i++) {
+ var klass = classes[i],
+ selectorFactoryName = selectors[klass];
+ if(selectorFactoryName && !flagMap[klass]) {
+ matches.push($injector.get(selectorFactoryName));
+ flagMap[klass] = true;
+ }
+ }
+ return matches;
+ }
+ }
+
+ /**
+ * @ngdoc object
+ * @name ngAnimate.$animate
+ * @function
+ *
+ * @description
+ * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations.
+ * When any of these operations are run, the $animate service
+ * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object)
+ * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run.
+ *
+ * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives
+ * will work out of the box without any extra configuration.
+ *
+ * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
+ *
+ * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
+ *
+ */
+ return {
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#enter
+ * @methodOf ngAnimate.$animate
+ * @function
+ *
+ * @description
+ * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once
+ * the animation is started, the following CSS classes will be present on the element for the duration of the animation:
+ *
+ * Below is a breakdown of each step that occurs during enter animation:
+ *
+ * | Animation Step | What the element class attribute looks like |
+ * |----------------------------------------------------------------------------------------------|---------------------------------------------|
+ * | 1. $animate.enter(...) is called | class="my-animation" |
+ * | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" |
+ * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
+ * | 4. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" |
+ * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" |
+ * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-enter" |
+ * | 7. the .ng-enter-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
+ * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
+ * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
+ * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" |
+ *
+ * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
+ * @param {jQuery/jqLite element} parentElement the parent element of the element that will be the focus of the enter animation
+ * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
+ */
+ enter : function(element, parentElement, afterElement, doneCallback) {
+ this.enabled(false, element);
+ $delegate.enter(element, parentElement, afterElement);
+ $rootScope.$$postDigest(function() {
+ performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#leave
+ * @methodOf ngAnimate.$animate
+ * @function
+ *
+ * @description
+ * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
+ * the animation is started, the following CSS classes will be added for the duration of the animation:
+ *
+ * Below is a breakdown of each step that occurs during enter animation:
+ *
+ * | Animation Step | What the element class attribute looks like |
+ * |----------------------------------------------------------------------------------------------|---------------------------------------------|
+ * | 1. $animate.leave(...) is called | class="my-animation" |
+ * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
+ * | 3. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" |
+ * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" |
+ * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-leave" |
+ * | 6. the .ng-leave-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
+ * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
+ * | 9. The element is removed from the DOM | ... |
+ * | 10. The doneCallback() callback is fired (if provided) | ... |
+ *
+ * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
+ */
+ leave : function(element, doneCallback) {
+ cancelChildAnimations(element);
+ this.enabled(false, element);
+ $rootScope.$$postDigest(function() {
+ performAnimation('leave', 'ng-leave', element, null, null, function() {
+ $delegate.leave(element);
+ }, doneCallback);
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#move
+ * @methodOf ngAnimate.$animate
+ * @function
+ *
+ * @description
+ * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or
+ * add the element directly after the afterElement element if present. Then the move animation will be run. Once
+ * the animation is started, the following CSS classes will be added for the duration of the animation:
+ *
+ * Below is a breakdown of each step that occurs during move animation:
+ *
+ * | Animation Step | What the element class attribute looks like |
+ * |----------------------------------------------------------------------------------------------|---------------------------------------------|
+ * | 1. $animate.move(...) is called | class="my-animation" |
+ * | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" |
+ * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
+ * | 4. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" |
+ * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" |
+ * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-move" |
+ * | 7. the .ng-move-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
+ * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
+ * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
+ * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" |
+ *
+ * @param {jQuery/jqLite element} element the element that will be the focus of the move animation
+ * @param {jQuery/jqLite element} parentElement the parentElement element of the element that will be the focus of the move animation
+ * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
+ */
+ move : function(element, parentElement, afterElement, doneCallback) {
+ cancelChildAnimations(element);
+ this.enabled(false, element);
+ $delegate.move(element, parentElement, afterElement);
+ $rootScope.$$postDigest(function() {
+ performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#addClass
+ * @methodOf ngAnimate.$animate
+ *
+ * @description
+ * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class.
+ * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide
+ * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions
+ * or keyframes are defined on the -add or base CSS class).
+ *
+ * Below is a breakdown of each step that occurs during addClass animation:
+ *
+ * | Animation Step | What the element class attribute looks like |
+ * |------------------------------------------------------------------------------------------------|---------------------------------------------|
+ * | 1. $animate.addClass(element, 'super') is called | class="my-animation" |
+ * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
+ * | 3. the .super-add class are added to the element | class="my-animation ng-animate super-add" |
+ * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" |
+ * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate super-add" |
+ * | 6. the .super, .super-add-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super super-add super-add-active" |
+ * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation super-add super-add-active" |
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" |
+ * | 9. The super class is kept on the element | class="my-animation super" |
+ * | 10. The doneCallback() callback is fired (if provided) | class="my-animation super" |
+ *
+ * @param {jQuery/jqLite element} element the element that will be animated
+ * @param {string} className the CSS class that will be added to the element and then animated
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
+ */
+ addClass : function(element, className, doneCallback) {
+ performAnimation('addClass', className, element, null, null, function() {
+ $delegate.addClass(element, className);
+ }, doneCallback);
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#removeClass
+ * @methodOf ngAnimate.$animate
+ *
+ * @description
+ * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value
+ * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in
+ * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if
+ * no CSS transitions or keyframes are defined on the -remove or base CSS classes).
+ *
+ * Below is a breakdown of each step that occurs during removeClass animation:
+ *
+ * | Animation Step | What the element class attribute looks like |
+ * |-----------------------------------------------------------------------------------------------|---------------------------------------------|
+ * | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" |
+ * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation super ng-animate" |
+ * | 3. the .super-remove class are added to the element | class="my-animation super ng-animate super-remove"|
+ * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" |
+ * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation super ng-animate super-remove" |
+ * | 6. the .super-remove-active and .ng-animate-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" |
+ * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" |
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
+ * | 9. The doneCallback() callback is fired (if provided) | class="my-animation" |
+ *
+ *
+ * @param {jQuery/jqLite element} element the element that will be animated
+ * @param {string} className the CSS class that will be animated and then removed from the element
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
+ */
+ removeClass : function(element, className, doneCallback) {
+ performAnimation('removeClass', className, element, null, null, function() {
+ $delegate.removeClass(element, className);
+ }, doneCallback);
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#enabled
+ * @methodOf ngAnimate.$animate
+ * @function
+ *
+ * @param {boolean=} value If provided then set the animation on or off.
+ * @return {boolean} Current animation state.
+ *
+ * @description
+ * Globally enables/disables animations.
+ *
+ */
+ enabled : function(value, element) {
+ switch(arguments.length) {
+ case 2:
+ if(value) {
+ cleanup(element);
+ } else {
+ var data = element.data(NG_ANIMATE_STATE) || {};
+ data.disabled = true;
+ element.data(NG_ANIMATE_STATE, data);
+ }
+ break;
+
+ case 1:
+ rootAnimateState.disabled = !value;
+ break;
+
+ default:
+ value = !rootAnimateState.disabled;
+ break;
+ }
+ return !!value;
+ }
+ };
+
+ /*
+ all animations call this shared animation triggering function internally.
+ The animationEvent variable refers to the JavaScript animation event that will be triggered
+ and the className value is the name of the animation that will be applied within the
+ CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
+ and the onComplete callback will be fired once the animation is fully complete.
+ */
+ function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
+ var classes = (element.attr('class') || '') + ' ' + className;
+ var animationLookup = (' ' + classes).replace(/\s+/g,'.');
+ if (!parentElement) {
+ parentElement = afterElement ? afterElement.parent() : element.parent();
+ }
+
+ var matches = lookup(animationLookup);
+ var isClassBased = animationEvent == 'addClass' || animationEvent == 'removeClass';
+ var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
+
+ //skip the animation if animations are disabled, a parent is already being animated,
+ //the element is not currently attached to the document body or then completely close
+ //the animation if any matching animations are not found at all.
+ //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
+ if (animationsDisabled(element, parentElement) || matches.length === 0) {
+ domOperation();
+ closeAnimation();
+ return;
+ }
+
+ var animations = [];
+ //only add animations if the currently running animation is not structural
+ //or if there is no animation running at all
+ if(!ngAnimateState.running || !(isClassBased && ngAnimateState.structural)) {
+ forEach(matches, function(animation) {
+ //add the animation to the queue to if it is allowed to be cancelled
+ if(!animation.allowCancel || animation.allowCancel(element, animationEvent, className)) {
+ var beforeFn, afterFn = animation[animationEvent];
+
+ //Special case for a leave animation since there is no point in performing an
+ //animation on a element node that has already been removed from the DOM
+ if(animationEvent == 'leave') {
+ beforeFn = afterFn;
+ afterFn = null; //this must be falsy so that the animation is skipped for leave
+ } else {
+ beforeFn = animation['before' + animationEvent.charAt(0).toUpperCase() + animationEvent.substr(1)];
+ }
+ animations.push({
+ before : beforeFn,
+ after : afterFn
+ });
+ }
+ });
+ }
+
+ //this would mean that an animation was not allowed so let the existing
+ //animation do it's thing and close this one early
+ if(animations.length === 0) {
+ domOperation();
+ fireDoneCallbackAsync();
+ return;
+ }
+
+ if(ngAnimateState.running) {
+ //if an animation is currently running on the element then lets take the steps
+ //to cancel that animation and fire any required callbacks
+ $timeout.cancel(ngAnimateState.closeAnimationTimeout);
+ cleanup(element);
+ cancelAnimations(ngAnimateState.animations);
+ (ngAnimateState.done || noop)(true);
+ }
+
+ //There is no point in perform a class-based animation if the element already contains
+ //(on addClass) or doesn't contain (on removeClass) the className being animated.
+ //The reason why this is being called after the previous animations are cancelled
+ //is so that the CSS classes present on the element can be properly examined.
+ if((animationEvent == 'addClass' && element.hasClass(className)) ||
+ (animationEvent == 'removeClass' && !element.hasClass(className))) {
+ domOperation();
+ fireDoneCallbackAsync();
+ return;
+ }
+
+ //the ng-animate class does nothing, but it's here to allow for
+ //parent animations to find and cancel child animations when needed
+ element.addClass(NG_ANIMATE_CLASS_NAME);
+
+ element.data(NG_ANIMATE_STATE, {
+ running:true,
+ structural:!isClassBased,
+ animations:animations,
+ done:onBeforeAnimationsComplete
+ });
+
+ //first we run the before animations and when all of those are complete
+ //then we perform the DOM operation and run the next set of animations
+ invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
+
+ function onBeforeAnimationsComplete(cancelled) {
+ domOperation();
+ if(cancelled === true) {
+ closeAnimation();
+ return;
+ }
+
+ //set the done function to the final done function
+ //so that the DOM event won't be executed twice by accident
+ //if the after animation is cancelled as well
+ var data = element.data(NG_ANIMATE_STATE);
+ if(data) {
+ data.done = closeAnimation;
+ element.data(NG_ANIMATE_STATE, data);
+ }
+ invokeRegisteredAnimationFns(animations, 'after', closeAnimation);
+ }
+
+ function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) {
+ var endFnName = phase + 'End';
+ forEach(animations, function(animation, index) {
+ var animationPhaseCompleted = function() {
+ progress(index, phase);
+ };
+
+ //there are no before functions for enter + move since the DOM
+ //operations happen before the performAnimation method fires
+ if(phase == 'before' && (animationEvent == 'enter' || animationEvent == 'move')) {
+ animationPhaseCompleted();
+ return;
+ }
+
+ if(animation[phase]) {
+ animation[endFnName] = isClassBased ?
+ animation[phase](element, className, animationPhaseCompleted) :
+ animation[phase](element, animationPhaseCompleted);
+ } else {
+ animationPhaseCompleted();
+ }
+ });
+
+ function progress(index, phase) {
+ var phaseCompletionFlag = phase + 'Complete';
+ var currentAnimation = animations[index];
+ currentAnimation[phaseCompletionFlag] = true;
+ (currentAnimation[endFnName] || noop)();
+
+ for(var i=0;i 0 ? '; ' : '') + style;
+ node.setAttribute('style', newStyle);
+ return oldStyle;
+ }
+
+ function getElementAnimationDetails(element, cacheKey) {
+ var data = cacheKey ? lookupCache[cacheKey] : null;
+ if(!data) {
+ var transitionDuration = 0;
+ var transitionDelay = 0;
+ var animationDuration = 0;
+ var animationDelay = 0;
+ var transitionDelayStyle;
+ var animationDelayStyle;
+ var transitionDurationStyle;
+ var transitionPropertyStyle;
+
+ //we want all the styles defined before and after
+ forEach(element, function(element) {
+ if (element.nodeType == ELEMENT_NODE) {
+ var elementStyles = $window.getComputedStyle(element) || {};
+
+ transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
+
+ transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
+
+ transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY];
+
+ transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
+
+ transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
+
+ animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
+
+ animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay);
+
+ var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
+
+ if(aDuration > 0) {
+ aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
+ }
+
+ animationDuration = Math.max(aDuration, animationDuration);
+ }
+ });
+ data = {
+ total : 0,
+ transitionPropertyStyle: transitionPropertyStyle,
+ transitionDurationStyle: transitionDurationStyle,
+ transitionDelayStyle: transitionDelayStyle,
+ transitionDelay: transitionDelay,
+ transitionDuration: transitionDuration,
+ animationDelayStyle: animationDelayStyle,
+ animationDelay: animationDelay,
+ animationDuration: animationDuration
+ };
+ if(cacheKey) {
+ lookupCache[cacheKey] = data;
+ }
+ }
+ return data;
+ }
+
+ function parseMaxTime(str) {
+ var maxValue = 0;
+ var values = angular.isString(str) ?
+ str.split(/\s*,\s*/) :
+ [];
+ forEach(values, function(value) {
+ maxValue = Math.max(parseFloat(value) || 0, maxValue);
+ });
+ return maxValue;
+ }
+
+ function getCacheKey(element) {
+ var parentElement = element.parent();
+ var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY);
+ if(!parentID) {
+ parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
+ parentID = parentCounter;
+ }
+ return parentID + '-' + element[0].className;
+ }
+
+ function animateSetup(element, className) {
+ var cacheKey = getCacheKey(element);
+ var eventCacheKey = cacheKey + ' ' + className;
+ var stagger = {};
+ var ii = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
+
+ if(ii > 0) {
+ var staggerClassName = className + '-stagger';
+ var staggerCacheKey = cacheKey + ' ' + staggerClassName;
+ var applyClasses = !lookupCache[staggerCacheKey];
+
+ applyClasses && element.addClass(staggerClassName);
+
+ stagger = getElementAnimationDetails(element, staggerCacheKey);
+
+ applyClasses && element.removeClass(staggerClassName);
+ }
+
+ element.addClass(className);
+
+ var timings = getElementAnimationDetails(element, eventCacheKey);
+
+ /* there is no point in performing a reflow if the animation
+ timeout is empty (this would cause a flicker bug normally
+ in the page. There is also no point in performing an animation
+ that only has a delay and no duration */
+ var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
+ if(maxDuration === 0) {
+ element.removeClass(className);
+ return false;
+ }
+
+ var node = element[0];
+ //temporarily disable the transition so that the enter styles
+ //don't animate twice (this is here to avoid a bug in Chrome/FF).
+ var activeClassName = '';
+ if(timings.transitionDuration > 0) {
+ element.addClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
+ activeClassName += NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME + ' ';
+ node.style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
+ }
+
+ forEach(className.split(' '), function(klass, i) {
+ activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
+ });
+
+ element.data(NG_ANIMATE_CSS_DATA_KEY, {
+ className : className,
+ activeClassName : activeClassName,
+ maxDuration : maxDuration,
+ classes : className + ' ' + activeClassName,
+ timings : timings,
+ stagger : stagger,
+ ii : ii
+ });
+
+ return true;
+ }
+
+ function animateRun(element, className, activeAnimationComplete) {
+ var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
+ if(!element.hasClass(className) || !data) {
+ activeAnimationComplete();
+ return;
+ }
+
+ var node = element[0];
+ var timings = data.timings;
+ var stagger = data.stagger;
+ var maxDuration = data.maxDuration;
+ var activeClassName = data.activeClassName;
+ var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000;
+ var startTime = Date.now();
+ var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
+ var formerStyle;
+ var ii = data.ii;
+
+ var applyFallbackStyle, style = '';
+ if(timings.transitionDuration > 0) {
+ node.style[TRANSITION_PROP + PROPERTY_KEY] = '';
+
+ var propertyStyle = timings.transitionPropertyStyle;
+ if(propertyStyle.indexOf('all') == -1) {
+ applyFallbackStyle = true;
+ var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'clip';
+ style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; ';
+ style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; ';
+ }
+ }
+
+ if(ii > 0) {
+ if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
+ var delayStyle = timings.transitionDelayStyle;
+ if(applyFallbackStyle) {
+ delayStyle += ', ' + timings.transitionDelay + 's';
+ }
+
+ style += CSS_PREFIX + 'transition-delay: ' +
+ prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
+ }
+
+ if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
+ style += CSS_PREFIX + 'animation-delay: ' +
+ prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
+ }
+ }
+
+ if(style.length > 0) {
+ formerStyle = applyStyle(node, style);
+ }
+
+ element.on(css3AnimationEvents, onAnimationProgress);
+ element.addClass(activeClassName);
+
+ // This will automatically be called by $animate so
+ // there is no need to attach this internally to the
+ // timeout done method.
+ return function onEnd(cancelled) {
+ element.off(css3AnimationEvents, onAnimationProgress);
+ element.removeClass(activeClassName);
+ animateClose(element, className);
+ if(formerStyle != null) {
+ formerStyle.length > 0 ?
+ node.setAttribute('style', formerStyle) :
+ node.removeAttribute('style');
+ }
+ };
+
+ function onAnimationProgress(event) {
+ event.stopPropagation();
+ var ev = event.originalEvent || event;
+ var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
+ /* $manualTimeStamp is a mocked timeStamp value which is set
+ * within browserTrigger(). This is only here so that tests can
+ * mock animations properly. Real events fallback to event.timeStamp,
+ * or, if they don't, then a timeStamp is automatically created for them.
+ * We're checking to see if the timeStamp surpasses the expected delay,
+ * but we're using elapsedTime instead of the timeStamp on the 2nd
+ * pre-condition since animations sometimes close off early */
+ if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && ev.elapsedTime >= maxDuration) {
+ activeAnimationComplete();
+ }
+ }
+ }
+
+ function prepareStaggerDelay(delayStyle, staggerDelay, index) {
+ var style = '';
+ forEach(delayStyle.split(','), function(val, i) {
+ style += (i > 0 ? ',' : '') +
+ (index * staggerDelay + parseInt(val, 10)) + 's';
+ });
+ return style;
+ }
+
+ function animateBefore(element, className) {
+ if(animateSetup(element, className)) {
+ return function(cancelled) {
+ cancelled && animateClose(element, className);
+ };
+ }
+ }
+
+ function animateAfter(element, className, afterAnimationComplete) {
+ if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
+ return animateRun(element, className, afterAnimationComplete);
+ } else {
+ animateClose(element, className);
+ afterAnimationComplete();
+ }
+ }
+
+ function animate(element, className, animationComplete) {
+ //If the animateSetup function doesn't bother returning a
+ //cancellation function then it means that there is no animation
+ //to perform at all
+ var preReflowCancellation = animateBefore(element, className);
+ if(!preReflowCancellation) {
+ animationComplete();
+ return;
+ }
+
+ //There are two cancellation functions: one is before the first
+ //reflow animation and the second is during the active state
+ //animation. The first function will take care of removing the
+ //data from the element which will not make the 2nd animation
+ //happen in the first place
+ var cancel = preReflowCancellation;
+ afterReflow(function() {
+ //once the reflow is complete then we point cancel to
+ //the new cancellation function which will remove all of the
+ //animation properties from the active animation
+ cancel = animateAfter(element, className, animationComplete);
+ });
+
+ return function(cancelled) {
+ (cancel || noop)(cancelled);
+ };
+ }
+
+ function animateClose(element, className) {
+ element.removeClass(className);
+ element.removeClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
+ element.removeData(NG_ANIMATE_CSS_DATA_KEY);
+ }
+
+ return {
+ allowCancel : function(element, animationEvent, className) {
+ //always cancel the current animation if it is a
+ //structural animation
+ var oldClasses = (element.data(NG_ANIMATE_CSS_DATA_KEY) || {}).classes;
+ if(!oldClasses || ['enter','leave','move'].indexOf(animationEvent) >= 0) {
+ return true;
+ }
+
+ var parentElement = element.parent();
+ var clone = angular.element(element[0].cloneNode());
+
+ //make the element super hidden and override any CSS style values
+ clone.attr('style','position:absolute; top:-9999px; left:-9999px');
+ clone.removeAttr('id');
+ clone.html('');
+
+ forEach(oldClasses.split(' '), function(klass) {
+ clone.removeClass(klass);
+ });
+
+ var suffix = animationEvent == 'addClass' ? '-add' : '-remove';
+ clone.addClass(suffixClasses(className, suffix));
+ parentElement.append(clone);
+
+ var timings = getElementAnimationDetails(clone);
+ clone.remove();
+
+ return Math.max(timings.transitionDuration, timings.animationDuration) > 0;
+ },
+
+ enter : function(element, animationCompleted) {
+ return animate(element, 'ng-enter', animationCompleted);
+ },
+
+ leave : function(element, animationCompleted) {
+ return animate(element, 'ng-leave', animationCompleted);
+ },
+
+ move : function(element, animationCompleted) {
+ return animate(element, 'ng-move', animationCompleted);
+ },
+
+ beforeAddClass : function(element, className, animationCompleted) {
+ var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
+ if(cancellationMethod) {
+ afterReflow(animationCompleted);
+ return cancellationMethod;
+ }
+ animationCompleted();
+ },
+
+ addClass : function(element, className, animationCompleted) {
+ return animateAfter(element, suffixClasses(className, '-add'), animationCompleted);
+ },
+
+ beforeRemoveClass : function(element, className, animationCompleted) {
+ var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
+ if(cancellationMethod) {
+ afterReflow(animationCompleted);
+ return cancellationMethod;
+ }
+ animationCompleted();
+ },
+
+ removeClass : function(element, className, animationCompleted) {
+ return animateAfter(element, suffixClasses(className, '-remove'), animationCompleted);
+ }
+ };
+
+ function suffixClasses(classes, suffix) {
+ var className = '';
+ classes = angular.isArray(classes) ? classes : classes.split(/\s+/);
+ forEach(classes, function(klass, i) {
+ if(klass && klass.length > 0) {
+ className += (i > 0 ? ' ' : '') + klass + suffix;
+ }
+ });
+ return className;
+ }
+ }]);
+ }]);
+
+
+})(window, window.angular);
diff --git a/lib/katello/plugin.rb b/lib/katello/plugin.rb
index 4e18e916670..149304a01cf 100644
--- a/lib/katello/plugin.rb
+++ b/lib/katello/plugin.rb
@@ -65,6 +65,13 @@
:engine => Katello::Engine
divider :top_menu, :parent => :content
+ menu :top_menu,
+ :content_views,
+ :caption => N_('Content Views (new!)'),
+ :url_hash => {:controller => 'katello/content_views',
+ :action => 'all'},
+ :engine => Katello::Engine
+
menu :top_menu,
:content_search,
:caption => N_('Content Search'),