Added groundwork for the refstack user-interface.

Used a combination of AngularJS and Bootstrap to form
a preliminary front-end website for Refstack. Most of the content
text can be considered placeholders and is subject to change.

Change-Id: Ide82783478d1863052fe54d02ca6ee88113c46b2
This commit is contained in:
Paul Van Eck 2015-04-09 16:45:53 -07:00
parent 649137a0b0
commit cfe34d9371
25 changed files with 788 additions and 0 deletions

3
refstack-ui/.bowerrc Normal file
View File

@ -0,0 +1,3 @@
{
"directory": "app/assets/lib"
}

9
refstack-ui/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.tmp
AUTHORS
ChangeLog
build/
cover/
dist
node_modules
npm-debug.log
app/assets/lib

36
refstack-ui/README.rst Normal file
View File

@ -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`

30
refstack-ui/app/app.js Normal file
View File

@ -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'
})
}]);

View File

@ -0,0 +1 @@
../../../../defcore/2015.03.json

View File

@ -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%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,3 @@
'use strict';
/* Miscellaneous Refstack JavaScript */

View File

@ -0,0 +1,93 @@
<p>
<strong>Overview</strong>
</p>
<p>Refstack intends on being THE source of tools for interoperability testing
of OpenStack clouds.</p>
<p>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.</p>
<p>
<strong>Refstack and DefCore</strong> - 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.
</p>
<p>
<strong>Value Add for Vendors</strong> - 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.
</p>
<p>
<strong>Refstack consists of two parts:</strong>
</p>
<ul>
<li>
<dl>
<dt>
<strong>refstack-api</strong>
</dt>
<dd>
<p>Our API isn't just for us to collect data from private and public cloud
vendors. It can be used by vendors in house to compare interoperability
data over time.</p>
<ul>
<li>install docs:
<a href="https://github.com/stackforge/refstack/blob/master/doc/refstack.md">doc/refstack.md</a>
</li>
<li>repository:
<a href="http://git.openstack.org/cgit/stackforge/refstack">http://git.openstack.org/cgit/stackforge/refstack</a>
</li>
<li>storyboard:
<a href="https://storyboard.openstack.org/#!/project/700">https://storyboard.openstack.org/#!/project/700</a>
</li>
<li>reviews:
<a href="https://review.openStack.org/#q,status:open+refstack,n,z">https://review.OpenStack.org/#q,status:open+refstack,n,z</a>
</li>
</ul>
</dd>
</dl>
</li>
<li>
<dl>
<dt>
<strong>refstack-client</strong>
</dt>
<dd>
<p>refstack-client contains the tools you will need to run the DefCore tests.</p>
<ul>
<li>repository:
<a href="http://git.openstack.org/cgit/stackforge/refstack-client">http://git.openstack.org/cgit/stackforge/refstack-client</a>
</li>
<li>storyboard:
<a href="https://storyboard.openstack.org/#!/project/703">https://storyboard.openstack.org/#!/project/703</a>
</li>
<li>reviews:
<a href="https://review.openstack.org/#q,status:open+refstack-client,n,z">https://review.openstack.org/#q,status:open+refstack-client,n,z</a>
</li>
</ul>
</dd>
</dl>
</li>
</ul>
<p>
<strong>Get involved!</strong>
</p>
<ul>
<li>Mailing List:
<a href="mailto:fits%40OpenStack.org">fits@OpenStack.org</a>
</li>
<li>IRC: #refstack on Freenode</li>
<li>Dev Meetings: Mondays @ 19:00 UTC in #openstack-meeting-alt on Freenode</li>
<li>Web-site:
<a href="http://refstack.net">http://refstack.net</a>
</li>
<li>Wiki:
<a href="https://wiki.OpenStack.org/wiki/refstack">https://wiki.OpenStack.org/wiki/refstack</a>
</li>
</ul>

View File

@ -0,0 +1,71 @@
<h3>DefCore Capabilities</h3>
<strong>Version:</strong>
<select ng-model="version" ng-change="update()">
<option value="2015.03">2015.03</option>
</select>
<br /><br />
<strong>Target Program:</strong>
<select ng-model="target" >
<option value="platform">OpenStack Powered Platform</option>
<option value="compute">OpenStack Powered Compute</option>
<option value="object">OpenStack Powered Object Storage</option>
</select>
<span class="program-about"><a target="_blank" href="http://www.openstack.org/brand/interop/">About</a></span>
<br /><br />
<strong>Capability Status:</strong>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="status.required" ng-true-value="'required'" ng-false-value="''">
<span class="required">Required</span>
</label>
<label>
<input type="checkbox" ng-model="status.advisory" ng-true-value="'advisory'" ng-false-value="''">
<span class="advisory">Advisory</span>
</label>
<label>
<input type="checkbox" ng-model="status.deprecated" ng-true-value="'deprecated'" ng-false-value="''">
<span class="deprecated">Deprecated</span>
</label>
<label>
<input type="checkbox" ng-model="status.removed" ng-true-value="'removed'" ng-false-value="''">
<span class="removed">Removed</span>
</label>
</div>
<p><small>Tests marked with <span class="flagged"></span> are tests flagged by DefCore.</small></p>
<ol class="capabilities">
<li class="capability-list-item" ng-repeat="capability in capabilities.capabilities | arrayConverter | filter:filterProgram | filter:filterStatus">
<span class="capability-name">{{capability.name}}</span><br />
<em>{{capability.description}}</em><br />
Status: <span class="{{capability.status}}">{{capability.status}}</span><br />
<a ng-click="hideAchievements = !hideAchievements">Achievements ({{capability.achievements.length}})</a><br />
<ol collapse="hideAchievements" class="list-inline">
<li ng-repeat="achievement in capability.achievements">
{{achievement}}
</li>
</ol>
<a ng-click="hideTests = !hideTests">Tests ({{capability.tests.length}})</a>
<ul collapse="hideTests">
<li ng-repeat="test in capability.tests">
<span ng-class="{'flagged': capability.flagged.indexOf(test) > -1}"> {{test}}</span>
</li>
</ul>
</li>
</ol>
<hr>
<div class="criteria">
<h4><a ng-click="hideCriteria = !hideCriteria">Criteria</a></h4>
<div collapse="hideCriteria">
<ul>
<li ng-repeat="(key, criterion) in capabilities.criteria">
<span class="criterion-name">{{criterion.name}}</span><br />
<em>{{criterion.Description}}</em><br />
Weight: {{criterion.weight}}
</li>
</ul>
</div>
</div>
<br /><br />

View File

@ -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;
};
}]);

View File

@ -0,0 +1,31 @@
<div class="jumbotron">
<div class="pull-left left">
<h1>OpenStack Interoperability</h1>
<p>Refstack is a source of tools for OpenStack interoperability testing.</p>
</div>
<div class="pull-right right">
<img src="assets/img/openstack-logo.png" alt="OpenStack">
</div>
<div class="clearfix"></div>
</div>
<div class="row">
<div class="col-lg-6">
<h4>What is Refstack?</h4>
<ul>
<li>Toolset for testing interoperability between OpenStack clouds.</li>
<li>Database backed website supporting collection and publication of
community test results for OpenStack.</li>
<li>User interface to display individual test run results.</li>
</ul>
</div>
<div class="col-lg-6">
<h4>OpenStack Marketing Programs</h4>
<ul>
<li>OpenStack Powered Platform</li>
<li>OpenStack Powered Compute</li>
<li>OpenStack Powered Object Storage</li>
</ul>
</div>
</div>

View File

@ -0,0 +1 @@
<p>Community results list here.</p>

BIN
refstack-ui/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<!--
Copyright (c) 2015 IBM Corp.
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.
-->
<html ng-app="refstackApp">
<head>
<meta charset="utf-8">
<meta name="description" content="Refstack">
<meta name="viewport" content="width=device-width">
<title>Refstack</title>
<link rel="shorcut icon" href="favicon.ico">
<link rel="stylesheet" href="assets/lib/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/style.css">
<script src="assets/lib/angular/angular.js"></script>
<script src="assets/lib/angular-ui-router/release/angular-ui-router.js"></script>
<script src="assets/lib/angular-bootstrap/ui-bootstrap.min.js"></script>
<script src="app.js"></script>
<script src="assets/js/refstack.js"></script>
<!-- Controllers -->
<script src="shared/header/headerController.js"></script>
<script src="components/capabilities/capabilitiesController.js"></script>
<!-- Filters -->
<script src="shared/filters.js"></script>
</head>
<body class="container">
<header ng-include src="'shared/header/header.html'"></header>
<div ui-view></div>
</body>
</html>

View File

@ -0,0 +1,4 @@
# robotstxt.org
User-agent: *

View File

@ -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;
};
});

View File

@ -0,0 +1,26 @@
<div class="heading"><a ui-sref="home"><img src="assets/img/refstack-logo.png" alt="Refstack"></a>
Refstack
</div>
<nav class="navbar navbar-default" role="navigation" ng-controller="headerController">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" ng-click="navbarCollapsed = !navbarCollapsed">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="collapse navbar-collapse" id="navbar" collapse="navbarCollapsed">
<ul class="nav navbar-nav">
<li ng-class="{ active: isActive('/')}"><a ui-sref="home">Home</a></li>
<li ng-class="{ active: isActive('/about')}"><a ui-sref="about">About</a></li>
<li ng-class="{ active: isActive('/capabilities')}"><a ui-sref="capabilities">DefCore Capabilities</a></li>
<li ng-class="{ active: isActive('/results')}"><a ui-sref="results">Community Results</a></li>
</ul>
</div>
</div>
</nav>

View File

@ -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;
};
}]);

18
refstack-ui/bower.json Normal file
View File

@ -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"
}
}

28
refstack-ui/package.json Normal file
View File

@ -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"
}
}

View File

@ -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'
}
});
};

View File

@ -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);
});
});
});

View File

@ -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);
});
});
});