diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6a964ff --- /dev/null +++ b/LICENSE @@ -0,0 +1,180 @@ +Some icons from here: +http://jxnblk.com/ - License: MIT + +The rest is Apache 2.0: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f0dc93e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include AUTHORS +include ChangeLog +exclude .gitignore +exclude .gitreview + +recursive-include xstatic/pkg/app_catalog_common * + +global-exclude *.pyc diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..95220e2 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,23 @@ +[metadata] +name = app-catalog-common +description = 'OpenStack Application Catalog Common code for the OpenStack Dashboard', +author = OpenStack Foundation +author_email = openstack-dev@lists.openstack.org +description-file = README.rst +classifier = + Development Status :: 4 - Beta + Environment :: OpenStack + Framework :: Django + Intended Audience :: Developers + Intended Audience :: System Administrators + Intended Audience :: Information Technology + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Topic :: Internet :: WWW/HTTP +[files] +packages = + xstatic diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ca36388 --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +#! /usr/bin/env python +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from setuptools import setup + +setup( + setup_requires=['pbr>=1.3'], + pbr=True, +) diff --git a/xstatic/__init__.py b/xstatic/__init__.py new file mode 100755 index 0000000..de40ea7 --- /dev/null +++ b/xstatic/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/xstatic/pkg/__init__.py b/xstatic/pkg/__init__.py new file mode 100755 index 0000000..de40ea7 --- /dev/null +++ b/xstatic/pkg/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/xstatic/pkg/app_catalog_common/__init__.py b/xstatic/pkg/app_catalog_common/__init__.py new file mode 100755 index 0000000..9634e16 --- /dev/null +++ b/xstatic/pkg/app_catalog_common/__init__.py @@ -0,0 +1,49 @@ +""" +XStatic resource package + +See package 'XStatic' for documentation and basic tools. +""" + +DISPLAY_NAME = 'app-catalog-common' # official name, upper/lowercase allowed, no spaces +PACKAGE_NAME = 'app-catalog-common' # name used for PyPi + +NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar') + # please use a all-lowercase valid python + # package name + +VERSION = '0.0.1' # version of the packaged files, please use the upstream + # version number +BUILD = '1' # our package build number, so we can release new builds + # with fixes for xstatic stuff. +PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi + +DESCRIPTION = "%s %s (XStatic packaging standard)" % (DISPLAY_NAME, VERSION) + +PLATFORMS = 'any' +CLASSIFIERS = [] +KEYWORDS = '%s xstatic' % NAME + +# XStatic-* package maintainer: +MAINTAINER = 'Kevin Fox' +MAINTAINER_EMAIL = 'kevin@efox.cc' + +# this refers to the project homepage of the stuff we packaged: +HOMEPAGE = 'https://apps.openstack.org' + +# this refers to all files: +LICENSE = '(same as %s)' % DISPLAY_NAME + +from os.path import join, dirname +BASE_DIR = join(dirname(__file__), 'data') +# linux package maintainers just can point to their file locations like this: +#BASE_DIR = '/usr/share/javascript/jquery' + +LOCATIONS = { + # CDN locations (if no public CDN exists, use an empty dict) + # if value is a string, it is a base location, just append relative + # path/filename. if value is a dict, do another lookup using the + # relative path/filename you want. + # your relative path/filenames should usually be without version + # information, because either the base dir/url is exactly for this + # version or the mapping will care for accessing this version. +} diff --git a/xstatic/pkg/app_catalog_common/data/1439233859_grid.png b/xstatic/pkg/app_catalog_common/data/1439233859_grid.png new file mode 100644 index 0000000..043faca Binary files /dev/null and b/xstatic/pkg/app_catalog_common/data/1439233859_grid.png differ diff --git a/xstatic/pkg/app_catalog_common/data/1439233889_list.png b/xstatic/pkg/app_catalog_common/data/1439233889_list.png new file mode 100644 index 0000000..b61c18d Binary files /dev/null and b/xstatic/pkg/app_catalog_common/data/1439233889_list.png differ diff --git a/xstatic/pkg/app_catalog_common/data/_details_panel.html b/xstatic/pkg/app_catalog_common/data/_details_panel.html new file mode 100644 index 0000000..8636e4b --- /dev/null +++ b/xstatic/pkg/app_catalog_common/data/_details_panel.html @@ -0,0 +1,47 @@ +
+
+
+
+ +
+
+
+
{$ asset.name $}
+
{$ asset.provided_by.company $}
+
+
+
+ +
+
License
+
+
{$ asset.license $}
+ License Details +
+
+
Description
+
{$ asset.description $}
+
+
Dependencies
+
+
+
+
+ + + + + + +
{$ dep.asset.name $}
+
+
+
+
diff --git a/xstatic/pkg/app_catalog_common/data/action.html b/xstatic/pkg/app_catalog_common/data/action.html new file mode 100644 index 0000000..6e416f1 --- /dev/null +++ b/xstatic/pkg/app_catalog_common/data/action.html @@ -0,0 +1,51 @@ +
+
+ Unsupported +
+
+
+ Checking + Error + Unsupported +
+ Launch + Launch +
+
+
+ Install Instructions +
+ Checking + Install +
+ Installed + Launch +
+
+
+
+ Unsupported +
+ Checking + Install +
+ Installed + Launch +
+
+
+
+
+
+ Unsupported +
+ Install + Installed +
+
+
+
+ Unsupported +
+
+
diff --git a/xstatic/pkg/app_catalog_common/data/app_catalog.js b/xstatic/pkg/app_catalog_common/data/app_catalog.js new file mode 100644 index 0000000..930ae29 --- /dev/null +++ b/xstatic/pkg/app_catalog_common/data/app_catalog.js @@ -0,0 +1,583 @@ +/* + * Copyright 2015 IBM Corp. + * Copyright 2015 Kevin Fox. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function() { + 'use strict'; + + angular + .module('horizon.dashboard.project.app_catalog', []) + .filter('encodeURIComponent', function() { + return window.encodeURIComponent; + }).directive('appAction', function () { + return { + restrict: 'EA', + templateUrl: STATIC_URL + 'lib/app-catalog-common/action.html' + }; + }).directive('appCatalogMagicSearch', [ + 'horizon.framework.widgets.basePath', + appCatalogMagicSearchBar + ]).controller('appCatalogTableCtrl', [ + '$scope', + '$http', + '$timeout', + '$modal', + 'horizon.framework.widgets.toast.service', + 'appCatalogModel', + appCatalogTableCtrl + ]).controller('appComponentCatalogTableCtrl', [ + '$scope', + '$http', + '$timeout', + '$modal', + 'horizon.framework.widgets.toast.service', + 'appCatalogModel', + appComponentCatalogTableCtrl + ]).service('appCatalogModel', [ + '$http', + '$injector', + 'horizon.app.core.openstack-service-api.heat', + 'horizon.app.core.openstack-service-api.glance', + 'horizon.app.core.openstack-service-api.serviceCatalog', + appCatalogModel + ]).directive('stars', stars); + + function appCatalogModel($http, $injector, heatAPI, glanceAPI, serviceCatalog) { + var $scope = this; + var callbacks = { + update: [], + error: [], + deprecated: [], + retired: [] + }; + this.assets = []; + this.assets_filtered = []; +//FIXME reduce duplication here.... + this.supported_service_type_to_label = { + heat: 'Orchestration', + glance: 'Images', + murano: 'Murano' + }; + this.service_filters = [ + {id:'heat', name:'Orchestration'}, + {id:'glance', name: 'Images'}, + {id:'murano', name: 'Murano'} + ]; + this.service_filters_selections = { + 'heat':false, + 'glance':false, + 'murano':false + }; + var notifyUpdate = function() { + angular.forEach(callbacks.update, function(callback) { + callback(); + }); + }; + var notifyError = function(message) { + angular.forEach(callbacks.error, function(callback) { + callback(message); + }); + }; + var notifyDeprecated = function(message) { + angular.forEach(callbacks.deprecated, function(callback) { + callback(message); + }); + }; + var notifyRetired = function() { + angular.forEach(callbacks.retired, function(callback) { + callback(); + }); + }; + var muranoAPI; + $scope.has_murano = false; + $scope.selected_facets = []; + $scope.selected_text = ""; + if ($injector.has('horizon.app.core.openstack-service-api.murano')) { + muranoAPI = $injector.get('horizon.app.core.openstack-service-api.murano'); + $scope.has_murano = true; + } +//FIXME [{'name':'heat', 'type':'orchestration'}, {'name':'glance', 'type':'image'}] + serviceCatalog.get().then(function(catalog) { + angular.forEach(catalog, function(entry) { + if (entry.name in $scope.supported_service_type_to_label) { + $scope.service_filters_selections[entry.name] = true; + } + }); + $scope.catalog = catalog; + $scope.update_assets_filtered(); + }); + this.update_assets_filtered = function() { +//FIXME this is not ideal... + var textSearchableFields = [ + ['name'], + ['provided_by', 'name'], + ['provided_by', 'company'], + ['supported_by', 'name'], + ['description'], + ['license'], + ['service', 'type'], + ['service', 'container_format'], + ['service', 'disk_format'], + ['service', 'package_name'], + ['service', 'murano_package_name'] + ]; + $scope.assets_filtered.length = 0; + angular.forEach($scope.assets, function(asset) { + if ($scope.service_filters_selections[asset.service.type] == true || + asset.service.type == 'bundle') { + var filteredOut = false; + angular.forEach(asset.depends, function(dep) { + if ($scope.service_filters_selections[dep.asset.service.type] == false) { + filteredOut = true; + } + }); + if ($scope.selected_facets.length != 0) { + angular.forEach($scope.selected_facets, function(filter) { + var val = filter[0].split('.').reduce(function(obj,i) {return obj[i];}, asset); + if (val.toLowerCase().indexOf(filter[1].toLowerCase()) == -1) { + filteredOut = true; + } + }); + } + if ($scope.selected_text != '') { + var found = false; + angular.forEach(textSearchableFields, function(field) { + try { + var val = field.reduce(function(obj,i) {return obj[i];}, asset); + if (val.toLowerCase().indexOf($scope.selected_text.toLowerCase()) != -1) { + found = true; + } + } catch (e) {} + }); + if (!found) { + filteredOut = true; + } + } + if (!filteredOut) { + $scope.assets_filtered.push(asset); + } + } + }); + var types = {}; + angular.forEach($scope.assets_filtered, function(asset) { + types[asset.service.type] = true; + }); +//FIXME dedup some of this later. + var map = {'heat': 'Orchestration', 'glance': 'Images'}; + var options = []; + + angular.forEach(types, function(type) { + if (type in map) { + options.push({'key':type, 'label':map[type]}); + } + }); + angular.forEach($scope.asset_filter_facets, function(facet) { + if (facet.name == 'service.type') { +//FIXME Doesn't seem to work currently +// facet['options'] = options; + } + }); + notifyUpdate(); + }; + this.toggle_service_filter = function(serviceName) { + var value = $scope.service_filters_selections[serviceName]; + if (value) { + value = false; + } else { + value = true; + } + $scope.service_filters_selections[serviceName] = value; + $scope.update_assets_filtered(); + }; + this.register_callback = function(type, callback) { + callbacks[type].push(callback); + }; + var semver_compare = function(a, b) { + var v = a[0] - b[0] + if (v === 0) { + v = a[1] - b[1]; + if (v === 0) { + v = a[2] - b[2]; + } + } + return v; + }; + $scope.eversion_check = function(asset, version) { + if (!( 'ever' in asset.service)) { + return true; + } + var matched = false; + angular.forEach(asset.service.ever, function(ever) { + var has_min = 'min' in ever; + var has_max = 'max' in ever; + if(has_max && has_min) { + if (semver_compare(ever.min, version) <= 0 && + semver_compare(version, ever.max) <= 0) { + matched = true; + } + } else if (has_max) { + if (semver_compare(version, ever.max) <= 0) { + matched = true; + } + } else if (has_min) { + if (semver_compare(ever.min, version) <= 0) { + matched = true; + } + } + }); + return matched; + }; + this.init = function(appCatalogSettings) { +//FIXME move this to a test file. +// test_evars($scope); + var tver = appCatalogSettings.APP_CATALOG_VERSION.VER; + var defaultVersion = [2015, 2, 0]; //Mitaka + if (tver.indexOf('8.') === 0) { + defaultVersion = [2015,1,0]; //Liberty + } + var appCatalogUrl = appCatalogSettings.APP_CATALOG_URL; + $scope.heat_release = appCatalogSettings.HEAT_VERSION.REL; + $scope.heat_version = appCatalogSettings.HEAT_VERSION.VER; + if($scope.heat_version) { + $scope.heat_version = $scope.heat_version.split('.', 3).map(Number); + } else { + $scope.heat_version = defaultVersion; + } + $scope.murano_release = appCatalogSettings.MURANO_VERSION.REL; + $scope.murano_version = appCatalogSettings.MURANO_VERSION.VER; + if($scope.murano_version) { + $scope.murano_version = $scope.murano_version.split('.', 3).map(Number); + } else { + $scope.murano_version = defaultVersion; + } + var req = { + url: appCatalogUrl + '/api/v1/assets', + headers: { + 'X-Requested-With': undefined, + 'X-App-Catalog-Versions': [ + appCatalogSettings.HORIZON_VERSION.VER, + appCatalogSettings.HORIZON_VERSION.REL, + appCatalogSettings.APP_CATALOG_VERSION.VER, + appCatalogSettings.APP_CATALOG_VERSION.REL + ].join(' ') + } + }; + $http(req).success(function(data) { + if ('deprecated' in data) { + notifyDeprecated(data.deprecated); + } + if ('retired' in data) { + notifyRetired(); + } + var process = function(asset) { + var url = asset.attributes.url; + var args = {'template_url': url}; + if ($scope.eversion_check(asset, $scope.heat_version) != true) { + asset.disabled = true; + notifyUpdate(); + return; + } + if ('environment' in asset.service ) { + args['environment'] = asset.service.environment; + } + heatAPI.validate(args, true).success(function(data) { + asset.validated = true; + notifyUpdate(); + }).error(function(data, status) { + var str = 'ERROR: Could not retrieve template:'; + asset.disabled = true; + asset.validated = 'unsupported'; + if (status == 400 && data.slice(0, str.length) == str) { + asset.validated = 'error'; + } + notifyUpdate(); + }); + }; + angular.forEach(data.assets, function(asset) { + $scope.assets.push(asset); + if ('version' in asset.service && asset.service.version > 1) { + asset.disabled = true; + } else if (asset.service.type == 'heat') { + process(asset); + } else if (asset.service.type == 'murano') { + asset.validated = true; + asset.disabled = !$scope.has_murano; + if ($scope.eversion_check(asset, $scope.murano_version) != true) { + asset.disabled = true; + } + } else if (asset.service.type != 'glance' && asset.service.type != 'bundle') { + asset.disabled = true; + } + asset.has_murano = $scope.has_murano; + }); + $scope.glance_loaded = true; + $scope.murano_loaded = true; + updateFoundAssets($scope); + }).error(function() { + notifyError('There was an error while retrieving entries from the Application Catalog.'); + }); + if ($scope.has_murano) { + muranoAPI.getPackages().success(function(data) { + $scope.murano_packages = data; + var muranoPackageDefinitions = {}; + angular.forEach(data.packages, function(pkg) { + angular.forEach(pkg.class_definitions, function(definition) { + muranoPackageDefinitions[definition] = {'id': pkg.id}; + }); + }); + $scope.murano_package_definitions = muranoPackageDefinitions; + updateFoundAssets($scope); + }); + } + glanceAPI.getImages().success(function(data) { + $scope.glance_images = data; + var glanceNames = {}; + angular.forEach(data.items, function(item) { + glanceNames[item.name] = {'id': item.id}; + }); + $scope.glance_names = glanceNames; + updateFoundAssets($scope); + }); + }; + this.update_selected_facets = function(selectedFacets) { + $scope.selected_facets.length = 0; + if (selectedFacets != undefined) { + angular.forEach(selectedFacets, function(facet) { + $scope.selected_facets.push(facet); + }); + } + $scope.update_assets_filtered(); + }; + this.update_selected_text = function(selectedText) { + $scope.selected_text = selectedText; + $scope.update_assets_filtered(); + }; + this.asset_filter_strings = { + cancel: gettext('Cancel'), + prompt: gettext('Search'), + remove: gettext('Remove'), + text: gettext('Text') + }; + this.asset_filter_facets = [ + { + name: 'name', + label: gettext('Name'), + singleton: true + }, + { + name: 'license', + label: gettext('License'), + singleton: true + }, + { + name: 'service.type', + label: gettext('Service Type'), +//FIXME make dynamic later. + options: [ + {key: 'heat', label: 'Orchestration'}, + {key: 'glance', label: 'Images'}, + {key: 'murano', label: 'Murano'} + ], + singleton: true + }]; + } + function commonInit($scope, $modal, toast, appCatalogModel) { + $scope.WEBROOT = WEBROOT; + $scope.STATIC_URL = STATIC_URL; + $scope.toggle_service_filter = appCatalogModel.toggle_service_filter; + $scope.service_filters = appCatalogModel.service_filters; + $scope.service_filters_selections = appCatalogModel.service_filters_selections; + $scope.asset_filter_strings = appCatalogModel.asset_filter_strings; + $scope.asset_filter_facets = appCatalogModel.asset_filter_facets; + $scope.init = appCatalogModel.init; + + var retired = function() { + var newscope = $scope.$new(); + var modal = $modal.open({ + templateUrl: STATIC_URL + "lib/app-catalog-common/retired_panel.html", + scope: newscope + }); + newscope.cancel = function() { + modal.dismiss(''); + }; + }; + var error = function(message) { + toast.add('error', message); + }; + var deprecated = function(message) { + toast.add('warning', message); + }; + appCatalogModel.register_callback('error', error); + appCatalogModel.register_callback('deprecated', deprecated); + appCatalogModel.register_callback('retired', retired); +//FIXME probably belongs in its own directive... + var textSearchWatcher = $scope.$on('textSearch', function(event, text) { + appCatalogModel.update_selected_text(text); + + }); + var textSearchWatcher2 = $scope.$on('searchUpdated', function(event, query) { + var selectedFacets; + if (query != '') { + selectedFacets = query.split('&'); + for (var i = 0; i < selectedFacets.length; i++) { + var s = selectedFacets[i]; + var idx = s.indexOf('='); + selectedFacets[i] = [s.slice(0, idx), s.slice(idx + 1)]; + } + } + appCatalogModel.update_selected_facets(selectedFacets); + }); + } + function appCatalogMagicSearchBar(basePath) { + var directive = { + compile: function (element, attrs) { + /** + * Need to set template here since MagicSearch template + * attribute is not interpolated. Can't hardcode the + * template location and need to use basePath. + */ + var templateUrl = basePath + 'magic-search/magic-search.html'; + element.find('magic-search').attr('template', templateUrl); + element.addClass('hz-magic-search-bar'); + }, + restrict: 'E', + templateUrl: STATIC_URL + "lib/app-catalog-common/magic_search.html" + }; + return directive; + } + + function appCatalogTableCtrl($scope, $http, $timeout, $modal, toast, appCatalogModel) { + $scope.assets = []; + var update = function() { + $scope.assets = []; + angular.forEach(appCatalogModel.assets_filtered, function(asset) { + if (typeof asset.tags !== "undefined" && asset.tags.indexOf('app') > -1) { + $scope.assets.push(asset); + } + }); + }; + appCatalogModel.register_callback('update', update); + commonInit($scope, $modal, toast, appCatalogModel); + $scope.switcher = {pannel: 'app', active: 'grid'}; + $scope.changeActivePanel = function(name) { + $scope.switcher.active = name; + }; + $scope.details = function(asset) { + var newscope = $scope.$new(); + newscope.asset = asset; + var modal = $modal.open({ + templateUrl: STATIC_URL + "lib/app-catalog-common/details_panel.html", + scope: newscope + }); + newscope.cancel = function() { + modal.dismiss(''); + }; + }; + } + + function appComponentCatalogTableCtrl($scope, $http, $timeout, $modal, toast, appCatalogModel) { + $scope.assets = appCatalogModel.assets_filtered; + var update = function() { + $timeout(function() { + $scope.assets = appCatalogModel.assets_filtered; + }, 0, false); + }; + appCatalogModel.register_callback('update', update); + commonInit($scope, $modal, toast, appCatalogModel); + $scope.switcher = {pannel: 'component', active: 'list'}; + } + + function updateFoundAssets($scope) { + var i; + if ('glance_loaded' in $scope && 'glance_names' in $scope) { + for (i in $scope.assets) { + if ($scope.assets[i].service.type != 'glance') { + continue; + } + var name = $scope.assets[i].name; + var isInstalled = name in $scope.glance_names; + $scope.assets[i].installed = isInstalled; + if (isInstalled) { + $scope.assets[i].installed_id = $scope.glance_names[name].id; + } + } + } + if ('murano_loaded' in $scope && 'murano_package_definitions' in $scope) { + for (i in $scope.assets) { + if ($scope.assets[i].service.type != 'murano') { + continue; + } + var definition = $scope.assets[i].service.package_name; + var isInstalled = definition in ($scope.murano_package_definitions); + $scope.assets[i].installed = isInstalled; + if (isInstalled) { + $scope.assets[i].service.murano_id = $scope.murano_package_definitions[definition].id; + } + } + } + var assetNameToAsset = {}; + angular.forEach($scope.assets, function(asset) { + assetNameToAsset[asset.name] = asset; + }); + angular.forEach($scope.assets, function(asset) { + asset.disabled = false; + if ('depends' in asset) { + angular.forEach(asset.depends, function(dep) { + dep.asset = assetNameToAsset[dep.name]; + if('disabled' in asset && dep.asset.disabled) { + asset.disabled = true; + } + }); + } + }); + $scope.update_assets_filtered(); + } + + function stars() { + var star = angular.element(''); + star.addClass('fa fa-star'); + star.css({ color: 'goldenrod' }); + return { + restrict: 'E', + scope: { value: '=' }, + link: function(scope, element) { + for (var i = 0; i < scope.value; i++) { + element.append(star.clone()); + } + } + }; + } + +/*FIXME move out to testing file.*/ + function test_evars($scope) { + var assert = function(t, a, b) { + if (!t) { + console.log("Failed", a, b); + } + } + assert($scope.eversion_check({service:{}}, [2014,1,1]), [], [2014,1,1]); + assert($scope.eversion_check({service:{ever:[{min:[2014,1,1]}]}}, [2014,1,1]), [2014,1,1], [2014,1,1]); + assert($scope.eversion_check({service:{ever:[{max:[2014,1,1]}]}}, [2014,1,1]), [2014,1,1], [2014,1,1]); + assert(!$scope.eversion_check({service:{ever:[{max:[2014,1,1]}]}}, [2015,1,1]), [2014,1,1], [2015,1,1]); + assert($scope.eversion_check({service:{ever:[{max:[2014,1,1]}]}}, [2013,1,1]), [2014,1,1], [2013,1,1]); + assert(!$scope.eversion_check({service:{ever:[{min:[2016,1,1]}]}}, [2015,1,1]), [2016,1,1], [2015,1,1]); + assert($scope.eversion_check({service:{ever:[{min:[2013,1,1]}]}}, [2014,1,1]), [2013,1,1], [2014,1,1]); + assert($scope.eversion_check({service:{ever:[{min:[2013,1,1],max:[2015,1,1]}]}}, [2014,1,1]), [2013,2015], [2014,1,1]); + assert(!$scope.eversion_check({service:{ever:[{min:[2013,1,1],max:[2015,1,1]}]}}, [2011,1,1]), [2013,2015], [2011,1,1]); + assert(!$scope.eversion_check({service:{ever:[{min:[2013,1,1],max:[2015,1,1]}]}}, [2016,1,1]), [2013,2015], [2016,1,1]); + } + +})(); diff --git a/xstatic/pkg/app_catalog_common/data/app_catalog.scss b/xstatic/pkg/app_catalog_common/data/app_catalog.scss new file mode 100644 index 0000000..7c87bf8 --- /dev/null +++ b/xstatic/pkg/app_catalog_common/data/app_catalog.scss @@ -0,0 +1,11 @@ +.app_catalog_grid_item_hidden { + height: 172px; + width: 182px; + margin: -170px 0px 0px -10px; + opacity: 1; + overflow: hidden; + display: block; + position: relative; + background: rgba(0, 0, 0, .25); + z-index: 100; +} diff --git a/xstatic/pkg/app_catalog_common/data/details_panel.html b/xstatic/pkg/app_catalog_common/data/details_panel.html new file mode 100644 index 0000000..45fb4fc --- /dev/null +++ b/xstatic/pkg/app_catalog_common/data/details_panel.html @@ -0,0 +1,11 @@ +
+ + + +
diff --git a/xstatic/pkg/app_catalog_common/data/featured-corner-glance.png b/xstatic/pkg/app_catalog_common/data/featured-corner-glance.png new file mode 100644 index 0000000..7302882 Binary files /dev/null and b/xstatic/pkg/app_catalog_common/data/featured-corner-glance.png differ diff --git a/xstatic/pkg/app_catalog_common/data/featured-corner-heat.png b/xstatic/pkg/app_catalog_common/data/featured-corner-heat.png new file mode 100644 index 0000000..258da13 Binary files /dev/null and b/xstatic/pkg/app_catalog_common/data/featured-corner-heat.png differ diff --git a/xstatic/pkg/app_catalog_common/data/featured-corner-murano.png b/xstatic/pkg/app_catalog_common/data/featured-corner-murano.png new file mode 100644 index 0000000..d6e6ab5 Binary files /dev/null and b/xstatic/pkg/app_catalog_common/data/featured-corner-murano.png differ diff --git a/xstatic/pkg/app_catalog_common/data/magic_search.html b/xstatic/pkg/app_catalog_common/data/magic_search.html new file mode 100644 index 0000000..7fee196 --- /dev/null +++ b/xstatic/pkg/app_catalog_common/data/magic_search.html @@ -0,0 +1,12 @@ + diff --git a/xstatic/pkg/app_catalog_common/data/main_panel.html b/xstatic/pkg/app_catalog_common/data/main_panel.html new file mode 100644 index 0000000..07b878b --- /dev/null +++ b/xstatic/pkg/app_catalog_common/data/main_panel.html @@ -0,0 +1,150 @@ +
+ Service Types: + + + + + + + +
+ +
+ +
+
+
+
+ +
+ + + +
+ + + +
+
{$ asset.name $} + + +
+
{$ asset.provided_by.name $} + + +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameLicense
+ + {$ asset.name $}{$ asset.license $}
+ +
+
+
+
+
diff --git a/xstatic/pkg/app_catalog_common/data/retired_panel.html b/xstatic/pkg/app_catalog_common/data/retired_panel.html new file mode 100644 index 0000000..ddc8523 --- /dev/null +++ b/xstatic/pkg/app_catalog_common/data/retired_panel.html @@ -0,0 +1,11 @@ +
+ + + +