diff --git a/refstack-ui/.bowerrc b/refstack-ui/.bowerrc new file mode 100644 index 00000000..5d2a4415 --- /dev/null +++ b/refstack-ui/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "app/assets/lib" +} diff --git a/refstack-ui/.gitignore b/refstack-ui/.gitignore new file mode 100644 index 00000000..e4109d51 --- /dev/null +++ b/refstack-ui/.gitignore @@ -0,0 +1,9 @@ +.tmp +AUTHORS +ChangeLog +build/ +cover/ +dist +node_modules +npm-debug.log +app/assets/lib diff --git a/refstack-ui/README.rst b/refstack-ui/README.rst new file mode 100644 index 00000000..942d677a --- /dev/null +++ b/refstack-ui/README.rst @@ -0,0 +1,36 @@ +======================= +Refstack User Interface +======================= + +User interface for interacting with the Refstack API. + +Setup +===== + +You can start a development server by doing the following: + +Install NodeJS and NPM: + +:code:`curl -sL https://deb.nodesource.com/setup | sudo bash -` + +:code:`sudo apt-get install nodejs` + +From the Refstack project root directory, move into the UI folder: + +:code:`cd refstack-ui` + +Install dependencies and start the server: + +:code:`npm start` + +Doing this will automatically perform :code:`npm start` and :code:`bower install` +to get all dependencies. + +By default, as noted in package.json, the server will use 0.0.0.0:8080. + +Test +==== + +To run unit tests, simply perform the following: + +:code:`npm test` diff --git a/refstack-ui/app/app.js b/refstack-ui/app/app.js new file mode 100644 index 00000000..5ad00355 --- /dev/null +++ b/refstack-ui/app/app.js @@ -0,0 +1,30 @@ +'use strict'; + +/* App Module */ + +var refstackApp = angular.module('refstackApp', [ + 'ui.router', 'ui.bootstrap']); + +refstackApp.config(['$stateProvider', '$urlRouterProvider', + function($stateProvider, $urlRouterProvider) { + $urlRouterProvider.otherwise('/'); + $stateProvider. + state('home', { + url: '/', + templateUrl: '/components/home/home.html' + }). + state('about', { + url: '/about', + templateUrl: '/components/about/about.html' + }). + state('capabilities', { + url: '/capabilities', + templateUrl: '/components/capabilities/capabilities.html', + controller: 'capabilitiesController' + }). + state('results', { + url: '/results', + templateUrl: '/components/results/results.html' + }) + }]); + diff --git a/refstack-ui/app/assets/capabilities/2015.03.json b/refstack-ui/app/assets/capabilities/2015.03.json new file mode 120000 index 00000000..ac386c6d --- /dev/null +++ b/refstack-ui/app/assets/capabilities/2015.03.json @@ -0,0 +1 @@ +../../../../defcore/2015.03.json \ No newline at end of file diff --git a/refstack-ui/app/assets/css/style.css b/refstack-ui/app/assets/css/style.css new file mode 100644 index 00000000..624a172c --- /dev/null +++ b/refstack-ui/app/assets/css/style.css @@ -0,0 +1,130 @@ +body { + background: white; + color: black; + font-family: 'Helvetica Neue', 'Helvetica', 'Verdana', sans-serif; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.heading { + font-size: 3em; + font-weight: bold; + margin-bottom: 10px; + margin-top: 10px; +} + +.heading img { + height: 50px; + vertical-align: text-bottom; +} + +form { + margin: 0; + padding: 0; + border: 0; +} + +fieldset { + border: 0; +} + +input.error { + background: #FAFF78; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Futura-CondensedExtraBold', 'Futura', 'Helvetica', sans-serif; +} + +.footer { + background: none repeat scroll 0% 0% #333; +} + +.required { + color: #1D6503; +} + +.advisory { + color: #9F8501; +} + +.deprecated { + color: #B03838; +} + +.removed { + color: #801601; +} + +.checkbox { + word-spacing: 20px; + background: #F8F8F8; + padding: 10px; +} + +.capabilities { + color: #4B4B4B; +} + +.capabilities a, .criteria a { + cursor: pointer; +} + +.capabilities .capability-list-item { + border-bottom: 2px solid #AFAFAF; + padding-bottom: .6em; +} + +.capabilities .capability-name { + font-size: 1.3em; + font-weight: bold; + color: black; +} + +#criteria { + color: #4B4B4B; +} + +.criterion-name { + font-size: 1.1em; + font-weight: bold; +} + +.list-inline li:before { + content: '\00BB'; +} + +.flagged:before { + color: #E6A100; + content: '\2691'; +} + +.program-about { + font-size: .8em; +} + +.jumbotron .left { + width: 70%; +} + +.container .jumbotron { + background: #F6F6F6; + border-top: 2px solid #C9C9C9; + border-bottom: 2px solid #C9C9C9; + border-radius: 0; +} + +.jumbotron .right { + width: 30%; +} + +.jumbotron img { + width: 70%; + height: 70%; +} diff --git a/refstack-ui/app/assets/img/openstack-logo.png b/refstack-ui/app/assets/img/openstack-logo.png new file mode 100644 index 00000000..e425be6c Binary files /dev/null and b/refstack-ui/app/assets/img/openstack-logo.png differ diff --git a/refstack-ui/app/assets/img/refstack-logo.png b/refstack-ui/app/assets/img/refstack-logo.png new file mode 100755 index 00000000..fc45f3ee Binary files /dev/null and b/refstack-ui/app/assets/img/refstack-logo.png differ diff --git a/refstack-ui/app/assets/js/refstack.js b/refstack-ui/app/assets/js/refstack.js new file mode 100644 index 00000000..2ccf6b1f --- /dev/null +++ b/refstack-ui/app/assets/js/refstack.js @@ -0,0 +1,3 @@ +'use strict'; + +/* Miscellaneous Refstack JavaScript */ diff --git a/refstack-ui/app/components/about/about.html b/refstack-ui/app/components/about/about.html new file mode 100644 index 00000000..82d72825 --- /dev/null +++ b/refstack-ui/app/components/about/about.html @@ -0,0 +1,93 @@ +

+ Overview +

+

Refstack intends on being THE source of tools for interoperability testing + of OpenStack clouds.

+

Refstack provides users in the OpenStack community with a Tempest wrapper, + refstack-client, that helps to verify interoperability of their cloud + with other OpenStack clouds. It does so by validating any cloud + implementation against the OpenStack Tempest API tests.

+

+ Refstack and DefCore - The prototypical use case for Refstack provides + the DefCore Committee the tools for vendors and other users to run API + tests against their clouds to provide the DefCore committee with a reliable + overview of what APIs and capabilities are being used in the marketplace. + This will help to guide the DefCore-defined capabilities and help ensure + interoperability across the entire OpenStack ecosystem. It can also + be used to validate clouds against existing DefCore capability lists, + giving you assurance that your cloud faithfully implements OpenStack + standards. +

+

+ Value Add for Vendors - Vendors can use Refstack to demonstrate that + their distros, and/or their customers' installed clouds remain with OpenStack + after their software has been incorporated into the distro or cloud. +

+

+ Refstack consists of two parts: +

+ +

+ Get involved! +

+ diff --git a/refstack-ui/app/components/capabilities/capabilities.html b/refstack-ui/app/components/capabilities/capabilities.html new file mode 100644 index 00000000..15a9eacf --- /dev/null +++ b/refstack-ui/app/components/capabilities/capabilities.html @@ -0,0 +1,71 @@ +

DefCore Capabilities

+Version: + +

+Target Program: + +About +

+ +Capability Status: +
+ + + + +
+

Tests marked with are tests flagged by DefCore.

+ +
    +
  1. + {{capability.name}}
    + {{capability.description}}
    + Status: {{capability.status}}
    + Achievements ({{capability.achievements.length}})
    +
      +
    1. + {{achievement}} +
    2. +
    + + Tests ({{capability.tests.length}}) + +
  2. +
+
+ +
+

Criteria

+
+ +
+
+

diff --git a/refstack-ui/app/components/capabilities/capabilitiesController.js b/refstack-ui/app/components/capabilities/capabilitiesController.js new file mode 100644 index 00000000..efc1b5d0 --- /dev/null +++ b/refstack-ui/app/components/capabilities/capabilitiesController.js @@ -0,0 +1,63 @@ +'use strict'; + +/* Refstack Capabilities Controller */ + +var refstackApp = angular.module('refstackApp'); + +refstackApp.controller('capabilitiesController', ['$scope', '$http', function($scope, $http) { + $scope.version = '2015.03'; + $scope.hideAchievements = true; + $scope.hideTests = true; + $scope.target = 'platform'; + $scope.status = { + required: 'required', + advisory: '', + deprecated: '', + removed: '' + }; + + $scope.update = function() { + // Rate-limiting is an issue with this URL. Using a local copy for now. + // var content_url = 'https://api.github.com/repos/openstack/defcore/contents/'.concat($scope.version, '.json'); + var content_url = 'assets/capabilities/'.concat($scope.version, '.json'); + $http.get(content_url).success(function(data) { + //$scope.data = data; + //$scope.capabilities = JSON.parse(atob($scope.data.content.replace(/\s/g, ''))); + $scope.capabilities = data; + }).error(function(error) { + console.log(error); + $scope.capabilities = 'Error retrieving capabilities.'; + }); + } + $scope.update() + + $scope.filterProgram = function(capability){ + var components = $scope.capabilities.components; + if ($scope.target === 'platform') { + var platform_components = $scope.capabilities.platform.required; + var cap_array = []; + // For each component required for the platform program. + angular.forEach(platform_components, function(component) { + // Get each capability belonging to each status. + angular.forEach(components[component], function(capabilities) { + cap_array = cap_array.concat(capabilities); + }); + }); + return (cap_array.indexOf(capability.id) > -1); + } + else { + var cap_array = []; + angular.forEach(components[$scope.target], function(capabilities) { + cap_array = cap_array.concat(capabilities); + }); + return (cap_array.indexOf(capability.id) > -1); + } + }; + + $scope.filterStatus = function(capability){ + return capability.status === $scope.status.required || + capability.status === $scope.status.advisory || + capability.status === $scope.status.deprecated || + capability.status === $scope.status.removed; + }; +}]); diff --git a/refstack-ui/app/components/home/home.html b/refstack-ui/app/components/home/home.html new file mode 100644 index 00000000..5929645a --- /dev/null +++ b/refstack-ui/app/components/home/home.html @@ -0,0 +1,31 @@ +
+
+

OpenStack Interoperability

+

Refstack is a source of tools for OpenStack interoperability testing.

+
+
+ OpenStack +
+
+
+ +
+
+

What is Refstack?

+ +
+ +
+

OpenStack Marketing Programs

+ +
+
diff --git a/refstack-ui/app/components/results/results.html b/refstack-ui/app/components/results/results.html new file mode 100644 index 00000000..d2847d5f --- /dev/null +++ b/refstack-ui/app/components/results/results.html @@ -0,0 +1 @@ +

Community results list here.

diff --git a/refstack-ui/app/favicon.ico b/refstack-ui/app/favicon.ico new file mode 100644 index 00000000..156019aa Binary files /dev/null and b/refstack-ui/app/favicon.ico differ diff --git a/refstack-ui/app/index.html b/refstack-ui/app/index.html new file mode 100644 index 00000000..bf4c81fd --- /dev/null +++ b/refstack-ui/app/index.html @@ -0,0 +1,49 @@ + + + + + + + + Refstack + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + diff --git a/refstack-ui/app/robots.txt b/refstack-ui/app/robots.txt new file mode 100644 index 00000000..93c44208 --- /dev/null +++ b/refstack-ui/app/robots.txt @@ -0,0 +1,4 @@ +# robotstxt.org + +User-agent: * + diff --git a/refstack-ui/app/shared/filters.js b/refstack-ui/app/shared/filters.js new file mode 100644 index 00000000..0b8b31f5 --- /dev/null +++ b/refstack-ui/app/shared/filters.js @@ -0,0 +1,18 @@ +'use strict'; + +/* Refstack Filters */ + +var refstackApp = angular.module('refstackApp'); + +// Convert an object of objects to an array of objects to use with ng-repeat +// filters. +refstackApp.filter('arrayConverter', function() { + return function(objects) { + var array = []; + angular.forEach(objects, function(object, key) { + object['id'] = key; + array.push(object); + }); + return array; + }; +}); diff --git a/refstack-ui/app/shared/header/header.html b/refstack-ui/app/shared/header/header.html new file mode 100644 index 00000000..a42ddd6f --- /dev/null +++ b/refstack-ui/app/shared/header/header.html @@ -0,0 +1,26 @@ +
Refstack +Refstack +
+ + diff --git a/refstack-ui/app/shared/header/headerController.js b/refstack-ui/app/shared/header/headerController.js new file mode 100644 index 00000000..1690912b --- /dev/null +++ b/refstack-ui/app/shared/header/headerController.js @@ -0,0 +1,18 @@ +'use strict'; + +/* Refstack Header Controller */ + +var refstackApp = angular.module('refstackApp') +refstackApp.controller('headerController', ['$scope', '$location', function($scope, $location) { + $scope.navbarCollapsed = true; + $scope.isActive = function(viewLocation) { + var path = $location.path().substr(0, viewLocation.length); + if (path === viewLocation) { + // Make sure "/" only matches when viewLocation is "/". + if (!($location.path().substr(0).length > 1 && viewLocation.length === 1 )) { + return true; + } + } + return false; + }; +}]); diff --git a/refstack-ui/bower.json b/refstack-ui/bower.json new file mode 100644 index 00000000..0959df44 --- /dev/null +++ b/refstack-ui/bower.json @@ -0,0 +1,18 @@ +{ + "name": "refstack-ui", + "version": "0.0.1", + "description": "Refstack user interface", + "dependencies": { + "angular": "1.3.15", + "angular-ui-router": "0.2.13", + "angular-resource": "1.3.15", + "angular-bootstrap": "0.12.1", + "bootstrap": "3.3.2" + }, + "devDependencies": { + "angular-mocks": "1.3.15" + }, + "resolutions": { + "angular": "1.3.15" + } +} diff --git a/refstack-ui/package.json b/refstack-ui/package.json new file mode 100644 index 00000000..a6b37215 --- /dev/null +++ b/refstack-ui/package.json @@ -0,0 +1,28 @@ +{ + "version": "0.0.1", + "private": true, + "name": "refstack-ui", + "description": "A user interface for Refstack", + "license": "Apache2", + "devDependencies": { + "karma": "^0.12.23", + "karma-chrome-launcher": "^0.1.5", + "karma-jasmine": "^0.2.2", + "karma-firefox-launcher": "^0.1.3", + "protractor": "~1.0.0", + "http-server": "^0.6.1", + "tmp": "0.0.23", + "bower": "1.3.12", + "shelljs": "^0.2.6" + }, + "scripts": { + "postinstall": "bower install", + + "prestart": "npm install", + "start": "http-server ./app -a 0.0.0.0 -p 8080", + + "pretest": "npm install", + "test": "node node_modules/karma/bin/karma start tests/karma.conf.js", + "test-single-run": "node node_modules/karma/bin/karma start tests/karma.conf.js --single-run" + } +} diff --git a/refstack-ui/tests/karma.conf.js b/refstack-ui/tests/karma.conf.js new file mode 100644 index 00000000..2f6cb08b --- /dev/null +++ b/refstack-ui/tests/karma.conf.js @@ -0,0 +1,42 @@ +module.exports = function(config){ + config.set({ + + basePath : '../', + + files : [ + // Angular libraries. + 'app/assets/lib/angular/angular.js', + 'app/assets/lib/angular-ui-router/release/angular-ui-router.js', + 'app/assets/lib/angular-bootstrap/ui-bootstrap.min.js', + 'app/assets/lib/angular-mocks/angular-mocks.js', + + // JS files. + 'app/app.js', + 'app/components/**/*.js', + 'app/shared/*.js', + 'app/shared/**/*.js', + 'app/assets/js/*.js', + + // Test Specs. + 'tests/unit/*.js' + ], + + autoWatch : true, + + frameworks: ['jasmine'], + + browsers : ['Firefox'], + + plugins : [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-jasmine', + ], + + junitReporter : { + outputFile: 'test_out/unit.xml', + suite: 'unit' + } + + }); +}; diff --git a/refstack-ui/tests/unit/ControllerSpec.js b/refstack-ui/tests/unit/ControllerSpec.js new file mode 100644 index 00000000..2c2bd673 --- /dev/null +++ b/refstack-ui/tests/unit/ControllerSpec.js @@ -0,0 +1,94 @@ +'use strict'; + +/* Jasmine specs for Refstack controllers */ +describe('Refstack controllers', function() { + + describe('headerController', function() { + var scope, ctrl, $location; + beforeEach(module('refstackApp')); + + beforeEach(inject(function($rootScope, $controller, _$location_) { + scope = $rootScope.$new(); + $location = _$location_; + ctrl = $controller('headerController', {$scope: scope}); + })); + + it('should set "navbarCollapsed" to true', function() { + expect(scope.navbarCollapsed).toBe(true); + }); + + it('should have a function to check if the URL path is active', function() { + $location.path('/'); + expect($location.path()).toBe('/'); + expect(scope.isActive('/')).toBe(true); + expect(scope.isActive('/about')).toBe(false); + + $location.path('/results?cpid=123&foo=bar'); + expect($location.path()).toBe('/results?cpid=123&foo=bar'); + expect(scope.isActive('/results')).toBe(true); + }); + }); + + describe('capabilitiesController', function() { + var scope, ctrl, $httpBackend; + beforeEach(module('refstackApp')); + + beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) { + $httpBackend = _$httpBackend_; + scope = $rootScope.$new(); + ctrl = $controller('capabilitiesController', {$scope: scope}); + })); + + it('should set default states', function() { + expect(scope.hideAchievements).toBe(true); + expect(scope.hideTests).toBe(true); + expect(scope.version).toBe('2015.03'); + expect(scope.target).toBe('platform'); + expect(scope.status).toEqual({required: 'required', advisory: '', + deprecated: '', removed: ''}); + + }); + + it('should fetch the selected capabilities version', function() { + $httpBackend.expectGET('assets/capabilities/2015.03.json').respond({'foo': 'bar'}); + $httpBackend.flush(); + expect(scope.capabilities).toEqual({'foo': 'bar'}); + }); + + it('should have a function to check if a status filter is selected', function() { + expect(scope.filterStatus({'status': 'required'})).toBe(true); + expect(scope.filterStatus({'status': 'advisory'})).toBe(false); + expect(scope.filterStatus({'status': 'deprecated'})).toBe(false); + expect(scope.filterStatus({'status': 'removed'})).toBe(false); + + scope.status = { + required: 'required', + advisory: 'advisory', + deprecated: 'deprecated', + removed: 'removed' + }; + + expect(scope.filterStatus({'status': 'required'})).toBe(true); + expect(scope.filterStatus({'status': 'advisory'})).toBe(true); + expect(scope.filterStatus({'status': 'deprecated'})).toBe(true); + expect(scope.filterStatus({'status': 'removed'})).toBe(true); + }); + + it('should have a function to check if a capability belongs to a program', function() { + scope.capabilities = {'platform': {'required': ['compute']}, + 'components': { + 'compute': { + 'required': ['cap_id_1'], + 'advisory': ['cap_id_2'], + 'deprecated': ['cap_id_3'], + 'removed': ['cap_id_4'] + } + }}; + expect(scope.filterProgram({'id': 'cap_id_1'})).toBe(true); + expect(scope.filterProgram({'id': 'cap_id_2'})).toBe(true); + expect(scope.filterProgram({'id': 'cap_id_3'})).toBe(true); + expect(scope.filterProgram({'id': 'cap_id_4'})).toBe(true); + expect(scope.filterProgram({'id': 'cap_id_5'})).toBe(false); + }); + }); +}); diff --git a/refstack-ui/tests/unit/FilterSpec.js b/refstack-ui/tests/unit/FilterSpec.js new file mode 100644 index 00000000..b185acce --- /dev/null +++ b/refstack-ui/tests/unit/FilterSpec.js @@ -0,0 +1,20 @@ +'use strict'; + +/* Jasmine specs for Refstack filters */ +describe('Refstack filters', function() { + + describe('Filter: arrayConverter', function() { + var $filter; + beforeEach(module('refstackApp')); + beforeEach(inject(function(_$filter_) { + $filter = _$filter_('arrayConverter'); + })); + + it('should convert dict to array of dict values', function () { + var object = { 'id1': {'key1': 'value1'}, 'id2': {'key2': 'value2'}}; + var expected = [{'key1': 'value1', 'id': 'id1'}, + {'key2': 'value2', 'id': 'id2'}]; + expect($filter(object)).toEqual(expected); + }); + }); +});