summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Xu <xudan16@huawei.com>2019-03-22 07:44:31 +0000
committerGerrit Code Review <gerrit@opnfv.org>2019-03-22 07:44:31 +0000
commitaab171fe0b7b749a7414cc8d652b5f4662743106 (patch)
tree2108f5db2442bdbbd327ee80f2a17c9bf496d079
parent3b78fa2aeb6c69efb86c68ba5f6c7d7efccfb024 (diff)
parentd0bbf3b8952379883550c6eb2062476a6d15043e (diff)
Merge changes I36bbb6e6,I2f597763
* changes: Enable Web Portal for ONAP results Prepare Web Portal for ONAP integration
-rw-r--r--3rd_party/static/onap-ui/app.js202
-rw-r--r--3rd_party/static/onap-ui/assets/css/ascend.css1170
-rw-r--r--3rd_party/static/onap-ui/assets/css/combine.css4033
-rw-r--r--3rd_party/static/onap-ui/assets/css/cvp-style.css125
-rw-r--r--3rd_party/static/onap-ui/assets/css/header.css37
-rw-r--r--3rd_party/static/onap-ui/assets/css/home/home.css72
-rw-r--r--3rd_party/static/onap-ui/assets/css/index.css4
-rw-r--r--3rd_party/static/onap-ui/assets/img/icon.pngbin0 -> 7881 bytes
-rw-r--r--3rd_party/static/onap-ui/assets/img/logo.pngbin0 -> 21360 bytes
-rw-r--r--3rd_party/static/onap-ui/components/application/application.html111
-rw-r--r--3rd_party/static/onap-ui/components/application/applicationController.js110
-rw-r--r--3rd_party/static/onap-ui/components/auth-failure/authFailureController.js33
-rw-r--r--3rd_party/static/onap-ui/components/directory/directory.html34
-rw-r--r--3rd_party/static/onap-ui/components/directory/directoryController.js45
-rw-r--r--3rd_party/static/onap-ui/components/home/home.html170
-rw-r--r--3rd_party/static/onap-ui/components/home/homeController.js58
-rw-r--r--3rd_party/static/onap-ui/components/logout/logout.html1
-rw-r--r--3rd_party/static/onap-ui/components/logout/logoutController.js44
-rw-r--r--3rd_party/static/onap-ui/components/profile/profile.html88
-rw-r--r--3rd_party/static/onap-ui/components/profile/profileController.js68
-rw-r--r--3rd_party/static/onap-ui/components/results-report/data/2019.04/heat-testcases.json13
-rw-r--r--3rd_party/static/onap-ui/components/results-report/data/2019.04/tosca-testcases.json13
-rw-r--r--3rd_party/static/onap-ui/components/results-report/partials/reportDetails.html60
-rw-r--r--3rd_party/static/onap-ui/components/results-report/resultsReport.html33
-rw-r--r--3rd_party/static/onap-ui/components/results-report/resultsReportController.js224
-rw-r--r--3rd_party/static/onap-ui/components/results/modal/applicationModal.html158
-rw-r--r--3rd_party/static/onap-ui/components/results/modal/applicationView.html103
-rw-r--r--3rd_party/static/onap-ui/components/results/modal/reviewsModal.html41
-rw-r--r--3rd_party/static/onap-ui/components/results/modal/sharedModal.html16
-rw-r--r--3rd_party/static/onap-ui/components/results/results.html168
-rw-r--r--3rd_party/static/onap-ui/components/results/resultsController.js614
-rw-r--r--3rd_party/static/onap-ui/config.json1
-rw-r--r--3rd_party/static/onap-ui/favicon.icobin0 -> 2175 bytes
-rw-r--r--3rd_party/static/onap-ui/index.html79
-rw-r--r--3rd_party/static/onap-ui/package.json36
-rw-r--r--3rd_party/static/onap-ui/robots.txt4
-rw-r--r--3rd_party/static/onap-ui/shared/alerts/alertModal.html8
-rw-r--r--3rd_party/static/onap-ui/shared/alerts/alertModalFactory.js74
-rw-r--r--3rd_party/static/onap-ui/shared/filters.js100
-rw-r--r--3rd_party/static/onap-ui/shared/footer/footer.html3
-rw-r--r--3rd_party/static/onap-ui/shared/header/header.html48
-rw-r--r--3rd_party/static/onap-ui/shared/header/headerController.js63
-rw-r--r--docker/.gitignore3
-rw-r--r--docker/Dockerfile.api8
-rw-r--r--docker/Dockerfile.web17
-rw-r--r--docker/config.env.sample3
-rw-r--r--docker/docker-compose.yml47
-rw-r--r--docker/nginx/sites-available/default-onap64
-rw-r--r--docker/nginx/sites-available/default-opnfv (renamed from docker/nginx/sites-available/default)20
-rwxr-xr-xdocker/prepare-env.sh1
-rwxr-xr-xdocker/start-nginx.sh4
-rw-r--r--etc/config.ini2
-rw-r--r--opnfv_testapi/cmd/server.py10
-rw-r--r--opnfv_testapi/resources/application_handlers.py84
-rw-r--r--opnfv_testapi/resources/handlers.py15
-rw-r--r--opnfv_testapi/resources/result_handlers.py40
-rw-r--r--opnfv_testapi/resources/review_handlers.py119
-rw-r--r--opnfv_testapi/resources/review_models.py39
-rw-r--r--opnfv_testapi/resources/test_handlers.py47
-rw-r--r--opnfv_testapi/router/url_mappings.py15
-rw-r--r--opnfv_testapi/tornado_swagger/swagger.py3
-rw-r--r--opnfv_testapi/ui/auth/sign.py17
-rw-r--r--opnfv_testapi/ui/auth/user.py52
-rw-r--r--opnfv_testapi/ui/root.py10
-rw-r--r--requirements.txt2
65 files changed, 8781 insertions, 105 deletions
diff --git a/3rd_party/static/onap-ui/app.js b/3rd_party/static/onap-ui/app.js
new file mode 100644
index 0000000..e2f1364
--- /dev/null
+++ b/3rd_party/static/onap-ui/app.js
@@ -0,0 +1,202 @@
+/*
+ * 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';
+
+ /** Main app module where application dependencies are listed. */
+ angular
+ .module('testapiApp', [
+ 'ui.router','ui.bootstrap', 'cgBusy',
+ 'ngResource', 'angular-confirm', 'ngDialog', 'xeditable'
+ ]);
+
+ angular
+ .module('testapiApp')
+ .config(configureRoutes);
+
+ configureRoutes.$inject = ['$stateProvider', '$urlRouterProvider'];
+
+ /**
+ * Handle application routing. Specific templates and controllers will be
+ * used based on the URL route.
+ */
+ function configureRoutes($stateProvider, $urlRouterProvider) {
+ $urlRouterProvider.otherwise('/');
+ $stateProvider.
+ state('home', {
+ url: '/',
+ templateUrl: 'onap-ui/components/home/home.html',
+ controller: 'HomeController as ctrl'
+ }).
+ state('directory', {
+ url: '/directory/:companyID&:logo',
+ templateUrl: 'onap-ui/components/directory/directory.html',
+ controller: 'DirectoryController as ctrl'
+ }).
+ state('userResults', {
+ url: '/user_results',
+ templateUrl: 'onap-ui/components/results/results.html',
+ controller: 'ResultsController as ctrl'
+ }).
+ state('communityResults', {
+ url: '/community_results',
+ templateUrl: 'onap-ui/components/results/results.html',
+ controller: 'ResultsController as ctrl'
+ }).
+ state('resultsDetail', {
+ url: '/results/:testID&:innerID',
+ templateUrl: 'onap-ui/components/results-report' +
+ '/resultsReport.html',
+ controller: 'ResultsReportController as ctrl'
+ }).
+ state('profile', {
+ url: '/profile',
+ templateUrl: 'onap-ui/components/profile/profile.html',
+ controller: 'ProfileController as ctrl'
+ }).
+ state('authFailure', {
+ url: '/auth_failure',
+ templateUrl: 'onap-ui/components/home/home.html',
+ controller: 'AuthFailureController as ctrl'
+ }).
+ state('logout', {
+ url: '/logout',
+ templateUrl: 'onap-ui/components/logout/logout.html',
+ controller: 'LogoutController as ctrl'
+ }).
+ state('application', {
+ url: '/application',
+ templateUrl: '/onap-ui/components/application/application.html',
+ controller: 'ApplicationController as ctrl'
+ });
+ }
+
+ angular
+ .module('testapiApp')
+ .config(disableHttpCache);
+
+ disableHttpCache.$inject = ['$httpProvider'];
+
+ /**
+ * Disable caching in $http requests. This is primarily for IE, as it
+ * tends to cache Angular IE requests.
+ */
+ function disableHttpCache($httpProvider) {
+ if (!$httpProvider.defaults.headers.get) {
+ $httpProvider.defaults.headers.get = {};
+ }
+ $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
+ $httpProvider.defaults.headers.get.Pragma = 'no-cache';
+ }
+
+ angular
+ .module('testapiApp')
+ .run(setup);
+
+ setup.$inject = [
+ '$http', '$rootScope', '$window', '$state', 'testapiApiUrl'
+ ];
+
+ /**
+ * Set up the app with injections into $rootscope. This is mainly for auth
+ * functions.
+ */
+ function setup($http, $rootScope, $window, $state, testapiApiUrl) {
+
+ $rootScope.auth = {};
+ $rootScope.auth.doSignIn = doSignIn;
+ $rootScope.auth.doSignOut = doSignOut;
+ $rootScope.auth.doSignCheck = doSignCheck;
+ $rootScope.auth.canReview = canReview;
+
+
+ var sign_in_url = testapiApiUrl + '/auth/signin';
+ var sign_out_url = testapiApiUrl + '/auth/signout';
+ var profile_url = testapiApiUrl + '/profile';
+
+ function canReview(user) {
+ if (user.role.indexOf('reviewer') != -1) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /** This function initiates a sign in. */
+ function doSignIn(type) {
+ $rootScope.auth.type = type;
+ $window.location.href = sign_in_url+"?type="+type;
+ }
+
+ /** This function will initate a sign out. */
+ function doSignOut() {
+ var resp = confirm("Are you sure to sign out?");
+ if (!resp)
+ return;
+ $rootScope.auth.currentUser = null;
+ $rootScope.auth.isAuthenticated = false;
+ $window.location.href = sign_out_url+"?type="+$rootScope.auth.type;
+ }
+
+ /**
+ * This function checks to see if a user is logged in and
+ * authenticated.
+ */
+ function doSignCheck() {
+ return $http.get(profile_url, {withCredentials: true}).
+ success(function (data) {
+ $rootScope.auth.currentUser = data;
+ $rootScope.auth.isAuthenticated = true;
+ $rootScope.auth.type = data.type;
+ }).
+ error(function () {
+ $rootScope.auth.currentUser = null;
+ $rootScope.auth.isAuthenticated = false;
+ });
+ }
+
+ $rootScope.auth.doSignCheck();
+ }
+
+ angular
+ .element(document)
+ .ready(loadConfig);
+
+ /**
+ * Load config and start up the angular application.
+ */
+ function loadConfig() {
+
+ var $http = angular.injector(['ng']).get('$http');
+
+ /**
+ * Store config variables as constants, and start the app.
+ */
+ function startApp(config) {
+ // Add config options as constants.
+ angular.forEach(config, function(value, key) {
+ angular.module('testapiApp').constant(key, value);
+ });
+
+ angular.bootstrap(document, ['testapiApp']);
+ }
+
+ $http.get('onap-ui/config.json').success(function (data) {
+ startApp(data);
+ }).error(function () {
+ startApp({});
+ });
+ }
+})();
diff --git a/3rd_party/static/onap-ui/assets/css/ascend.css b/3rd_party/static/onap-ui/assets/css/ascend.css
new file mode 100644
index 0000000..cd7370f
--- /dev/null
+++ b/3rd_party/static/onap-ui/assets/css/ascend.css
@@ -0,0 +1,1170 @@
+.ascend .container-wrap,
+.ascend .project-title,
+body .vc_text_separator div,
+.carousel-wrap[data-full-width="true"] .carousel-heading,
+.carousel-wrap span.left-border,
+.carousel-wrap span.right-border,
+#page-header-wrap,
+.page-header-no-bg,
+#full_width_portfolio .project-title.parallax-effect,
+.portfolio-items .col,
+.page-template-template-portfolio-php .portfolio-items .col.span_3,
+.page-template-template-portfolio-php .portfolio-items .col.span_4 {
+ background-color: #f6f6f6;
+}
+
+#call-to-action .triangle {
+ color: #f6f6f6;
+}
+
+.ascend #footer-outer #footer-widgets .col ul li,
+.ascend #sidebar div ul li,
+.ascend #sidebar .widget.widget_categories li,
+.ascend #sidebar .widget.widget_pages li,
+.ascend #sidebar .widget.widget_nav_menu li {
+ border: none !important;
+ padding: 4px 0;
+}
+
+.ascend #sidebar .widget.widget_categories li,
+.ascend #sidebar .widget.widget_pages li,
+.ascend #sidebar .widget.widget_nav_menu li,
+.ascend #footer-outer .widget.widget_categories li,
+.ascend #footer-outer .widget.widget_pages li,
+.ascend #footer-outer .widget.widget_nav_menu li {
+ padding: 4px 0 !important;
+}
+
+.ascend #sidebar .widget.widget_categories li a,
+.ascend #sidebar .widget.widget_pages li a,
+.ascend #sidebar .widget.widget_nav_menu li a,
+.ascend #footer-outer .widget.widget_categories li a,
+.ascend #footer-outer .widget.widget_pages li a,
+.ascend #footer-outer .widget.widget_nav_menu li a {
+ padding: 0 !important;
+ border: none !important;
+}
+
+.ascend.woocommerce #sidebar div ul li {
+ padding: 6px 0 !important;
+}
+
+.ascend #footer-outer #footer-widgets .col ul li a,
+.ascend #sidebar div ul li a {
+ display: block;
+}
+
+.ascend #footer-outer .widget h4,
+.ascend #sidebar h4 {
+ margin-bottom: 12px;
+}
+
+.ascend #footer-outer #copyright {
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+ background-color: transparent;
+}
+
+.ascend #footer-outer[data-using-widget-area="false"] #copyright {
+ border: none;
+}
+
+.nectar-progress-bar span strong {
+ padding: 4px 0 !important;
+ background-color: transparent;
+ color: inherit;
+}
+
+.ascend {
+ background-color: #fff;
+}
+
+.nectar-progress-bar span strong:after {
+ display: none;
+}
+
+.nectar-progress-bar .bar-wrap {
+ background-color: rgba(0, 0, 0, 0.043);
+}
+
+.ascend .nectar-progress-bar .bar-wrap,
+.ascend .nectar-progress-bar span {
+ -webkit-box-shadow: none;
+ -o-box-shadow: none;
+ box-shadow: none;
+ -webkit-border-radius: 0;
+ -o-border-radius: 0;
+ border-radius: 0;
+}
+
+body .carousel-wrap[data-full-width="false"] .control-wrap {
+ right: 10px;
+ text-align: center;
+}
+
+.carousel-wrap[data-full-width="false"] .control-wrap .carousel-prev {
+ padding-right: 10px;
+ padding-left: 10px;
+ margin-right: 0;
+}
+
+.carousel-wrap[data-full-width="false"] .control-wrap .carousel-next {
+ padding-left: 10px;
+ margin-left: 0;
+}
+
+.carousel-wrap[data-full-width="false"] .control-wrap .carousel-prev,
+.carousel-wrap[data-full-width="false"] .control-wrap .carousel-next {
+ right: 0 !important;
+ position: relative;
+ display: block;
+ float: left;
+}
+
+.ascend .carousel-next,
+.ascend .carousel-prev {
+ background-color: transparent;
+ height: 23px;
+ width: 10px;
+ line-height: 22px;
+ font-size: 14px;
+ cursor: pointer;
+ top: 1px !important;
+ transition: all .25s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .25s cubic-bezier(0.12, 0.75, 0.4, 1);
+ box-sizing: content-box;
+}
+
+.carousel-wrap .control-wrap .item-count {
+ height: 23px;
+ top: -1px;
+ right: -2px;
+ line-height: 22px;
+ letter-spacing: 4px;
+ position: relative;
+ display: block;
+ float: left;
+ z-index: 10;
+ cursor: pointer;
+ transition: all .25s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .25s cubic-bezier(0.12, 0.75, 0.4, 1);
+}
+
+.ascend [data-full-width="false"] .carousel-next:after,
+.ascend [data-full-width="false"] .carousel-prev:after {
+ display: block;
+ content: ' ';
+ position: absolute;
+ width: 24px;
+ height: 2px;
+ background-color: #000;
+ top: 8px;
+ opacity: 0;
+ left: -4px;
+ cursor: pointer;
+ transform: translateX(-20px);
+ transition: all .25s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .25s cubic-bezier(0.12, 0.75, 0.4, 1);
+}
+
+.ascend .light [data-full-width="false"] .carousel-next:after,
+.ascend .light [data-full-width="false"] .carousel-prev:after {
+ background-color: #fff;
+}
+
+.ascend .light .carousel-next i,
+.ascend .light .carousel-prev i {
+ color: #fff;
+}
+
+.ascend [data-full-width="false"] .carousel-next:after {
+ left: 5px;
+}
+
+.ascend [data-full-width="false"] .carousel-next:hover:after {
+ opacity: 1;
+ transform: translateX(-12px);
+}
+
+.ascend [data-full-width="false"] .carousel-prev:after {
+ transform: translateX(20px);
+}
+
+.ascend [data-full-width="false"] .carousel-prev:hover:after {
+ opacity: 1;
+ transform: translateX(0px);
+}
+
+.ascend [data-full-width="false"] .carousel-prev:hover i {
+ transform: translateX(-18px);
+}
+
+.ascend [data-full-width="false"] .carousel-prev.next-hovered,
+.ascend [data-full-width="false"] .item-count.next-hovered {
+ transform: translateX(-18px);
+}
+
+.ascend .carousel-next:hover,
+.ascend .carousel-prev:hover {
+ background-color: transparent !important;
+}
+
+.carousel-wrap[data-full-width="true"] .carousel-prev,
+.carousel-wrap[data-full-width="true"] .carousel-next {
+ border: none !important;
+}
+
+html .ascend .carousel-heading .container .carousel-prev {
+ right: 30px;
+}
+
+html .ascend[data-ext-responsive="true"] .carousel-heading .container .carousel-prev {
+ right: 40px;
+}
+
+.ascend .carousel-next i,
+.ascend .carousel-prev i {
+ color: #000;
+ font-size: 22px;
+ line-height: 17px;
+ height: 20px;
+ transition: all .25s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .25s cubic-bezier(0.12, 0.75, 0.4, 1);
+}
+
+.ascend .carousel-next:hover,
+.ascend .carousel-prev:hover {
+ border-color: transparent;
+}
+
+.ascend .light .carousel-next:hover i,
+.ascend .light .carousel-prev:hover i {
+ color: #fff;
+}
+
+.ascend .nectar-button.see-through,
+body.ascend .nectar-button.see-through,
+.swiper-slide .button a,
+body.ascend .nectar-button.see-through-2,
+.ascend .nectar-button,
+#to-top {
+ border-radius: 0 !important;
+ box-shadow: none !important;
+}
+
+.ascend #header-outer .cart-menu .cart-icon-wrap .icon-salient-cart {
+ font-size: 22px !important;
+ left: 0;
+ transition: all .2s linear !important;
+ -o-transition: all .2s linear !important;
+ -webkit-transition: all .2s linear !important;
+}
+
+.ascend[data-is="minimal"] #header-outer .cart-menu .cart-icon-wrap .icon-salient-cart {
+ font-size: 18px !important;
+}
+
+.ascend .icon-salient-cart:before {
+ content: "\e606";
+}
+
+.ascend #header-outer a.cart-contents .cart-wrap span:before {
+ display: none !important;
+}
+
+.ascend #header-outer .cart-menu .cart-icon-wrap {
+ width: 53px !important;
+}
+
+.ascend #header-outer .cart-menu {
+ padding-left: 29px;
+}
+
+.ascend #header-outer .cart-wrap {
+ top: -7px !important;
+ margin-right: 17px;
+}
+
+.ascend #header-outer .cart-menu-wrap {
+ right: 0 !important;
+}
+
+#header-outer .cart-wrap {
+ font-size: 11px;
+}
+
+.ascend #header-outer[data-full-width="true"] header#top nav ul #search-btn {
+ margin-left: 1px !important;
+ visibility: hidden;
+}
+
+.ascend #header-outer[data-full-width="true"][data-remove-border="true"] header#top nav ul #search-btn {
+ margin-left: 22px !important;
+}
+
+.ascend #header-outer[data-full-width="true"][data-cart="true"] header#top nav ul .slide-out-widget-area-toggle {
+ margin-left: 82px !important;
+ margin-right: -82px;
+ visibility: hidden;
+ position: relative;
+}
+
+.ascend[data-user-set-ocm="1"] #header-outer[data-full-width="true"][data-cart="false"] header#top nav ul .slide-out-widget-area-toggle {
+ visibility: hidden;
+ margin-right: -30px;
+ margin-left: 28px;
+}
+
+.ascend[data-header-search="false"] #header-outer[data-full-width="true"][data-cart="false"] header#top nav ul .slide-out-widget-area-toggle {
+ margin-left: 18px;
+}
+
+.ascend[data-header-search="false"] #header-outer[data-full-width="true"][data-cart="false"][data-format="centered-menu"] header#top nav ul #social-in-menu {
+ margin-right: 13px;
+}
+
+.ascend[data-header-search="false"] #header-outer[data-full-width="true"][data-cart="true"] header#top nav ul .slide-out-widget-area-toggle {
+ margin-left: 102px !important;
+}
+
+.ascend[data-slide-out-widget-area="true"] #header-outer[data-full-width="true"] .cart-menu-wrap {
+ right: 80px !important;
+}
+
+.ascend[data-slide-out-widget-area="true"] #header-outer[data-full-width="true"] .cart-outer[data-user-set-ocm="off"] .cart-menu-wrap {
+ right: 0 !important;
+}
+
+.ascend #header-outer[data-full-width="true"] header#top nav ul #search-btn a {
+ padding-left: 25px !important;
+ padding-right: 25px !important;
+}
+
+.ascend #header-outer[data-full-width="true"] header#top nav ul .slide-out-widget-area-toggle a {
+ padding-left: 28px !important;
+ padding-right: 28px !important;
+}
+
+.ascend #header-outer.transparent[data-transparent-header="true"][data-full-width="true"] header#top nav ul #search-btn a,
+.ascend #header-outer.transparent[data-transparent-header="true"][data-full-width="true"] header#top nav ul .slide-out-widget-area-toggle a {
+ border-left: 1px solid rgba(255, 255, 255, 0.25);
+}
+
+.ascend #header-outer[data-full-width="true"] header#top nav ul #search-btn a,
+.ascend #header-outer[data-full-width="true"] header#top nav ul .slide-out-widget-area-toggle a {
+ border-left: 1px solid rgba(0, 0, 0, 0.07);
+}
+
+.ascend[data-header-color="dark"] #header-outer[data-full-width="true"] header#top nav ul #search-btn a,
+body.ascend[data-header-color="dark"] #header-outer .cart-menu,
+.ascend[data-header-color="dark"] #header-outer[data-full-width="true"] header#top nav ul .slide-out-widget-area-toggle a {
+ border-left: 1px solid rgba(255, 255, 255, 0.13);
+}
+
+.ascend #header-outer[data-full-width="true"][data-cart="true"] header#top nav > ul.buttons,
+.ascend #header-outer[data-full-width="true"] header#top nav > ul.product_added.buttons,
+.ascend #boxed #header-outer[data-cart="true"] header#top nav > ul.buttons,
+.ascend #boxed #header-outer header#top nav > ul.product_added.buttons {
+ padding-right: 55px !important;
+}
+
+.ascend[data-header-search="false"][data-slide-out-widget-area="false"] #header-outer[data-full-width="true"][data-cart="true"] header#top nav > ul.buttons,
+.ascend[data-header-search="false"][data-slide-out-widget-area="false"] #header-outer[data-full-width="true"] header#top nav > ul.product_added.buttons,
+.ascend[data-header-search="false"][data-slide-out-widget-area="false"] #boxed #header-outer[data-cart="true"] header#top nav > ul.buttons,
+.ascend[data-header-search="false"][data-slide-out-widget-area="false"] #boxed #header-outer header#top nav > ul.product_added.buttons {
+ padding-right: 80px !important;
+}
+
+@media only screen and (min-width: 1000px) {
+.ascend #header-outer[data-full-width="true"][data-cart="false"] header > .container #search-btn {
+ margin-right: -28px !important;
+}
+}
+
+.ascend #header-outer[data-full-width="true"] header#top nav ul #search-btn > div {
+ border: none !important;
+}
+
+.ascend #header-outer a.cart-contents .cart-wrap span {
+ border-radius: 99px !important;
+ font: bold 11px/16px Arial;
+ line-height: 18px !important;
+ width: 18px !important;
+ padding: 0 1px !important;
+ visibility: hidden;
+}
+
+.ascend #header-outer .sf-menu ul li a {
+ border-bottom: none;
+}
+
+.ascend #header-outer .first-load a.cart-contents .cart-wrap span {
+ visibility: visible;
+ animation: .6s ease-in-out .12s normal both 1 bounce_in_animation;
+ -webkit-animation: .6s ease-in-out .12s normal both 1 bounce_in_animation;
+}
+
+.ascend #header-outer .static a.cart-contents span {
+ visibility: visible;
+}
+
+.ascend #header-outer .has_products .cart-menu .cart-icon-wrap .icon-salient-cart {
+ transition: all .2s linear !important;
+ -o-transition: all .2s linear !important;
+ -webkit-transition: all .2s linear !important;
+}
+
+body.ascend #header-outer .cart-menu {
+ border-left: 1px solid rgba(0, 0, 0, 0.07);
+ background-color: transparent !important;
+}
+
+body.ascend #boxed #header-outer .cart-menu-wrap {
+ position: absolute !important;
+ top: 0 !important;
+ box-shadow: none !important;
+}
+
+body.ascend #boxed #header-outer .widget_shopping_cart,
+body.ascend.woocommerce #boxed .cart-notification {
+ position: absolute !important;
+}
+
+body.ascend #boxed #header-outer .cart-menu-wrap .cart-menu {
+ box-shadow: none !important;
+}
+
+header#top #mobile-cart-link i {
+ line-height: 36px !important;
+}
+
+body[data-is="minimal"] header#top #mobile-cart-link i {
+ line-height: 34px !important;
+}
+
+body.ascend #search-outer {
+ background-color: rgba(255, 255, 255, 0.96) !important;
+ height: 95% !important;
+ position: fixed;
+ transform: rotateX(90deg);
+ padding: 0;
+ z-index: 1010 !important;
+}
+
+body.ascend #search-outer .container {
+ height: auto !important;
+ float: none !important;
+ width: 100% !important;
+ padding: 0 40px;
+ position: static;
+}
+
+#header-outer #search {
+ position: static !important;
+}
+
+body.ascend #search-outer #search input[type="text"] {
+ color: #000 !important;
+ height: auto !important;
+ font-size: 80px !important;
+ text-align: center !important;
+}
+
+#search-outer > #search form {
+ width: 100% !important;
+ float: none !important;
+}
+
+#search-outer > #search form,
+#search-outer #search .span_12 span {
+ opacity: 0;
+ position: relative;
+}
+
+#search-outer #search #close {
+ position: absolute;
+ top: 25px;
+ right: 25px;
+}
+
+#search-outer #search #close a {
+ right :0 !important;
+ top: 0 !important;
+ transition: all .47s cubic-bezier(0.3, 1, 0.3, 0.95) 0;
+ -webkit-transition: all .47s cubic-bezier(0.3, 1, 0.3, 0.95) 0;
+}
+
+#search-outer #search #close a:hover {
+ -ms-transform: rotate(90deg) translateZ(0);
+ -webkit-transform: rotate(90deg) translateZ(0);
+ transform: rotate(90deg) translateZ(0);
+}
+
+#search-outer #search .span_12 span {
+ text-align: center;
+ display: block;
+ color: rgba(0, 0, 0, 0.4);
+ margin-top: 15px;
+}
+
+#boxed #search-outer {
+ width: auto !important;
+ min-width: 1200px;
+ left: auto !important;
+}
+
+body.ascend #search-outer #search #close a span {
+ color: #000;
+}
+
+body.ascend #search-outer .ui-widget-content {
+ top: 90px !important;
+}
+
+.ascend #search-results .result span.bottom-line,
+.ascend .masonry-blog-item span.bottom-line,
+.ascend .masonry-blog-item .more-link {
+ display: none;
+}
+
+.ascend .masonry-blog-item .inner-wrap,
+.ascend .masonry-blog-item .mejs-container .mejs-controls {
+ box-shadow: none !important;
+}
+
+.ascend .masonry-blog-item .post-meta {
+ padding: 0 18px 18px !important;
+}
+
+.ascend .post .nectar-love-wrap {
+ line-height: 20px;
+}
+
+.ascend .masonry-blog-item .inner-wrap {
+ padding: 0;
+}
+
+.ascend .masonry-blog-item .content-inner .post-featured-img img,
+.ascend .masonry-blog-item .more-link,
+#post-area.masonry article.post .quote-inner,
+#post-area.masonry article.post .link-inner,
+#post-area.masonry article.post .status-inner,
+#post-area.masonry article.post .aside-inner {
+ margin-bottom: 0 !important;
+}
+
+.ascend .masonry-blog-item .content-inner {
+ padding-bottom: 0 !important;
+ border: 0 !important;
+ margin-bottom: 0 !important;
+}
+
+.ascend .masonry-blog-item .article-content-wrap {
+ padding: 16px 18px 18px !important;
+}
+
+.ascend #post-area.masonry article.post.quote .post-content .post-meta,
+.ascend #post-area.masonry article.post.link .post-content .post-meta,
+.ascend #post-area.masonry article.format-status .post-content .post-meta,
+.ascend #post-area.masonry article.post.format-aside .post-meta {
+ display: none !important;
+}
+
+.ascend article.post .content-inner {
+ border: none !important;
+}
+
+.ascend #author-bio {
+ padding-bottom: 20px;
+ border: none;
+ text-align: center;
+}
+
+.ascend #author-bio img {
+ display: block;
+ margin: 0 auto;
+ margin-bottom: 15px !important;
+ position: relative;
+}
+
+.ascend #author-bio h3 span,
+.ascend .comments-section .comment-wrap.full-width-section > h3 span {
+ display: block;
+ margin-bottom: 5px;
+ line-height: 12px;
+ font-size: 12px;
+ text-transform: none;
+}
+
+.ascend .container-wrap #author-bio #author-info {
+ width: 600px !important;
+ margin: 0 auto;
+ padding-left: 0 !important;
+}
+
+.ascend .container-wrap #author-bio #author-info p {
+ max-width:70%;
+ margin:0 auto;
+}
+
+.ascend #author-bio .nectar-button {
+ margin-top: 20px;
+}
+
+.ascend #author-bio .avatar {
+ border-radius: 100%;
+}
+
+.ascend .comment-list {
+ margin-bottom: 0 !important;
+}
+
+.ascend .comment-list .reply {
+ top: 7px;
+}
+
+.ascend .comment-list .reply a {
+ color: #000;
+ background-color: transparent;
+ border-radius: 0 !important;
+}
+
+html .ascend .comment-list .reply a:hover {
+ color: #fff !important;
+}
+
+.ascend #respond {
+ margin-top: 80px !important;
+}
+
+.ascend h3#comments {
+ text-align: center;
+}
+
+.comment-list li.comment > div,
+.comment-list li.pingback > div {
+ background-color: transparent !important;
+ box-shadow: none !important;
+ padding-left: 85px !important;
+ padding-bottom: 0 !important;
+ padding-top: 0 !important;
+ margin-top: 4em !important;
+}
+
+.comment-list li.comment > div img.avatar,
+.comment-list li.pingback > div img.avatar {
+ left: 0 !important;
+ border-radius: 100%;
+ top: 0 !important;
+}
+
+.comment-list li.comment > div p,
+.comment-list li.pingback > div p {
+ margin-top: 30px;
+}
+
+.comment-list .children {
+ background-position:left 30px !important;
+ margin-left: 26px !important;
+ padding-left: 40px !important;
+}
+
+.comment-list .says {
+ display: none;
+}
+
+.ascend #reply-title {
+ margin-bottom: 50px;
+ text-align: center !important;
+}
+
+.ascend.single-product #reply-title,
+.ascend.single-product #commentform .form-submit {
+ text-align: left !important;
+}
+
+.ascend .comment #reply-title {
+ text-align: left !important;
+}
+
+.ascend #respond #cancel-comment-reply-link {
+ padding-left: 15px;
+}
+
+.ascend .comment-wrap {
+ padding-top: 0 !important;
+ margin-top: 0 !important;
+}
+
+.ascend.single-portfolio .comment-wrap {
+ margin-top: 30px !important;
+}
+
+.ascend .comment-wrap h3#comments {
+ padding-top: 80px;
+}
+
+.ascend #author-bio.no-pagination,
+.ascend .comment-wrap {
+ border-top: 1px solid #999;
+}
+
+.ascend .comments-section[data-author-bio="false"] .comment-wrap {
+ border: none;
+}
+
+.ascend .comment-list {
+ padding-bottom: 80px;
+ border-bottom: 1px solid #999;
+}
+
+.ascend #author-bio.no-pagination.lighter-grey,
+.ascend .comment-wrap.lighter-grey {
+ border-color: #ddd;
+}
+
+.ascend .comment-wrap,
+.ascend #author-bio {
+ padding-top: 80px;
+}
+
+.single-post.ascend #page-header-bg.fullscreen-header,
+.single-post #single-below-header.fullscreen-header {
+ background-color: #f6f6f6;
+}
+
+.single-post.ascend #single-below-header.fullscreen-header {
+ border-top: 1px solid #DDD;
+ border-bottom: none !important;
+}
+
+.tagcloud a,
+#header-outer .widget_shopping_cart a.button,
+article.post .more-link span,
+.blog-recent .more-link span {
+ border-radius: 0 !important;
+}
+
+.flex-direction-nav a,
+#pagination span,
+#pagination a,
+#pagination .next.inactive,
+#pagination .prev.inactive,
+.woocommerce nav.woocommerce-pagination ul li a,
+.woocommerce .container-wrap nav.woocommerce-pagination ul li span {
+ border-radius: 0 !important;
+}
+
+.ascend .col.boxed,
+.ascend .wpb_column.boxed {
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+ -o-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+ background-color: #fff;
+ padding: 25px 15px 15px;
+ position: relative;
+ -webkit-transition: transform .2s linear,-webkit-box-shadow .2s linear;
+ -moz-transition: transform .2s linear,-moz-box-shadow .2s linear;
+ transition: transform .2s linear,box-shadow .2s linear;
+ top: 0 !important;
+}
+
+.ascend .col.boxed:hover,
+.ascend .wpb_column.boxed:hover {
+ -ms-transform: translateY(-4px) !important;
+ -webkit-transform: translateY(-4px) !important;
+ transform: translateY(-4px) !important;
+ -webkit-box-shadow: 0 17px 25px rgba(0, 0, 0, 0.13);
+ -moz-box-shadow: 0 17px 25px rgba(0, 0, 0, 0.13);
+ -o-box-shadow: 0 17px 25px rgba(0, 0, 0, 0.13);
+ box-shadow: 0 17px 25px rgba(0, 0, 0, 0.13) !important;
+}
+
+.ascend a.pp_arrow_previous,
+.ascend a.pp_arrow_next,
+.ascend .mfp-arrow-right,
+.ascend .mfp-arrow-left,
+.woocommerce .pswp__button--arrow--right,
+.woocommerce .pswp__button--arrow--left {
+ overflow: visible;
+ height: 40px;
+ width: 40px;
+ border-radius: 100px;
+ border: 2px solid rgba(255, 255, 255, 0.4);
+}
+
+.woocommerce .pswp__button--arrow--right:before,
+.woocommerce .pswp__button--arrow--left:before {
+ background-image: none;
+ line-height: 40px !important;
+ height: 40px !important;
+ width: 36px !important;
+}
+
+a.pp_arrow_previous {
+ left: 30px;
+}
+
+a.pp_arrow_next {
+ right: 30px;
+}
+
+.ascend a.pp_arrow_next:hover,
+.ascend a.pp_arrow_previous:hover,
+.ascend .mfp-arrow-right:hover,
+.ascend .mfp-arrow-left:hover,
+.woocommerce .pswp__button--arrow--right:hover,
+.woocommerce .pswp__button--arrow--left:hover {
+ border: 2px solid rgba(255, 255, 255, 0);
+}
+
+.ascend a.pp_arrow_previous .icon-default-style {
+ border-radius:0 !important;
+ width: 20px !important;
+ height: 40px !important;
+ line-height: 39px !important;
+ font-size: 24px !important;
+ font-family: FontAwesome !important;
+ margin-top: 0 !important;
+ left: -1px;
+ transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+}
+
+.ascend .mfp-arrow-left,
+.woocommerce .pswp__button--arrow--left {
+ height: 40px !important;
+ line-height: 40px !important;
+ font-size: 24px !important;
+ font-family: FontAwesome !important;
+ margin-top: 0 !important;
+ opacity: 1;
+ left: 40px;
+ transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+}
+
+.ascend a.pp_arrow_next .icon-default-style {
+ border-radius: 0 !important;
+ width: 20px !important;
+ line-height: 39px !important;
+ height: 40px !important;
+ font-size: 24px !important;
+ margin-top: 0 !important;
+ font-family: FontAwesome !important;
+ left: 1px;
+ transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+}
+
+.ascend .mfp-arrow-right,
+.woocommerce .pswp__button--arrow--right {
+ line-height: 40px !important;
+ height: 40px !important;
+ font-size: 24px !important;
+ margin-top: 0 !important;
+ font-family: FontAwesome !important;
+ opacity: 1;
+ right: 40px;
+ transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+}
+
+.ascend a.pp_arrow_previous .icon-default-style:after,
+.ascend a.pp_arrow_next .icon-default-style:after,
+.ascend .mfp-arrow-left:after,
+.ascend .mfp-arrow-right:after,
+.woocommerce .pswp__button--arrow--right:after,
+.woocommerce .pswp__button--arrow--left:after {
+ display: block;
+ content: ' ';
+ position: absolute;
+ width: 36px;
+ height: 2px;
+ background-color: #fff;
+ top: 19px;
+ opacity: 0;
+ left: -6px;
+ cursor: pointer;
+ transform: translateX(-37px);
+ transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+ pointer-events: none;
+}
+
+.ascend .mfp-arrow-left:after,
+.ascend .mfp-arrow-right:after,
+.woocommerce .pswp__button--arrow--right:after,
+.woocommerce .pswp__button--arrow--left:after {
+ top: 17px;
+ -ms-transform: translateX(-27px);
+ -webkit-transform: translateX(-27px);
+ transform: translateX(-27px);
+}
+
+.ascend .mfp-arrow-left:after,
+.woocommerce .pswp__button--arrow--left:after {
+ left: 65px;
+}
+
+.ascend a.pp_arrow_previous .icon-default-style:after {
+ left: 59px;
+}
+
+.ascend a.pp_arrow_previous .icon-default-style:before,
+.mfp-arrow-left:before,
+.woocommerce .pswp__button--arrow--left:before {
+ content: "\f104";
+ color: #fff;
+}
+
+.mfp-arrow-left:before,
+.woocommerce .pswp__button--arrow--left:before {
+ top: -3px;
+ left: -1px;
+ display: block;
+ position: relative;
+ transition: all .35s cubic-bezier(0.12, 0.75 , 0.4, 1);
+ -webkit-transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+}
+
+.ascend a.pp_arrow_next .icon-default-style:before,
+.mfp-arrow-right:before,
+.woocommerce .pswp__button--arrow--right:before {
+ content: "\f105";
+ color: #fff;
+}
+
+.mfp-arrow-right:before,
+.woocommerce .pswp__button--arrow--right:before {
+ top: -3px;
+ right: -1px;
+ display: block;
+ transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+ -webkit-transition: all .35s cubic-bezier(0.12, 0.75, 0.4, 1);
+ position: relative;
+}
+
+.ascend a.pp_arrow_next:hover .icon-default-style:after,
+.mfp-arrow-right:hover:after,
+.woocommerce .pswp__button--arrow--right:hover:after {
+ opacity: 1;
+ -ms-transform: translateX(-18px);
+ -webkit-transform: translateX(-18px);
+ transform: translateX(-18px);
+}
+
+.mfp-arrow-right:hover:before,
+.woocommerce .pswp__button--arrow--right:hover:before {
+ -ms-transform: translateX(9px);
+ -webkit-transform: translateX(9px);
+ transform: translateX(9px);
+}
+
+.mfp-arrow-left:hover:before,
+.woocommerce .pswp__button--arrow--left:hover:before {
+ -ms-transform: translateX(-9px);
+ -webkit-transform: translateX(-9px);
+ transform: translateX(-9px);
+}
+
+.mfp-arrow-right:hover:after,
+.woocommerce .pswp__button--arrow--right:hover:after {
+ -ms-transform: translateX(-1px);
+ -webkit-transform: translateX(-1px);
+ transform: translateX(-1px);
+}
+
+.ascend a.pp_arrow_next:hover .icon-default-style {
+ -ms-transform: translateX(7px);
+ -webkit-transform: translateX(7px);
+ transform: translateX(7px);
+}
+
+.ascend a.pp_arrow_previous:hover .icon-default-style:after,
+.mfp-arrow-left:hover:after,
+.woocommerce .pswp__button--arrow--left:hover:after {
+ opacity: 1;
+ -ms-transform: translateX(-51px);
+ -webkit-transform: translateX(-51px);
+ transform: translateX(-51px);
+}
+
+.mfp-arrow-left:hover:after,
+.woocommerce .pswp__button--arrow--left:hover:after {
+ -ms-transform: translateX(-58px);
+ -webkit-transform: translateX(-58px);
+ transform: translateX(-58px);
+}
+
+.ascend a.pp_arrow_previous:hover .icon-default-style {
+ -ms-transform: translateX(-7px);
+ -webkit-transform: translateX(-7px);
+ transform: translateX(-7px);
+}
+
+.container-wrap input[type="text"],
+.container-wrap textarea,
+.container-wrap input[type="email"],
+.container-wrap input[type="password"],
+.container-wrap input[type="tel"],
+.container-wrap input[type="url"],
+.container-wrap input[type="search"],
+.container-wrap input[type="date"] {
+ background-color: transparent !important;
+ border: 1px solid #ccc !important;
+ -webkit-box-shadow: none !important;
+ -o-box-shadow: none !important;
+ box-shadow: none !important;
+ font-size: 16px !important;
+ padding: 16px !important;
+}
+
+.container-wrap input[type="text"]:focus,
+.container-wrap textarea:focus,
+.container-wrap input[type="email"]:focus,
+.container-wrap input[type="password"]:focus,
+.container-wrap input[type="tel"]:focus,
+.container-wrap input[type="url"]:focus,
+.container-wrap input[type="search"]:focus,
+.container-wrap input[type="date"]:focus {
+ border-color: #999 !important;
+}
+
+.ascend #commentform .form-submit {
+ text-align: center;
+ padding-bottom: 50px;
+ margin-top: 20px;
+}
+
+.ascend .container-wrap input[type="submit"],
+.ascend .container-wrap button[type="submit"],
+.woocommerce-cart .wc-proceed-to-checkout a.checkout-button {
+ padding: 16px !important;
+ border-radius: 0 !important;
+}
+
+body[data-button-style="rounded"].ascend .container-wrap input[type="submit"],
+body[data-button-style="rounded"].ascend .container-wrap button[type="submit"] {
+ padding: 16px 23px !important;
+}
+
+body[data-button-style="rounded"][data-form-submit="see-through"].ascend .container-wrap .widget_search input[type="submit"],
+body[data-button-style="rounded"][data-form-submit="see-through"].ascend .container-wrap .widget_search button[type="submit"] {
+ padding: 12px 23px !important;
+}
+
+.ascend .woocommerce .actions .button {
+ height: auto !important;
+ padding: 14px !important;
+}
+
+.ascend .cart .quantity input.plus,
+.ascend .cart .quantity input.minus {
+ font-weight: 400 !important;
+ height: 46px;
+ font-size: 16px;
+ width: 46px;
+}
+
+.ascend .cart .quantity input.qty {
+ height: 46px;
+ width: 46px;
+}
+
+.ascend .widget_search .search-form input[type=submit],
+.ascend .newsletter-widget form input[type=submit] {
+ line-height: 24px;
+}
+
+.container-wrap .span_12.light input[type="text"],
+.container-wrap .span_12.light textarea,
+.container-wrap .span_12.light input[type="email"],
+.container-wrap .span_12.light input[type="password"],
+.container-wrap .span_12.light input[type="tel"],
+.container-wrap .span_12.light input[type="url"],
+.container-wrap .span_12.light input[type="search"],
+.container-wrap .span_12.light input[type="date"] {
+ border: 1px solid rgba(255, 255, 255, 0.6) !important;
+ color: #fff;
+}
+
+.container-wrap .span_12.light input[type="text"]:focus,
+.container-wrap .span_12.light textarea:focus,
+.container-wrap .span_12.light input[type="email"]:focus,
+.container-wrap .span_12.light input[type="password"]:focus,
+.container-wrap .span_12.light input[type="tel"]:focus,
+.container-wrap .span_12.light input[type="url"]:focus,
+.container-wrap .span_12.light input[type="search"]:focus,
+.container-wrap .span_12.light input[type="date"]:focus {
+ border: 1px solid rgba(255, 255, 255, 0.8) !important;
+}
+
+.container-wrap .span_12.light input[type="submit"]:hover {
+ background-color: #333 !important;
+ opacity: .8 !important;
+}
+
+/* RTL */
+.rtl .comment-list li.comment > div,
+.rtl .comment-list li.pingback > div {
+ padding-right: 85px !important;
+ padding-left: 25px !important;
+}
+
+.rtl .comment-list li.comment > div img.avatar,
+.rtl .comment-list li.pingback > div img.avatar {
+ right: 0 !important;
+ left: auto !important;
+}
+
+body.rtl .carousel-wrap[data-full-width="false"] .control-wrap {
+ left: 0px;
+ right: auto;
+}
+
+.rtl .carousel-wrap[data-full-width="false"] .control-wrap .carousel-prev {
+ margin-left: 0;
+}
+
+.rtl .carousel-wrap[data-full-width="false"] .control-wrap .carousel-prev,
+.carousel-wrap[data-full-width="false"] .control-wrap .carousel-next {
+ left: 0 !important;
+ right: auto !important;
+}
+
+.rtl .carousel-wrap[data-full-width="false"] .control-wrap .item-count {
+ left: -2px;
+ right: auto;
+}
+
+.rtl.ascend [data-full-width="false"] .carousel-prev.next-hovered,
+.rtl.ascend [data-full-width="false"] .item-count.next-hovered {
+ transform: translateX(0px);
+}
+
+.rtl.ascend [data-full-width="false"] .carousel-next:hover {
+ transform: translateX(18px);
+}
+
+.rtl.ascend [data-full-width="false"] .carousel-prev:hover ~ .carousel-next ,
+.rtl.ascend [data-full-width="false"] .carousel-prev:hover ~ .item-count {
+ transform: translateX(18px);
+}
+
+.rtl.ascend [data-full-width="false"] .carousel-prev:hover i {
+ transform: translateX(0px);
+}
+
+.rtl.ascend [data-full-width="false"] .carousel-prev:after {
+ transform: translateX(30px);
+}
+
+.rtl.ascend [data-full-width="false"] .carousel-prev:hover:after {
+ opacity: 1;
+ transform: translateX(18px);
+}
+
+body a {
+ color: #1080A7;
+}
diff --git a/3rd_party/static/onap-ui/assets/css/combine.css b/3rd_party/static/onap-ui/assets/css/combine.css
new file mode 100644
index 0000000..ce80767
--- /dev/null
+++ b/3rd_party/static/onap-ui/assets/css/combine.css
@@ -0,0 +1,4033 @@
+html {
+ overflow-y: scroll;
+}
+
+hr {
+ height: 1px !important;
+}
+
+h3 {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+a,
+a:visited {
+ color: #bc1518;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+.center {
+ text-align: center;
+}
+
+@media (max-width: 950px) {
+ .defaultSkin table.mceLayout {
+ width: 100% !important;
+ }
+}
+
+/* fix for input-group bootstrap */
+.input-group .input-group-btn .btn {
+ height: 34px;
+}
+
+/* @group Navigation */
+#navigation {
+ display: block;
+ background: url('/themes/openstack/images/header-line.gif') repeat-x 0 bottom;
+ padding-bottom: 1px;
+}
+
+#navigation * {
+ padding: 0;
+ margin: 0;
+}
+
+#navigation ul {
+ display: block;
+ margin: 0 auto;
+}
+
+#navigation li {
+ display: block;
+ float: left;
+ margin-right: 20px;
+}
+
+#navigation li a {
+ display: block;
+ font-weight: normal;
+ text-decoration: none;
+ background-position: 50% 0;
+ padding: 20px 0 5px;
+ color: #353535;
+ font-size: 14px;
+}
+
+#navigation li a.current,
+#navigation li a.section {
+ border-bottom: 3px solid #cf2f19;
+ color: #cf2f19;
+}
+
+/* @group Auto-clearing */
+#navigation:after,
+#navigation ul:after,
+#header:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+#navigation,
+#navigation ul,
+#header:after {
+ display: inline-block;
+}
+
+/* Hides from IE-mac \*/
+* html #navigation,
+* html #navigation ul {
+ height: 1%;
+}
+
+#navigation,
+#navigation ul,
+#header:after {
+ display: block;
+}
+
+/* End hide from IE-mac */
+/* @end */
+#logo a {
+ display: block;
+ margin-top: 8px;
+ text-indent: -1000em;
+ background: url('/themes/openstack/images/open-stack-cloud-computing-logo-2.png') no-repeat left center;
+ height: 54px;
+ width: 177px;
+ margin-left: -10px;
+}
+
+#header {
+ margin-bottom: 0px;
+ margin-top: 20px;
+}
+
+/* @end */
+/* @group Tabs */
+.tabContentHidden {
+ display: none;
+}
+
+.tabTops {
+ border: 1px solid black;
+ border-bottom: none;
+}
+
+#subnav li {
+ list-style-type: none;
+ margin-bottom: 5px;
+ padding: 0;
+}
+
+#subnav ul {
+ padding-left: 0px;
+ margin-right: 0px;
+}
+
+#subnav ul.overviewNav li a {
+ display: block;
+ padding: 10px 20px 10px 42px;
+ text-decoration: none;
+ color: black;
+ background-color: #e9e9e9;
+ background-image: none;
+ text-align: right;
+ margin-right: 40px;
+}
+
+#subnav ul.overviewNav li a:hover {
+ background-color: #d6d6d6;
+ -webkit-transition: background-color 1s ease-out;
+}
+
+ul.subsectionNav li.current a {
+ background-color: #c4e0e9;
+ -webkit-transition: background-color 1s ease-out;
+}
+
+.subsectionNav a[href*="/essex/"] {
+ background-color: #E9E9E9 !important;
+}
+
+.subsectionNav a[href*="/start/"] {
+ background-color: #D5EFD4 !important;
+ margin-top: 30px;
+}
+
+.subsectionNav a[href*="/marketplace/training/"] {
+ margin-top: 30px;
+}
+
+.overviewNav li.active,
+.subsectionNav li.active {
+ background: url('/themes/openstack/images/pointer-arrow.gif') no-repeat right center;
+}
+
+#subnav ul li {
+ text-align: right;
+}
+
+ul.subsectionNav li a {
+ display: block;
+ padding: 10px 20px 10px 42px;
+ text-decoration: none;
+ color: black;
+ background: #e2ecef none no-repeat 5px center;
+ margin-right: 40px;
+}
+
+ul.subsectionNav li a:hover {
+ background-color: #c4e0e9;
+ -webkit-transition: background-color 1s ease-out;
+}
+
+ul.tabs {
+ padding: 0 2px 0 0;
+ white-space: nowrap;
+ list-style-type: none;
+ display: block;
+ zoom: 1;
+ margin-right: 0px;
+ clear: both;
+ border-bottom: 1px solid #d8d8d8;
+ background-color: #ececec;
+}
+
+.tabSet {
+ margin: auto;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ border: 1px solid #d8d8d8;
+ border-top: 1px solid #bcbcbc;
+}
+
+#home .tabSet {
+ margin-top: 45px;
+}
+
+.featureHeader {
+ margin-left: 20px;
+ margin-top: 20px;
+}
+
+.tabs li {
+ cursor: pointer;
+ display: inline;
+ margin-right: -3px;
+ padding: 0px;
+}
+
+.tabs li a {
+ margin: 0px;
+ display: inline-block;
+ color: #41728d;
+ font-size: 13px;
+ font-family: 'PT Sans', serif;
+ padding: 6px 14px;
+ text-shadow: #fff 0px 1px 1px;
+ border-right: 1px solid #d8d8d8;
+ width: 160px;
+ text-align: center;
+}
+
+.tabs li a:hover {
+ background-color: #eee;
+ text-decoration: none;
+}
+
+.tabs li.active a {
+ cursor: default;
+ text-decoration: none;
+ position: relative;
+ color: black;
+ background-color: #f5f5f5;
+ border-bottom: 1px solid #f5f5f5;
+ margin-bottom: -1px;
+ border-top: 1px white solid;
+ font-weight: bold;
+}
+
+.tabs li#showcode a {
+ background: #6b90da;
+ padding-bottom: 6px;
+ font-weight: bold;
+ color: #fff;
+}
+
+/* @end */
+/* @group Buttons */
+a.button {
+ font-family: 'PT Sans', serif;
+ border: 1px solid #ccc;
+ padding: 3px 30px;
+ color: #525252;
+ text-decoration: none;
+ font-size: 14px;
+ line-height: 3em;
+ -webkit-box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
+ -moz-box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
+ box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
+ text-shadow: #fff 0px 1px 1px;
+ background: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#bebebe));
+ background: #ddd;
+}
+
+a.button:hover {
+ color: black;
+ -webkit-transition: color 1s ease-out;
+}
+
+a.button:active {
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ border-color: #ababab;
+ border-top-color: #636363;
+ background: #ababab;
+ background: -webkit-gradient(linear, left top, left bottom, from(#bebebe), to(#dddddd));
+ -webkit-transition: none;
+ padding: 4px 29px 2px 31px !important;
+}
+
+/* @end */
+/* @group Rounded Buttons */
+.roundedButton,
+input.action {
+ font-family: 'PT Sans', serif;
+ border: 1px solid #e2e2e2;
+ padding: 4px 15px;
+ color: black !important;
+ text-decoration: none !important;
+ font-size: 12.5px;
+ line-height: 3em;
+ background: #FFFFFF;
+ /* old browsers */
+ /* firefox */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #FFFFFF), color-stop(50%, #F3F3F3), color-stop(100%, #EBEBEB));
+ /* webkit */
+ -webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2);
+ box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.5);
+ text-shadow: #fff 0px 1px 1px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ border-bottom-color: #a0a0a0;
+ border-right-color: #bababa;
+ border-left-color: #bababa;
+}
+
+.roundedButton:hover,
+input.action:hover {
+ color: black;
+ -webkit-transition: color 1s ease-out;
+ cursor: pointer;
+}
+
+a.roundedButton:active,
+input.action:active {
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ border-color: #ababab;
+ border-top-color: #636363;
+ background: #ababab;
+ background: -webkit-gradient(linear, left top, left bottom, from(#bebebe), to(#dddddd));
+ -webkit-transition: none;
+ padding: 4px 15px;
+}
+
+input.action {
+ line-height: 1.2em !important;
+}
+
+/* @end */
+.subhead {
+ color: #cf2f19;
+ font-size: 16px;
+ border-bottom: 1px dotted;
+ padding-bottom: 5px;
+ border-color: #c5e2ea;
+ margin-bottom: 20px;
+}
+
+/* @group projects page */
+.projectsPage h1,
+.communityPage h1,
+#blog h1 {
+ color: #264d69;
+ font-size: 24px;
+}
+
+.projectsPage h2 {
+ color: #264d69;
+ font-size: 17px;
+}
+
+#subnav {
+ padding-top: 38px;
+}
+
+.note {
+ color: #33730a;
+ background: #e2f4dc url('/themes/openstack/images/side-note-pointer.gif') no-repeat left center;
+}
+
+.note a {
+ color: #33730a;
+ text-decoration: underline;
+}
+
+.note p {
+ margin-left: 8px;
+ padding: 10px;
+ margin-bottom: 0px;
+ border-bottom: 1px solid #b5c8a8;
+}
+
+a#CitrixVideo {
+ display: block;
+ padding-top: 93px;
+ background: url('/themes/openstack/images/citrix-video-thumbnail.jpg') no-repeat;
+ color: #aeaeae;
+ text-decoration: none;
+ margin-top: 3px;
+}
+
+h3.videoHeader {
+ color: #939393;
+ font-size: 14px;
+}
+
+a.downloadLink {
+ text-decoration: none;
+ color: white;
+ font-family: helvetica, arial;
+ font-weight: bold;
+ display: block;
+ width: 250px;
+ text-align: center;
+ position: relative;
+ padding: 3px;
+ margin-bottom: 5px;
+ margin-top: 5px;
+ /* BORDER RADIUS */
+ border-radius: 5px;
+ background-color: #989996;
+ border: 2px solid white;
+}
+
+/* @end */
+#footer {
+ margin-top: 30px;
+}
+
+/* @group compute */
+.projectVitals {
+ border-top: 1px solid #c5e2ea;
+ padding-top: 10px;
+}
+
+.projectVitals h3 {
+ font-size: 16px;
+ color: #264d69;
+}
+
+#status {
+ padding: 12px;
+ color: #1d6006;
+ background-color: #e2f4dc;
+ border: 1px solid #b5c8a8;
+ margin-bottom: 10px;
+ height: 9em;
+}
+
+#status strong {
+ font-size: 120%;
+}
+
+#availability {
+ line-height: 1.4em;
+ padding: 12px;
+ color: #747474;
+ background-color: #f6f8f8;
+ border: 1px solid #d4d5d5;
+ margin-bottom: 10px;
+ height: 9em;
+}
+
+.projectVitals h4 {
+ font-size: 14px;
+ color: #797979;
+ margin-bottom: 4px;
+}
+
+#status p,
+#availability p {
+ margin: 0px;
+}
+
+#parallax {
+ background: #2f3134;
+ position: relative;
+ overflow: hidden;
+ width: 60em;
+ height: 300px;
+ margin: 1.5em 0;
+}
+
+/* @group FAQ */
+.faqs .span-5 {
+ font-size: 14px;
+ color: #707070;
+ font-weight: bold;
+}
+
+.faqs hr {
+ padding: 0px;
+}
+
+.faqs div {
+ margin-bottom: 20px;
+}
+
+/* @end */
+/* @end */
+/* @group community page */
+.communityBox {
+ height: 213px;
+ background: #f8f8f8 url('/themes/openstack/images/community-box-headers.png') no-repeat 0 0;
+}
+
+#userResources {
+ background-position: -200px 0;
+}
+
+#devCenter {
+ background-position: -400px 0;
+}
+
+.communityBox p,
+.communityBox h2 {
+ margin: 15px;
+}
+
+.communityBox,
+.communityBox a,
+.communityBox a.visited {
+ color: #6b6b6b;
+}
+
+.communityBox a {
+ text-decoration: underline;
+}
+
+.communityBox h2 {
+ color: black;
+ font-size: 17px;
+ margin-top: 60px;
+}
+
+.participants h2,
+.communityResources h2 {
+ font-size: 16px;
+ color: #264d69;
+ margin-top: 30px;
+ padding-top: 10px;
+ border-top: 1px dotted #c5e2ea;
+}
+
+#designSummit h2 {
+ text-indent: -1000px;
+ height: 222px;
+ margin-top: -9px;
+ margin-bottom: 10px;
+ background: url('/themes/openstack/images/openstack-design-summit-community.jpg') no-repeat 0 0;
+}
+
+#designSummit {
+ color: #6b6b6b;
+}
+
+#designSummit strong {
+ color: black;
+ font-weight: normal;
+}
+
+/* @end */
+/* @group blog */
+#blog h2 {
+ color: #5189a0;
+ font-size: 15px;
+ margin-bottom: 0px;
+}
+
+#blog h2 a {
+ background-color: #eaeaea;
+ text-align: center;
+ padding: 1px;
+ padding-left: 7px;
+ padding-right: 7px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ color: #757575;
+ font-size: 0.7em;
+ text-decoration: none;
+}
+
+div.byline {
+ border-bottom: 1px dotted #c5e2ea;
+ border-top: 1px dotted #c5e2ea;
+ margin-bottom: 10px;
+ padding: 2px 0;
+}
+
+div.byline p {
+ margin: 0px;
+}
+
+div.byline .postDate {
+ text-align: right;
+}
+
+div.byline p.name a,
+div.byline p.name a:visited {
+ color: #cf2f19;
+ text-decoration: none;
+}
+
+div.socialMedia {
+ background-color: #eef3f5;
+ margin-bottom: 20px;
+}
+
+div.socialMedia div {
+ padding: 10px;
+}
+
+div.socialMedia h4 {
+ margin-bottom: 2px;
+}
+
+div.socialMedia p {
+ margin-bottom: 0px;
+}
+
+div.socialMedia div.twitter {
+ border-bottom: 1px dotted #c5e2ea;
+}
+
+#blog h3 {
+ margin-bottom: 5px;
+ font-size: 12px;
+}
+
+/* @end */
+/* @group Brand */
+div.termsBox {
+ border: 1px solid #c6e2ea;
+ padding: 10px;
+ height: 300px;
+ overflow: scroll;
+ margin-bottom: 10px;
+}
+
+.termsBox h3,
+#openstack-trademark-policy h3 {
+ font-size: 100%;
+ font-weight: bold;
+}
+
+.termsBox a {
+ color: inherit;
+ text-decoration: underline;
+}
+
+#zenbox_tab {
+ top: 34% !important;
+ min-width: 109px !important;
+ line-height: 0px !important;
+}
+
+/* @end */
+/* @group quotes */
+ul#quotes {
+ margin-left: 0px;
+ padding-left: 0px;
+ margin-top: 10px;
+}
+
+ul#quotes li {
+ list-style-type: none;
+}
+
+ul#quotes li p {
+ line-height: 1.4em;
+}
+
+ul#quotes p {
+ font-size: 16px;
+ font-family: 'PT Sans', serif;
+ margin-bottom: 0px;
+ color: black;
+ line-height: 1.2em;
+ padding: 10px;
+ padding-bottom: 0px;
+}
+
+ul#quotes p.name {
+ margin-top: 10px;
+ font-size: 14px;
+ text-transform: uppercase;
+ color: #9b9b9b;
+ text-indent: 0px;
+}
+
+ul#quotes p.name strong {
+ font-weight: normal;
+ color: #494949;
+}
+
+/* @end */
+.tooltip {
+ background: black;
+ background: rgba(0, 0, 0, 0.8);
+ padding: 1px 8px;
+ color: white;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+}
+
+a.Datasheet {
+ padding-top: 110px;
+ display: block;
+ background: url('/themes/openstack/images/openstack-product-pdf.jpg') no-repeat center top;
+ margin-left: 20px;
+ margin-top: 30px;
+}
+
+a.Datasheet:hover {
+ text-decoration: none;
+ color: #bc1518;
+}
+
+a#DemoVideo {
+ display: block;
+ padding-top: 103px;
+ margin-left: 18px;
+ background: url('/themes/openstack/images/demo-video-thumbnail.jpg') no-repeat;
+ text-decoration: none;
+ margin-top: 50px;
+}
+
+/* @group Tables */
+.tabContent table {
+ margin: 20px;
+ width: 670px;
+}
+
+.tabContent table td {
+ border-bottom: 1px solid #d8d8d8;
+ vertical-align: top;
+ padding: 10px 10px 20px 0;
+}
+
+.tabContent table td p {
+ margin: 0px;
+}
+
+.tabContent table tr:last-child td {
+ border-bottom: none;
+}
+
+.tabContent table th {
+ font-family: 'PT Sans', serif;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 18px;
+ letter-spacing: -0.076em;
+ line-height: 1em;
+ color: #264d69;
+ padding-left: 0px;
+}
+
+.tabContent table a:active,
+.tabContent table a:visited,
+.tabContent table a {
+ color: inherit;
+ text-decoration: underline;
+}
+
+p.fnote {
+ margin-left: 20px;
+}
+
+/* @end */
+h2.user-story-quote {
+ line-height: 1.5em;
+ color: grey !important;
+}
+
+.user-story-quote-author {
+ text-transform: uppercase;
+}
+
+ul.user-project-list {
+ margin: 0px;
+ padding: 0px;
+ overflow: hidden;
+ margin-bottom: 20px;
+}
+
+ul.user-project-list li {
+ display: block;
+ margin-right: 5px;
+ background-color: #e2f1f5;
+ border: 1px solid #89c6d6;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ padding: 2px 8px;
+ margin-bottom: 5px;
+ color: #1a4b6b;
+ font-size: 95%;
+ width: 170px;
+ float: left;
+}
+
+#footer ul {
+ margin: 10px 0px 20px;
+ padding: 0px;
+ list-style: none;
+}
+
+#footer a,
+#footer a:visited,
+#footer a:active {
+ color: black;
+}
+
+#footer h3 {
+ color: #de0000;
+ font: 130% 'PT Sans', serif;
+}
+
+#footer textarea {
+ width: 260px;
+ height: 60px;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #EBEBEB), color-stop(5%, #FFFFFF));
+ /* webkit */
+}
+
+#footer form {
+ margin-top: 8px;
+ margin-bottom: 20px;
+}
+
+.user-links li {
+ padding-left: 20px;
+ background: url('/themes/openstack/images/link.png') no-repeat left center;
+ margin-left: -20px;
+ list-style: none;
+}
+
+.user-objectives {
+ background-color: #ecedec;
+ padding: 20px;
+ margin-bottom: 15px;
+}
+
+.user-objectives p {
+ margin-bottom: 0px;
+}
+
+.user-name {
+ padding-top: 10px;
+}
+
+.user-photo img {
+ border: 5px solid white;
+ -webkit-box-shadow: 3px 2px 2px rgba(0, 0, 0, 0.3);
+ box-shadow: 3px 2px 2px rgba(0, 0, 0, 0.3);
+}
+
+.siteMessage {
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ border: 1px solid;
+ margin-bottom: 10px;
+ margin-top: 20px;
+}
+
+.siteMessage p {
+ margin: 8px;
+}
+
+#InfoMessage {
+ color: black;
+ background-color: #DBEAEE;
+ border-color: #B5D8E2;
+ font-size: 120%;
+ padding: 10px;
+}
+
+#SuccessMessage {
+ color: #3E933A;
+ background-color: #E2F7D8;
+ border-color: #9FDE9C;
+}
+
+#ErrorMessage {
+ color: #DA1D1D;
+ background-color: #FFDFDF;
+ border-color: #FFBBBB;
+}
+
+p.message.bad {
+ color: #DA1D1D;
+ background-color: #FFDFDF;
+ border-color: #FFBBBB;
+ padding: 15px;
+ border: 1px solid #DA1D1D;
+}
+
+.topMessage {
+ background: #E4EEF1;
+ border-bottom: 1px solid white;
+ padding: 5px;
+ font-size: 1.3em;
+ color: #255E6E;
+ font-family: 'PT Sans', serif;
+ text-shadow: #fff 0px 1px 1px;
+ box-shadow: 1px 1px 1px 1px #ccc;
+}
+
+.topMessage p {
+ margin-bottom: 0px;
+}
+
+#header {
+ margin-top: 40px;
+}
+
+span.message {
+ font-weight: bold;
+ color: #CE332C;
+}
+
+.gsc-control-cse {
+ padding: 0px !important;
+}
+
+#gcse {
+ width: 100%;
+ height: 60px;
+}
+
+input.gsc-search-button,
+input.gsc-search-button:hover,
+input.gsc-search-button:focus {
+ background: #C43422 !important;
+ border: none !important;
+}
+
+table.gsc-search-box td {
+ padding: 2px !important;
+}
+
+.gsc-input-box {
+ border-color: #D3E9EF !important;
+}
+
+.gs-visibleUrl,
+.gs-visibleUrl-long {
+ color: #A5A5A5 !important;
+}
+
+.roundedButton-margin {
+ margin-left: 5px;
+}
+
+/*!
+ * Start Bootstrap - Landing Page Bootstrap Theme (http://startbootstrap.com)
+ * Code licensed under the Apache License v2.0.
+ * For details, see http://www.apache.org/licenses/LICENSE-2.0.
+ */
+body,
+html {
+ width: 100%;
+ height: 100%;
+}
+
+
+h1 {
+ color: #2A4E68;
+ font-size: 34px;
+ font-weight: 300;
+ margin: 40px 0;
+ text-align: center;
+}
+
+h3 {
+ color: #2A4E68;
+}
+
+h5 {
+ color: #DA422F;
+ margin-bottom: 0;
+}
+
+a,
+a:visited {
+ color: #1080A7;
+}
+
+.lead {
+ font-size: 18px;
+ font-weight: 400;
+}
+
+/*Header Navigation more styles in navigation_menu.css */
+
+.search-container {
+ position: relative;
+ display: none;
+ float: left;
+ width: 84%;
+}
+
+@media (min-width: 768px) and (max-width: 1200px) {
+ .search-container {
+ width: 80%;
+ }
+}
+
+@media (max-width: 767px), only screen and (max-device-width: 1024px) {
+ .search-container {
+ display: none;
+ }
+}
+
+.search-icon {
+ display: none;
+ padding: 17px 20px 16px;
+ float: left;
+ text-transform: uppercase;
+ color: #8a959e;
+ font-size: 12px;
+ font-weight: 400;
+}
+
+.search-icon:hover {
+ cursor: pointer;
+ color: #8a959e;
+}
+
+.search-icon i {
+ margin-right: 5px;
+ color: #8a959e;
+}
+
+@media (max-width: 767px), only screen and (max-device-width: 1024px) {
+ .search-icon {
+ display: none !important;
+ }
+}
+
+@media (max-width: 1040px) {
+ .header-search-text {
+ display: none;
+ }
+}
+
+@media (max-width: 767px), only screen and (max-device-width: 1024px) {
+ .header-search-form {
+ display: none;
+ }
+
+ .custom-search-box {
+ position: relative !important;
+ left: 0px !important;
+ }
+}
+
+.custom-search-box {
+ color: #30739C !important;
+ font-size: 12px !important;
+ text-transform: lowercase !important;
+ font-weight: 400 !important;
+ width: 100% !important;
+}
+
+.custom-search-box-mobile {
+ display: none !important;
+ position: relative;
+ width: 100% !important;
+}
+
+@media (max-width: 767px), only screen and (max-device-width: 1024px) {
+ .custom-search-box-mobile {
+ display: block !important;
+ }
+
+ .custom-search-box {
+ display: none !important;
+ }
+}
+
+.header-search,
+.custom-search-box,
+.custom-search-box-mobile {
+ border: 2px solid #dae5ee !important;
+ border-radius: 4px !important;
+ height: 37px !important;
+ margin: 7px 0 0 0 !important;
+ padding: 0 !important;
+ padding-left: 10px !important;
+ background: #fff !important;
+ width: 100% !important;
+ -webkit-transition: width 4s !important;
+ transition: width 4s !important;
+ -webkit-border-horizontal-spacing: 0 !important;
+ -webkit-border-vertical-spacing: 0 !important;
+}
+
+.header-search contenteditable .custom-search-box-mobile:hover,
+.custom-search-box-mobile:focus,
+.custom-search-box:hover,
+.custom-search-box:focus {
+ box-shadow: none !important;
+}
+
+.header-search::-webkit-input-placeholder,
+.custom-search-box::-webkit-input-placeholder,
+.custom-search-box-mobile::-webkit-input-placeholder {
+ color: #C0CDDB;
+ font-size: 12px;
+ text-transform: lowercase;
+ font-weight: 400;
+}
+
+.header-search:-moz-placeholder,
+.custom-search-box-mobile:-moz-placeholder,
+.custom-search-box:-moz-placeholder {
+ /* Firefox 18- */
+ color: #C0CDDB;
+ font-size: 12px;
+ text-transform: lowercase;
+ font-weight: 400;
+}
+
+.header-search::-moz-placeholder,
+.custom-search-box::-moz-placeholder,
+.custom-search-box-mobile::-moz-placeholder {
+ /* Firefox 19+ */
+ color: #C0CDDB;
+ font-size: 12px;
+ text-transform: lowercase;
+ font-weight: 400;
+}
+
+.header-search:-ms-input-placeholder,
+.custom-search-box:-ms-input-placeholder,
+.custom-search-box-mobile:-ms-input-placeholder {
+ color: #C0CDDB;
+ font-size: 12px;
+ text-transform: lowercase;
+ font-weight: 400;
+}
+
+.header-search:focus,
+.custom-search-box:focus,
+.custom-search-box-mobile:focus {
+ border-radius: 4px;
+ outline: none;
+ border: 2px solid #30739C !important;
+ box-shadow: none;
+}
+
+.close-search {
+ position: absolute;
+ top: 20px;
+ right: 10px;
+ color: #dae5ee;
+ z-index: 1001;
+ font-size: 16px;
+}
+
+.close-search:hover {
+ color: #30739C;
+ cursor: pointer;
+}
+
+@media (max-width: 767px), only screen and (max-device-width: 1024px) {
+ .close-search {
+ display: none !important;
+ }
+}
+
+.show {
+ display: block;
+}
+
+@media (max-width: 767px), only screen and (max-device-width: 1024px) {
+ .show {
+ display: none;
+ }
+}
+
+/*End Header Navigation*/
+/*Hero*/
+.intro-header {
+ padding-top: 0px;
+ padding-bottom: 0;
+ text-align: center;
+ color: #f8f8f8;
+ background: url('/themes/openstack/images/hero-bkgd1.jpg') no-repeat center center;
+ background-size: cover;
+ position: relative;
+}
+
+.intro-header h1 {
+ color: #f8f8f8;
+}
+
+.intro-message {
+ position: relative;
+ padding-top: 15px;
+ padding-bottom: 0;
+}
+
+@media (max-width: 1199px) {
+ .intro-message {
+ padding-bottom: 0px;
+ }
+}
+
+.intro-message > h1 {
+ margin: 0;
+ font-size: 2.3em;
+ font-weight: 400;
+ text-align: center;
+ font-family: "Open Sans", Helvetica, Arial, sans-serif;
+ width: 100%;
+ letter-spacing: -1px;
+}
+
+@media (max-width: 767px) {
+ .intro-message > h1 {
+ font-size: 1.5em;
+ }
+}
+
+.intro-divider {
+ width: 400px;
+ border-top: 1px solid #f8f8f8;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+.intro-message > h3 {
+ text-shadow: none;
+ text-align: left;
+ font-weight: 300;
+ font-size: 24px;
+ margin-top: 10px;
+}
+
+.hero-credit {
+ position: absolute;
+ bottom: 30px;
+ right: 30px;
+ color: #fff;
+ opacity: 0.5;
+}
+
+.hero-credit:hover {
+ opacity: 1;
+}
+
+.hero-credit a,
+.hero-credit a:hover {
+ color: #fff;
+}
+
+/*Hero Promo, add .featured to .intro-header*/
+.intro-header.featured {
+ text-align: center;
+ background: no-repeat scroll center center / cover rgba(0, 0, 0, 0);
+ min-height: 300px;
+ background-size: cover;
+ position: relative;
+ padding: 60px 0;
+}
+
+.intro-header.featured .intro-message {
+ padding-bottom: 5px;
+}
+
+a.promo-btn {
+ white-space: nowrap;
+ color: white;
+ font-size: 16px;
+ font-weight: 400;
+ background: #DA422F;
+ border-radius: 4px;
+ padding: 7px 60px;
+ margin: 0 auto;
+ text-align: center;
+ min-width: 250px;
+ display: inline-block;
+ line-height: 2;
+}
+
+a.promo-btn:hover,
+a.promo-btn:focus {
+ text-decoration: none;
+ background: #A51B1B;
+}
+
+.promo-btn-wrapper {
+ margin-bottom: 10px;
+}
+
+a.promo-btn i.fa-chevron-right {
+ background: transparent;
+ border-radius: 50%;
+ padding: 0;
+ font-size: 13px;
+ margin-left: 20px;
+ line-height: 1em;
+}
+
+p.promo-dates {
+ display: inline-block;
+ margin-top: 10px;
+ font-weight: 400;
+ margin-bottom: 30px;
+ letter-spacing: -.4px;
+}
+
+/*End Hero*/
+/*Overview Section*/
+.overview-section {
+ padding: 80px 0;
+}
+
+@media (max-width: 767px) {
+ .overview-section {
+ padding-top: 40px;
+ }
+}
+
+.overview-section h2 {
+ color: #2A4E68;
+ font-size: 34px;
+ font-weight: 300;
+ margin-bottom: 25px;
+}
+
+.overview-section p {
+ color: #888;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 1.4;
+}
+
+.overview-section a {
+ color: #30739C;
+ text-decoration: underline;
+}
+
+.btn-wrapper {
+ float: left;
+ width: 100%;
+ text-align: center;
+}
+
+@media (max-width: 980px) {
+ .overview-left {
+ margin-bottom: 50px;
+ }
+}
+
+a.overview-btn {
+ float: left;
+ background: #30739C;
+ color: #fff;
+ text-transform: uppercase;
+ border-radius: 4px;
+ padding: 15px 25px;
+ text-decoration: none;
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+a.overview-btn:hover {
+ background: #2A4E68;
+}
+
+a.overview-btn.left-btn {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ border-right: 1px solid #3387C9;
+ margin-right: 0;
+}
+
+a.overview-btn.left-btn:hover {
+ border-right-color: #1B486B;
+}
+
+a.overview-btn.right-btn {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ border-left: 1px solid #1B486B;
+ margin-left: 0;
+}
+
+.release-text {
+ display: block;
+ text-align: left;
+ color: #636568;
+ font-size: 13px;
+}
+
+.overview-section .img-responsive {
+ max-width: 550px;
+}
+
+.overview-right {
+ text-align: center;
+}
+
+.control-cloud-graphic {
+ max-width: 600px;
+ margin: 0 auto 15px;
+}
+
+a.demo-link {
+ font-size: 13px;
+ font-weight: 400;
+ text-decoration: none;
+ color: #34789A;
+ background: #F0F9FE;
+ padding: 10px 25px;
+ border-radius: 4px;
+}
+
+a.demo-link:hover {
+ color: #34789A;
+ background: #CDD9E2;
+}
+
+a.demo-link i {
+ margin-left: 5px;
+ font-size: 14px;
+}
+
+/*When Featured, Add Class .featured to .overview-section*/
+.overview-section.featured .overview-right {
+ background: #E8EEF5;
+ border-radius: 4px;
+ text-align: center;
+ padding: 20px;
+ margin-top: 50px;
+}
+
+@media (max-width: 1200px) {
+ .overview-section.featured .overview-right {
+ margin-top: 50px;
+ }
+}
+
+.overview-section.featured .overview-right h3 {
+ color: #30739C;
+ text-align: center;
+}
+
+.overview-section.featured .overview-right p {
+ color: #333;
+ font-size: 13px;
+}
+
+.overview-section.featured .overview-right img.promo-graphic {
+ max-width: 350px;
+ margin: 20px auto;
+}
+
+/*End Overview Section*/
+/*Customers Section*/
+.customers-row {
+ padding: 80px 0;
+ text-align: center;
+ background: #edf2f7;
+}
+
+.customers-row h2 {
+ text-transform: capitalize;
+ margin-bottom: 0;
+ text-align: center;
+}
+
+.customer-logos-wrapper {
+ margin: 15px 0;
+ text-align: center;
+}
+
+.customer-logos {
+ position: relative;
+ padding: 20px 0;
+ width: 15%;
+ display: inline-block;
+}
+
+@media (max-width: 767px) {
+ .customer-logos {
+ position: relative;
+ padding: 20px 0;
+ width: 80%;
+ margin: 0 auto;
+ }
+}
+
+.logo-hover {
+ background: #dee2e8;
+ border-radius: 3px;
+}
+
+.logo-hover:after {
+ content: ' ';
+ height: 0;
+ position: absolute;
+ width: 0;
+ border: 10px solid transparent;
+ border-top-color: #dee2e8;
+ top: 100%;
+ left: 50%;
+ margin-left: -10px;
+}
+
+.customers-description {
+ margin: 40px 0;
+ color: #2A4E68;
+ text-align: center;
+}
+
+.customers-description p {
+ padding: 20px 50px;
+ border-top: 1px solid #dee2e8;
+ border-bottom: 1px solid #dee2e8;
+ display: inline;
+}
+
+@media (max-width: 767px) {
+ .customers-description p {
+ display: block;
+ }
+}
+
+.customers-action {
+ margin-top: 40px;
+ text-align: center;
+}
+
+a.customer-btn {
+ background: #2A4E68;
+ color: #fff;
+ text-transform: uppercase;
+ border-radius: 4px;
+ padding: 15px 25px;
+ text-decoration: none;
+ border-style: none;
+}
+
+a.customer-btn:hover {
+ background: #173D5B;
+ color: #fff;
+}
+
+/*When Featured, Add Class .featured to .customers-row*/
+.customers-row.featured .customer-logos-wrapper {
+ width: 50%;
+ max-width: 1000px;
+ margin: 40px 25% 0;
+ border-top: 1px solid #DDE3E8;
+ padding-top: 20px;
+}
+
+.customers-row.featured .customer-logos-wrapper hr {
+ color: #333;
+}
+
+.customers-row.featured .customer-logos img {
+ width: 90%;
+ max-width: 100px;
+}
+
+.customers-row.featured .customer-logos:hover {
+ background: none;
+}
+
+.customers-row.featured .customer-logos:hover:after {
+ display: none;
+}
+
+.customers-row.featured .customers-action {
+ margin-top: 0;
+}
+
+.customers-row.featured button.customer-btn {
+ background: none;
+ border-radius: 0;
+ padding: 0;
+ text-decoration: underline;
+ color: #2A4E68;
+ text-transform: capitalize;
+}
+
+.featured-description {
+ width: 85%;
+ margin: 40px auto;
+}
+
+@media (max-width: 767px) {
+ iframe {
+ width: 90%;
+ }
+}
+
+/*End Customers Section*/
+/*Community Section*/
+.community-section {
+ padding: 75px 0;
+ background: url('/themes/openstack/images/community-bkgd-map.jpg') no-repeat 40% center;
+ background-size: cover;
+ min-height: 350px;
+}
+
+.community-section.featured {
+ background: url('/themes/openstack/images/community-bkgd2.jpg') no-repeat center center;
+ background-size: cover;
+}
+
+.community-graphic {
+ max-width: 650px;
+ margin: 0 auto;
+}
+
+@media (max-width: 994px) {
+ .community-graphic {
+ margin-bottom: 20px;
+ }
+}
+
+.community-section h2 {
+ color: #fff;
+}
+
+.community-section p {
+ color: #fff;
+}
+
+@media (max-width: 994px) {
+ .community-section h2 {
+ text-align: center;
+ }
+ .community-section p {
+ text-align: center;
+ }
+}
+
+a.community-btn {
+ font-size: 16px;
+ font-weight: 400;
+ background: transparent;
+ border: 1px solid #fff;
+ border-radius: 3px;
+ margin-top: 20px;
+ padding: 8px 30px 8px 40px;
+ color: white;
+ display: block;
+ max-width: 220px;
+
+}
+@media (max-width: 994px) {
+ a.community-btn {
+ display: block;
+ max-width: 220px;
+ margin: 20px auto 0;
+ }
+}
+
+a.community-btn i {
+ margin-left: 10px;
+}
+
+a.community-btn:hover {
+ text-decoration: none;
+ background: rgba(255, 255, 255, 0.2);
+}
+
+/*When Featured, add .featured to .community-section*/
+.community-section.featured h3 {
+ color: #fff;
+}
+
+@media (max-width: 1200px) {
+ .community-section.featured h3 {
+ text-align: center;
+ }
+}
+
+.designate-logo {
+ max-width: 302px;
+ margin: 50px 0 20px;
+}
+
+@media (max-width: 1200px) {
+ .designate-logo {
+ margin: 50px auto 20px;
+ }
+}
+
+.default-community {
+ background-color: rgba(5, 54, 86, 0.7);
+ border-radius: 4px;
+ padding: 30px;
+ text-align: center;
+}
+
+@media (max-width: 1200px) {
+ .default-community {
+ margin-top: 50px;
+ }
+}
+
+.default-community h2 {
+ margin-top: 0;
+}
+
+.community-graphic.small {
+ max-width: 450px;
+ margin: 20px auto;
+}
+
+a.featured-link {
+ display: block;
+ background: #DA422F;
+ padding: 10px 20px;
+ border-radius: 2px;
+ width: 80%;
+ text-decoration: none;
+ margin: 10px auto 0;
+ color: #fff;
+ font-weight: 600;
+}
+
+a.featured-link:hover {
+ text-decoration: none;
+ background: #831917;
+}
+
+/*End Community Section*/
+/*News and Events Section*/
+.news-section {
+ padding: 70px 0;
+}
+
+.news-section h2 a {
+ font-size: 14px;
+ color: #30739C;
+ font-weight: 400;
+ margin-left: 30px;
+}
+
+.news-section h2 a:hover {
+ color: #28709a;
+ text-decoration: none;
+}
+
+.event-ad,
+.news-ad {
+ width: 100%;
+ max-width: 560px;
+ margin-bottom: 20px;
+}
+
+.event-ad-lrg {
+ width: 100%;
+ height: 113px;
+ max-height: 113px;
+ max-width: 1140px;
+ margin-bottom: 20px;
+ image-rendering: optimizeQuality;
+}
+
+@media (max-width: 767px) {
+ .event-ad-lrg {
+ max-width: 767px;
+ }
+}
+
+.news-section .news-wrapper ul {
+ margin: 30px 0;
+ -moz-padding-start: 0;
+ -webkit-padding-start: 0;
+}
+
+.news-section .news-wrapper ul li {
+ list-style: none;
+}
+
+.single-event {
+ float: left;
+ width: 100%;
+ padding: 15px 10px;
+ border-bottom: 1px solid #ebeff4;
+}
+
+.single-event:hover {
+ background: #edf2f7;
+}
+
+.single-event.last {
+ border-bottom: none;
+}
+
+.left-event {
+ float: left;
+ width: 20%;
+}
+
+@media (min-width: 768px) and (max-width: 981px) {
+ .left-event {
+ width: 25%;
+ }
+}
+
+@media (max-width: 767px) {
+ .left-event {
+ width: 15%;
+ }
+}
+
+.event-details {
+ float: left;
+ margin-left: 3%;
+ width: 62%;
+}
+
+@media (min-width: 768px) and (max-width: 981px) {
+ .event-details {
+ width: 72%;
+ }
+}
+
+.right-event {
+ float: right;
+ width: 13%;
+}
+
+@media (min-width: 768px) and (max-width: 981px) {
+ .right-event {
+ display: none;
+ }
+}
+
+.date,
+.news-type,
+.planet-type {
+ background: #fff;
+ border: 2px solid #DA422F;
+ border-radius: 4px;
+ padding: 5px 0;
+ color: #DA422F;
+ font-size: 10px;
+ width: 100%;
+ text-align: center;
+ float: left;
+ margin-top: 5px;
+}
+
+.event-name,
+.news-title {
+ display: block;
+ font-size: 14px;
+ font-weight: 600;
+ color: #333;
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.location,
+.news-date {
+ display: block;
+ float: left;
+ font-style: italic;
+ color: #333;
+ font-weight: 300;
+}
+
+.right-arrow {
+ float: right;
+ background: #d9dfe5;
+ border-radius: 100px;
+ width: 30px;
+ height: 30px;
+ padding: 6px 10px;
+ color: #fff;
+ margin-right: 10px;
+ margin-top: 5px;
+ display: none;
+}
+
+.single-event:hover .right-arrow {
+ display: block;
+}
+
+.news-type {
+ border: 2px solid #2A4E68;
+ color: #2A4E68;
+}
+
+.planet-type {
+ border: 2px solid #68C8C3;
+ color: #68C8C3;
+}
+
+.see-more-bottom {
+ width: 100%;
+ float: left;
+ text-align: center;
+ margin-top: 40px;
+}
+
+.see-more-bottom a {
+ color: #2A4E68;
+ text-decoration: none;
+ padding: 5px 15px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: 400;
+ border: 2px solid #2A4E68;
+}
+
+.see-more-bottom a i {
+ margin-left: 10px;
+ font-size: 11px;
+}
+
+/*End News and Events Section*/
+/*Photo Row*/
+.photo-row-wrapper {
+ width: 100%;
+ color: #eee;
+ max-height: 240px;
+ margin-bottom: 10px;
+ overflow: hidden;
+ margin-top: 60px;
+}
+
+@media (max-width: 765px) {
+ .photo-row-wrapper {
+ display: none;
+ }
+}
+
+.photo-container {
+ width: 4000px;
+ max-height: 240px;
+ margin-left: -30px;
+}
+
+.photo-container img {
+ margin: 0 10px 0 0;
+ max-width: 22%;
+ max-height: 240px;
+}
+
+/*End Photo Row*/
+/*Footer*/
+
+.footer-links h3 {
+ color: #fff;
+ font-size: 14px;
+ font-weight: 400;
+}
+
+.footer-links ul {
+ margin-left: 0;
+ -moz-padding-start: 0;
+ -webkit-padding-start: 0;
+}
+
+.footer-links ul li a {
+ color: #aaa;
+ font-size: 12px;
+ font-weight: 400;
+ list-style: none;
+ margin-left: 0;
+}
+
+.social-icons {
+ width: 40px;
+ min-height: 40px;
+ display: inline-block;
+ margin-right: 10px;
+}
+
+.footer-twitter {
+ background: url('/themes/openstack/images/footer-twitter.png') no-repeat;
+}
+
+.footer-twitter:hover {
+ background: url('/themes/openstack/images/footer-twitter-hover.png') no-repeat;
+}
+
+.footer-facebook {
+ background: url('/themes/openstack/images/footer-facebook.png') no-repeat;
+}
+
+.footer-facebook:hover {
+ background: url('/themes/openstack/images/footer-facebook-hover.png') no-repeat;
+}
+
+.footer-linkedin {
+ background: url('/themes/openstack/images/footer-linkedin.png') no-repeat;
+}
+
+.footer-linkedin:hover {
+ background: url('/themes/openstack/images/footer-linkedin-hover.png') no-repeat;
+}
+
+.footer-youtube {
+ background: url('/themes/openstack/images/footer-youtube.png') no-repeat;
+}
+
+.footer-youtube:hover {
+ background: url('/themes/openstack/images/footer-youtube-hover.png') no-repeat;
+}
+
+.newsletter-form {
+ margin: 10px 0 30px;
+ width: 100%;
+}
+
+.newsletter-form label {
+ color: #aaa;
+ font-size: 12px;
+ font-weight: 300;
+ display: block;
+}
+
+.newsletter-input {
+ display: inline-block;
+ background: transparent;
+ border: 2px solid #888;
+ border-radius: 4px;
+ color: #888;
+ font-size: 12px;
+ font-weight: 400;
+ padding: 10px 15px;
+ width: 70%;
+}
+
+@media (max-width: 767px) {
+ .newsletter-input {
+ width: 70%;
+ }
+}
+
+.newsletter-input::-webkit-input-placeholder {
+ color: #888;
+ font-size: 12px;
+ font-weight: 400;
+ text-transform: uppercase;
+}
+
+.newsletter-input:-moz-placeholder {
+ /* Firefox 18- */
+ color: #888;
+ font-size: 12px;
+ font-weight: 400;
+ text-transform: uppercase;
+}
+
+.newsletter-input::-moz-placeholder {
+ /* Firefox 19+ */
+ color: #888;
+ font-size: 12px;
+ font-weight: 400;
+ text-transform: uppercase;
+}
+
+.newsletter-input:-ms-input-placeholder {
+ color: #888;
+ font-size: 12px;
+ font-weight: 400;
+ text-transform: uppercase;
+}
+
+.newsletter-input:focus {
+ outline: none;
+ border: 2px solid #666;
+}
+
+.newsletter-btn {
+ margin-left: 1%;
+ display: inline-block;
+ background: transparent;
+ border: 2px solid #30739C;
+ border-radius: 4px;
+ color: #30739C;
+ font-size: 12px;
+ font-weight: 400;
+ padding: 10px 15px;
+ text-transform: uppercase;
+ width: 27%;
+}
+
+@media (max-width: 767px) {
+ .newsletter-btn {
+ width: 18%;
+ margin-left: 2%;
+ padding: 10px;
+ }
+}
+
+.newsletter-btn:hover {
+ border: 2px solid #888;
+ color: #999;
+}
+
+.fine-print {
+ margin-top: 20px;
+ color: #aaa;
+ font-size: 12px;
+}
+
+.fine-print a {
+ color: #aaa;
+ text-decoration: underline;
+}
+
+.fine-print a:hover {
+ color: #fff;
+}
+
+.footer-bottom {
+ background: #222;
+ padding: 15px 0;
+ text-align: center;
+ width: 100%;
+}
+
+.feedback-input {
+ display: inline-block;
+ background: #222;
+ border: 2px solid #444;
+ border-radius: 4px;
+ color: #777;
+ font-size: 12px;
+ font-weight: 400;
+ padding: 10px 20px;
+ width: 310px;
+}
+
+@media (max-width: 767px) {
+ .feedback-input {
+ width: 70%;
+ }
+}
+
+.feedback-input::-webkit-input-placeholder {
+ color: #555;
+ font-size: 12px;
+ font-weight: 400;
+}
+
+.feedback-input:-moz-placeholder {
+ /* Firefox 18- */
+ color: #555;
+ font-size: 12px;
+ font-weight: 400;
+}
+
+.feedback-input::-moz-placeholder {
+ /* Firefox 19+ */
+ color: #555;
+ font-size: 12px;
+ font-weight: 400;
+}
+
+.feedback-input:-ms-input-placeholder {
+ color: #555;
+ font-size: 12px;
+ font-weight: 400;
+}
+
+.feedback-input:focus {
+ outline: none;
+ border: 2px solid #666;
+}
+
+.feedback-btn {
+ margin-left: 11px;
+ display: inline-block;
+ background: #222;
+ border: 2px solid #666;
+ border-radius: 4px;
+ color: #777;
+ font-size: 12px;
+ font-weight: 400;
+ padding: 10px 30px;
+}
+
+@media (max-width: 767px) {
+ .feedback-btn {
+ width: 18%;
+ margin-left: 2%;
+ padding: 10px;
+ }
+}
+
+.feedback-btn:hover {
+ border: 2px solid #888;
+ color: #999;
+}
+
+/*End Footer*/
+/* Line below navigation */
+.navbar-default {
+ border-bottom: 1px solid #ddd;
+}
+
+#home .navbar-default {
+ border-bottom: none;
+}
+
+#home .top-site-banner {
+ display: none;
+}
+
+/*End General Inner Page Styles*/
+/*Events Page*/
+.eventsBanner {
+ height: 150px;
+ padding: 20px;
+}
+
+.eventsPhotoCaption {
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 4px;
+ padding: 10px;
+ color: white;
+}
+
+.news-section.full {
+ padding: 20px 0;
+}
+
+.eventTitleArea {
+ text-align: center;
+ margin: 40px 0;
+}
+
+.eventTitleArea h1 {
+ color: #333;
+ font-weight: 300;
+}
+
+.postEvent {
+ float: left;
+ width: 100%;
+ padding: 30px 5%;
+ background: #F4F5F8;
+ margin: 20px 0 10px;
+ text-align: center;
+}
+
+.postEvent p {
+ margin-bottom: 20px;
+}
+
+.postEvent a {
+ background: #2A4E68;
+ padding: 10px 25px;
+ border-radius: 4px;
+ color: #fff;
+}
+
+.eventBlock {
+ float: left;
+ width: 100%;
+}
+
+.eventBlock.summit .date {
+ border-color: #2A4E68;
+ color: #2A4E68;
+}
+
+.eventBlock.past h2 {
+ margin-top: 50px;
+}
+
+.eventBlock.past .date {
+ border-color: #68C8C3;
+ color: #68C8C3;
+}
+
+/*End Events Page*/
+/*Community Page*/
+.communityBoxes {
+ margin: 30px 0 10px;
+ font-size: 13px;
+}
+
+.communityBoxes h2 {
+ font-size: 20px;
+ font-weight: 400;
+ margin-bottom: 15px;
+}
+
+.communityBoxes h2 a {
+ color: #DA422F;
+}
+
+.developersRow {
+ border-top: 1px solid #eee;
+ border-bottom: 1px solid #eee;
+ padding: 20px 30px;
+ margin-bottom: 30px;
+}
+
+.devLabel {
+ float: left;
+ margin-right: 30px;
+ font-weight: 700;
+}
+
+ul#developerActivity {
+ float: left;
+ padding-left: 0;
+ margin: 0;
+}
+
+ul#developerActivity li {
+ list-style: none;
+ display: inline-block;
+}
+
+ul#developerActivity li a {
+ font-weight: 700;
+ color: #222;
+}
+
+ul#developerActivity li span {
+ background: #E8EEF5;
+ padding: 5px 10px;
+ border-radius: 4px;
+ margin-right: 5px;
+ color: #2A4E68;
+ font-size: 11px;
+ font-weight: 400;
+}
+
+/*End Community Page*/
+/*Sofware Page*/
+.software {
+ margin-top: 30px;
+}
+
+.software-top {
+ text-align: center;
+}
+
+.software-top h1 {
+ margin: 5px 0 10px;
+}
+
+p.icon {
+ text-align: center;
+ margin: 40px 0 0;
+}
+
+p.software-description {
+ text-transform: uppercase;
+ font-size: 13px;
+ color: #777;
+}
+
+.openstack-diagram {
+ margin-bottom: 3em;
+}
+
+div.screenshots {
+ padding: 30px 0 20px;
+ margin: 30px 0;
+ border-top: 1px solid #edf2f7;
+ border-bottom: 1px solid #edf2f7;
+ width: 100%;
+}
+
+.screenshots ul {
+ padding: 0;
+ margin: 0;
+ text-align: center;
+}
+
+.screenshots ul li {
+ list-style: none;
+ display: inline-block;
+}
+
+@media (max-width: 767px) {
+ .screenshots ul li {
+ margin-bottom: 30px;
+ }
+}
+
+.screenshots ul li a img {
+ width: 100%;
+ border: 4px solid #edf2f7;
+}
+
+.screenshots ul li a img:hover {
+ border-color: #30739C;
+}
+
+.screenshots ul li p {
+ margin: 10px 0 0;
+ font-size: 12px;
+ color: #30739C;
+ text-align: center;
+}
+
+.newSubNav li#start a.current,
+.newSubNav li#start a.current:hover {
+ color: #488613;
+ background-color: #DDFFE2;
+}
+
+ul.slides h3 {
+ text-align: center;
+ margin: 0 0 30px;
+}
+
+.tabSet {
+ background: #edf2f7;
+ border: 1px solid #30739C;
+ margin: 30px 0;
+ padding-top: 20px;
+ padding-bottom: 20px;
+}
+
+.tabContent table {
+ width: 100%;
+ margin: 0;
+ table-layout: fixed;
+ word-wrap: break-word;
+}
+
+.get-started-wrapper h3 {
+ text-align: center;
+ margin: 40px 0;
+}
+
+a.start-btn {
+ background: #30739C;
+ color: #fff;
+ text-transform: capitalize;
+ font-size: 12px;
+ border-radius: 2px;
+ padding: 10px 15px;
+ text-decoration: none;
+ border-style: none;
+ display: inline-block;
+}
+
+img.deploy-powered {
+ margin-top: 40px;
+}
+
+@media (max-width: 767px) {
+ img.deploy-powered {
+ margin: 10px 0 30px;
+ }
+}
+
+img.deploy-compatible {
+ margin-top: 30px;
+}
+
+@media (max-width: 767px) {
+ img.deploy-compatible {
+ margin: 10px 0 30px;
+ }
+}
+
+img.icehouse-video {
+ max-width: 100%;
+}
+
+h1.release {
+ text-align: left;
+}
+
+.documentation {
+ margin-top: 50px;
+ text-align: center;
+ background: #edf2f7;
+ border-radius: 4px;
+ padding: 30px 0;
+}
+
+.documentation a {
+ padding: 7px 20px;
+ background: #30739C;
+ color: #edf2f7;
+ margin: 0 8px;
+ border-radius: 4px;
+ display: inline-block;
+}
+
+.documentation a:hover {
+ text-decoration: none;
+ background: #2A4E68;
+}
+
+@media (max-width: 767px) {
+ .documentation a {
+ padding: 10px;
+ margin: 5px;
+ font-size: 14px;
+ display: block;
+ }
+}
+
+/*End Sofware Page*/
+/*Marketplace Page*/
+h2.marketplace-header {
+ border-left: 3px solid #DA422F;
+ line-height: 1.2em;
+ margin: 30px 20px;
+ padding-left: 15px;
+ font-size: 1.5em;
+}
+
+.build-use-box {
+ background: #edf2f7;
+ border-radius: 4px;
+ margin-bottom: 30px;
+ min-height: 25em;
+ padding: 20px;
+}
+
+.build-use-box h3 {
+ margin-bottom: 20px;
+}
+
+.build-use-box ul {
+ padding-left: 20px;
+}
+
+.marketplace-description {
+ margin-top: 50px;
+}
+
+.video {
+ width: 100%;
+}
+
+.video iframe {
+ width: 100%;
+ height: 225px;
+}
+
+.program-logos {
+ border-top: 1px solid #eee;
+ margin-top: 60px;
+ padding-top: 60px;
+}
+
+.ecosystem-wrapper {
+ padding-left: 30px;
+ border-left: 1px solid #eee;
+}
+
+.marketplace-top-wrapper {
+ padding: 20px 0 20px;
+}
+
+.marketplace-brand {
+ margin-top: 20px;
+ padding-left: 30px;
+}
+
+h2.marketplace {
+ margin-bottom: -7px;
+ margin-top: 0;
+ font-size: 18px;
+ font-family: 'PT Sans', serif;
+ font-style: normal;
+ letter-spacing: -0.076em;
+ line-height: 1em;
+}
+
+h2.marketplace a,
+h2.marketplace a:hover {
+ color: #5B83A0;
+ text-decoration: none;
+}
+
+h1.marketplace {
+ font-size: 24pt;
+ margin-top: 5px;
+ text-align: left;
+}
+
+h1.marketplace:hover {
+ text-decoration: none;
+}
+
+h1.marketplace a,
+h1.marketplace a.visited {
+ color: #264D69;
+ font-family: 'PT Sans', serif;
+ font-style: normal;
+ letter-spacing: -0.076em;
+ line-height: 1em;
+}
+
+.grey-bar {
+ background-color: #edf2f7;
+ margin-bottom: 40px;
+ margin-top: 0;
+ padding-bottom: 10px;
+ padding-top: 10px;
+}
+
+ul.marketplace-nav {
+ padding: 0;
+ margin: 0;
+}
+
+ul.marketplace-nav li {
+ border-right: 1px solid #e8e8e8;
+ display: inline-block;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ text-transform: uppercase;
+ width: 130px;
+ height: 96px;
+ vertical-align: top;
+ text-align: center;
+}
+
+ul.marketplace-nav li:last-child {
+ border-right: none;
+}
+
+@media (min-width: 680px) and (max-width: 1000px) {
+ ul.marketplace-nav li {
+ width: 100px;
+ height: 110px;
+ }
+
+ ul.marketplace-nav li a {
+ font-size: 10px;
+ }
+}
+
+@media (max-width: 680px) {
+ ul.marketplace-nav li {
+ display: table-cell;
+ border-right: none;
+ text-align: left;
+ border-bottom: 1px solid #e8e8e8;
+ width: 100%;
+ height: auto;
+ padding: 5px 10px;
+ vertical-align: middle;
+ }
+}
+
+ul.marketplace-nav li a {
+ color: #848575;
+ text-decoration: none;
+ font-size: 12px;
+ vertical-align: middle;
+ width: 115px;
+ text-align: center;
+}
+
+@media (max-width: 680px) {
+ ul.marketplace-nav li a {
+ padding: 0;
+ vertical-align: middle;
+ float: left;
+ width: 100%;
+ text-align: left;
+ }
+}
+
+ul.marketplace-nav a:hover {
+ color: #DA422F;
+}
+
+ul.marketplace-nav a span {
+ background: url("/marketplace/code/ui/frontend/images/marketplace-icons.png") no-repeat scroll 0 0 rgba(0, 0, 0, 0);
+ display: block;
+ height: 50px;
+ margin: auto;
+ width: 40px;
+ text-align: center;
+}
+
+@media (max-width: 680px) {
+ ul.marketplace-nav a span {
+ display: table-cell;
+ vertical-align: middle;
+ }
+}
+
+ul.marketplace-nav #training a span {
+ background-position: 0 0;
+}
+
+ul.marketplace-nav #distros a span {
+ background-position: -50px 0;
+}
+
+ul.marketplace-nav #public-clouds a span {
+ background-position: -150px 0;
+}
+
+ul.marketplace-nav #private-clouds a span {
+ background-position: -250px 0;
+}
+
+ul.marketplace-nav #consulting a span {
+ background-position: -100px 0;
+}
+
+ul.marketplace-nav #drivers a span {
+ background-position: -200px 0;
+}
+
+ul.marketplace-nav .current a {
+ color: #DA422F;
+}
+
+/*End Marketplace Page*/
+/*Marketplace Listing Page*/
+.product-box {
+ border: 1px solid #E8E8E8;
+ margin-bottom: 30px;
+ padding: 30px 20px;
+ border-radius: 4px;
+ min-height: 200px;
+ border-left: 3px solid #DA422F;
+}
+
+.logo-area {
+ padding-top: 20%;
+ padding-left: 10px;
+}
+
+@media (max-width: 767px) {
+ .logo-area {
+ padding-top: 0;
+ padding-left: 0;
+ }
+}
+
+.company-details-area h4 {
+ text-transform: uppercase;
+ font-weight: 300;
+}
+
+.tested-listing {
+ border-top: 1px solid #E8E8E8;
+ border-bottom: 1px solid #E8E8E8;
+ padding: 10px 5px;
+ margin: 20px 0 15px;
+}
+
+.tested-listing i.fa-check-square {
+ color: #30c530;
+ font-size: 1.3em;
+ display: inline-block;
+ line-height: 21px;
+}
+
+.tested-listing .tested-listing-title {
+ color: #30c530;
+ text-transform: uppercase;
+ font-weight: 600;
+ font-size: 1em;
+ display: inline-block;
+ margin-left: 2px;
+ vertical-align: top;
+}
+
+.tested-listing .tested-listing-description {
+ display: inline-block;
+ font-style: italic;
+ font-size: .85em;
+ margin-left: 5px;
+}
+
+.details-button {
+ background: url("/marketplace/code/ui/frontend/images/register-arrow.png") no-repeat scroll 85% center #000000;
+ color: #FFFFFF !important;
+ display: inline-block;
+ margin-right: -10px;
+ padding: 10px 2%;
+ text-align: left;
+ text-transform: uppercase;
+ width: 100px;
+ font-size: 12px;
+ border-radius: 4px;
+}
+
+.filter-label {
+ background: url("/themes/openstack/images/marketplace/search-icon.png") no-repeat scroll left 7px rgba(0, 0, 0, 0);
+ color: #30739C;
+ float: left;
+ margin-bottom: 0;
+ margin-top: 5px;
+ padding-left: 25px;
+ padding-right: 20px;
+ padding-top: 8px;
+ text-transform: uppercase;
+ font-size: 12px;
+}
+
+input#name-term {
+ height: 40px;
+ width: 250px;
+ font-size: 12px;
+ padding: 5px 10px;
+ border: 1px solid #e8e8e8;
+ color: #30739C;
+ border-radius: 4px;
+}
+
+input#name-term::-webkit-input-placeholder {
+ color: #30739C;
+ font-size: 12px;
+}
+
+select#service-term {
+ height: 40px;
+ width: 200px;
+ padding: 5px 10px;
+ position: relative;
+ border-radius: 4px;
+ color: #30739C;
+ background: #fff;
+ border: 1px solid #e8e8e8;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ display: inline-block;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 400;
+}
+
+label#service-label {
+ position: relative;
+}
+
+label#service-label:after {
+ content: '>';
+ color: #30739C;
+ -webkit-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ transform: rotate(90deg);
+ right: 14px;
+ top: 6px;
+ padding: 0 0 6px;
+ border-bottom: 1px solid #e8e8e8;
+ position: absolute;
+ pointer-events: none;
+ font-size: 14px;
+ font-weight: 400;
+}
+
+label#service-label:before {
+ content: '';
+ right: 6px;
+ top: 0px;
+ width: 20px;
+ height: 20px;
+ background: #f8f8f8;
+ position: absolute;
+ pointer-events: none;
+ display: block;
+}
+
+@media (max-width: 767px) {
+ .filter-label {
+ width: 100%;
+ margin-bottom: 10px;
+ }
+
+ input#name-term {
+ display: block;
+ width: 100%;
+ margin-bottom: 10px;
+ }
+
+ select#service-term {
+ min-width: 400px;
+ }
+}
+
+ul.resource-links {
+ padding: 0;
+ margin: 0 0 30px;
+}
+
+ul.resource-links li {
+ list-style: none;
+ display: block;
+}
+
+ul.resource-links li a {
+ background-color: #edf2f7;
+ padding: 15px 15px;
+ margin: 0 0 5px;
+ display: block;
+ border-radius: 4px;
+}
+ul.resource-links > li > a:after {
+ position: absolute;
+ font-family: FontAwesome;
+ margin-top: 0px;
+ right: 35px;
+ content: "\f138";
+ font-size: 1.2em;
+}
+
+ul.resource-links li a:hover {
+ text-decoration: none;
+ background-color: #DAE1EB;
+}
+
+.add-your-course {
+ border-radius: 4px;
+ border: 1px solid #30739C;
+ background: #fff;
+ padding: 20px;
+ font-size: 13px;
+}
+
+.foundation-report-title {
+ font-size: 1.1em;
+ font-weight: 300;
+ margin: 0 0 15px;
+ position: relative;
+ display: inline-block;
+}
+
+.foundation-report-title:before,
+.foundation-report-title:after {
+ content: "";
+ position: absolute;
+ border-bottom: 1px solid white;
+ top: 13px;
+ width: 80px;
+}
+
+.foundation-report-title:before {
+ right: 100%;
+ margin-right: 10px;
+}
+
+.foundation-report-title:after {
+ left: 100%;
+ margin-left: 10px;
+}
+
+/*404 Page*/
+.four-o-four-wrapper {
+ text-align: center;
+ margin: 50px auto;
+}
+
+.four-o-four-wrapper img {
+ width: 100%;
+ max-width: 650px;
+ margin: 50px 0 70px;
+}
+
+.four-o-four-wrapper p {
+ color: #8a959e;
+}
+
+.four-o-four-wrapper a {
+ color: #30739C;
+ text-decoration: none;
+}
+
+.four-o-four-links {
+ margin-top: 50px;
+}
+
+.four-o-four-links a {
+ color: #30739C;
+ padding: 0 10px;
+ text-decoration: none;
+}
+
+@media (max-width: 767px) {
+ .four-o-four-links a {
+ padding: 0 5px;
+ }
+}
+
+/*End 404 Page*/
+/*Blog Page*/
+.blog-title {
+ width: 100%;
+ background: url(/themes/openstack/images/images/summit-promo-bkgd1.jpg) no-repeat center center;
+ background-size: cover;
+ padding: 50px 0;
+ text-align: center;
+}
+
+.blog-o {
+ max-width: 80px;
+ opacity: 0.9;
+}
+
+.blog-title h1 {
+ color: white;
+ font-weight: 400;
+ font-size: 45px;
+ margin: 10px 0;
+}
+
+.blog-title h1 span {
+ font-size: 16px;
+ display: block;
+ padding-top: 10px;
+ color: white;
+ font-weight: 300;
+}
+
+.container.blog {
+ margin-top: 30px;
+}
+
+.post {
+ margin-bottom: 60px;
+}
+
+.post-byline {
+ border-bottom: 1px dotted #c5e2ea;
+ border-top: 1px dotted #c5e2ea;
+ margin-bottom: 15px;
+ padding: 12px 0;
+ font-size: 12px;
+}
+
+.post-byline p {
+ margin-bottom: 0;
+}
+
+.post-byline .name a {
+ color: #DA422F;
+}
+
+.post-byline .postDate {
+ text-align: right;
+ color: #2A4E68;
+}
+
+.entry h1,
+.entry h2,
+.entry h3,
+.entry h3 a {
+ color: #2A4E68;
+ font-weight: 300;
+}
+
+.entry h1 {
+ font-size: 24px;
+}
+
+.entry h2 {
+ font-size: 20px;
+}
+
+.entry h3 {
+ font-size: 18px;
+}
+
+.entry h3 a {
+ text-decoration: underline;
+}
+
+#sidebar {
+ background: #edf2f7;
+ padding: 15px;
+ border-radius: 4px;
+ margin-top: 0;
+}
+
+#sidebar ul {
+ margin: 0;
+}
+
+#sidebar ul li {
+ font-size: 12px;
+}
+
+.creative-commons {
+ text-align: center;
+ margin-top: 40px;
+ font-size: 12px;
+}
+
+.creative-commons img {
+ margin-bottom: 10px;
+}
+
+.navigation a {
+ background: #30739C;
+ padding: 5px 10px;
+ border-radius: 4px;
+ color: white;
+}
+
+/*End Blog Page*/
+
+/* site banner */
+.top-site-banner {
+ background-color: #1080A7;
+ color: #fff;
+ padding-top: 10px;
+}
+
+a.top-site-banner-button {
+ border-radius: 4px;
+ font-size: 0.85em;
+ padding: 3px 20px;
+ background-color: #30739C;
+ display: inline-block;
+ color: white;
+ text-transform: uppercase;
+}
+
+a.top-site-banner-button:hover {
+ text-decoration: none;
+ background-color: #2A4E68;
+ color: white;
+}
+
+a.top-site-banner-button:visited {
+ color: white;
+}
+
+/* end of site banner */
+/*Paris Summit Page*/
+.conference-title#paris {
+ margin-top: 30px;
+ width: 100%;
+}
+
+.conference-title#paris img {
+ width: 100%;
+ max-width: 1150px;
+}
+
+.conference-title h2,
+.conference-title h1 {
+ display: none;
+}
+
+.conference-summary {
+ min-height: 90px;
+ border-bottom: 1px solid #edf2f7;
+ padding-top: 30px;
+ padding-bottom: 30px;
+}
+
+.conference-calendar img {
+ max-width: 100%;
+}
+
+.news-heading h2 {
+ margin-bottom: 0px;
+}
+
+.rss-wrapper {
+ margin: 20px 0 0;
+}
+
+a.rss {
+ color: #E46616;
+ background: url("/themes/openstack/images/rss.png") no-repeat left center white;
+ padding-left: 20px;
+ float: right;
+}
+
+.conference-calendar {
+ height: 90px;
+}
+
+.conference-calendar p.date {
+ display: none;
+}
+
+.register a,
+.register a:visited {
+ display: block;
+ background: url("/themes/openstack/images/pointer-arrow.png") no-repeat 90% center #FF9631;
+ color: white;
+ padding: 10px 20px;
+ border-radius: 4px;
+ width: 80%;
+ font-size: 15px;
+}
+
+.register a:hover {
+ background-color: #E46616;
+ text-decoration: none;
+}
+
+.summit-videos,
+.summit-videos:visited,
+.summit-videos:active {
+ display: block;
+ width: 169px;
+ padding: 6px;
+ padding-top: 140px;
+ background: url("/themes/openstack/images/conferences/portland/summit-videos.png") no-repeat top center;
+ color: black;
+ text-decoration: none !important;
+}
+
+.summit-videos#atlanta-experience {
+ background: url("/themes/openstack/images/conferences/paris/atlanta-experience.png") no-repeat top center;
+}
+
+ul.documents {
+ list-style: none;
+ padding: 0;
+}
+
+ul.documents li a,
+ul.documents li a:visited {
+ padding: 15px 10px 15px 45px;
+ display: block;
+ list-style: none;
+ background: url("/themes/openstack/images/pdf-icon.png") no-repeat 15px center #edf2f7;
+ margin: 1px 0px 0px 0px;
+ color: #30739C;
+ border-radius: 4px;
+}
+
+#important-dates h3 {
+ font-size: 18px;
+}
+
+#important-dates {
+ font-size: 13px;
+}
+
+.news-item {
+ border-bottom: 1px solid #edf2f7;
+ padding-bottom: 30px;
+ margin-bottom: 30px;
+}
+
+/*End Paris Summit Page*/
+.newSubNav {
+ width: 85%;
+}
+
+.newSubNav ul {
+ padding: 0px;
+ margin-top: 30px;
+ margin-left: -10px;
+}
+
+.newSubNav ul:nth-child(2) {
+ border-top: 1px solid #E8E8E8;
+ padding-top: 30px;
+}
+
+.newSubNav li {
+ list-style: none;
+ position: relative;
+}
+
+.newSubNav a {
+ color: #8A959E;
+ text-transform: uppercase;
+ font-size: 12px;
+ padding: 15px 20px;
+ display: block;
+}
+
+.newSubNav a.current,
+.newSubNav a.current:hover {
+ background-color: #EDF2F7;
+ font-weight: bold;
+ color: #2D709B;
+}
+
+.newSubNav a:hover {
+ text-decoration: none;
+ color: black;
+}
+
+.newSubNav .fa-chevron-right {
+ color: #C6D8E4;
+ position: absolute;
+ right: 15px;
+ top: 50%;
+ margin-top: -6px;
+}
+
+li#start a {
+ color: #75C320;
+}
+
+li#openstack-shared-services {
+ padding-bottom: 20px;
+ border-bottom: 1px solid #E8E8E8;
+}
+
+li#start {
+ margin-top: 20px;
+}
+
+fieldset:nth-child(1) {
+ margin-top: 20px;
+}
+
+fieldset {
+ border: 1px solid #CCCCCC;
+ padding: 20px 17px 20px 17px;
+ margin-bottom: 20px;
+}
+
+fieldset ul {
+ list-style: none;
+}
+
+input[type="radio"] {
+ margin-left: 0px;
+}
+
+input[type="checkbox"] {
+ margin-left: 0px;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+ display: inline-block;
+}
+
+fieldset.footer-feedback-fieldset {
+ border: 0;
+ background: transparent;
+ padding: 0;
+ margin: 0;
+}
+
+.footer-links ul li {
+ list-style: none;
+}
+
+/*New responsive nav*/
+.navbar-collapse {
+ border-top: 0
+}
+
+@media only screen and (max-device-width: 1024px) {
+ .navbar-header {
+ float: none;
+ }
+
+ .navbar-left,.navbar-right {
+ float: none !important;
+ }
+
+ .navbar-toggle {
+ display: block;
+ }
+
+ .navbar-fixed-top {
+ top: 0;
+ border-width: 0 0 1px;
+ }
+
+ .navbar-collapse.collapse {
+ display: none!important;
+ }
+
+ .navbar-nav {
+ float: none!important;
+ margin-top: 7.5px;
+ }
+
+ .navbar-nav>li {
+ float: none;
+ }
+
+ .navbar-nav>li>a {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ }
+
+ .collapse.in {
+ display: block !important;
+ }
+}
+
+i.mobile-expand {
+ display: none;
+}
+
+@media (max-width: 767px) , only screen and (max-device-width: 1024px) {
+ .navbar-default ul.navbar-main > li > a {
+ width: auto;
+ display: inline-block;
+ }
+
+ i.mobile-expand {
+ position: relative;
+ display: inline-block;
+ cursor: pointer;
+ }
+
+ i.mobile-expand:after {
+ position: absolute;
+ padding: 10px 15px;
+ line-height: 20px;
+ color: #8a959e;
+ top: 50%;
+ margin-top: 0;
+ right: 0px;
+ content: "+";
+ font-size: 1.7em;
+ font-style: normal;
+ }
+
+ .navbar-nav > li.open i.mobile-expand {
+ position: relative;
+ display: inline-block;
+ cursor: pointer;
+ }
+
+ .navbar-nav > li.open i.mobile-expand:after {
+ position: absolute;
+ padding: 10px 15px;
+ line-height: 20px;
+ color: #aaa;
+ top: 50%;
+ margin-top: 0;
+ right: 0px;
+ content: "-";
+ font-size: 3em;
+ font-style: normal;
+ font-weight: 300;
+ }
+
+ .navbar-nav > li > ul.dropdown-menu {
+ display: block;
+ max-height: 0;
+ height: 0;
+ position: relative;
+ overflow: hidden;
+ border: 0;
+ box-shadow: none;
+ width: 100%;
+ padding: 0;
+ background: #edf2f7;
+ margin: 0;
+ -moz-transition: max-height 2s ease;
+ -webkit-transition: max-height 2s ease;
+ -o-transition: max-height 2s ease;
+ transition: max-height 2s ease;
+ }
+
+ .navbar-nav > li.open > ul.dropdown-menu {
+ display: block;
+ position: relative;
+ border: 0;
+ box-shadow: none;
+ height: auto;
+ max-height: 700px;
+ width: 100%;
+ padding: 10px 5%;
+ background: #edf2f7;
+ }
+}
+
+.dropdown-menu .divider {
+ width: 100%;
+}
+
+/*End New responsive nav*/
+/*COA Page*/
+.coa-hero {
+ background: #333 url(/themes/openstack/images/coa/COA-bkgd.jpg) no-repeat center center;
+ padding: 60px 0;
+ background-size: cover;
+ color: white;
+ margin-bottom: 40px;
+}
+
+.coa-hero-content {
+ text-align: center;
+ background: rgba(0, 0, 0, 0.6);
+ padding: 20px 0;
+}
+
+.coa-hero i.fa-graduation-cap {
+ padding: 10px 30px 10px 0;
+ border-right: 2px solid white;
+ margin-right: 30px;
+ font-size: 6em;
+ display: inline-block;
+}
+
+.coa-hero h1 {
+ color: white;
+ display: inline-block;
+ text-align: left;
+ font-size: 3em;
+ font-weight: 400;
+}
+
+@media (max-width: 767px) {
+ .coa-hero i.fa-graduation-cap {
+ margin-right: 0;
+ padding: 0;
+ border-right: 0;
+ }
+
+ .coa-hero h1 {
+ display: block;
+ text-align: center;
+ margin-top: 5px;
+ }
+}
+
+.coa-wrapper p {
+ text-align: left;
+ color: #2A4E68;
+}
+
+img.coa-hero-img {
+ max-width: 100%;
+ margin: 20px 0 60px;
+}
+
+.coa-sendgrid {
+ background: #edf2f7;
+ margin: 60px 0;
+ border-radius: 4px;
+ padding: 40px 20px;
+ text-align: center;
+}
+
+.coa-sendgrid p {
+ color: #2A4E68;
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+.coa-sendgrid i {
+ color: #C8CDD3;
+ margin: 0 0 10px;
+}
+
+.sendgrid-subscription-widget.coa-sendgrid input[type="email"] {
+ border: 2px solid #C8CDD3;
+ padding: 10px 20px;
+ border-radius: 4px;
+ color: #2A4E68;
+ font-size: 14px;
+ text-transform: lowercase;
+ font-weight: 400;
+ width: 100%;
+}
+
+.sendgrid-subscription-widget.coa-sendgrid input[type="email"]:focus {
+ outline: none;
+}
+
+.sendgrid-subscription-widget.coa-sendgrid label {
+ display: inline-block;
+ margin-right: 10px;
+ width: 40%;
+}
+
+@media (max-width: 767px) {
+ .sendgrid-subscription-widget.coa-sendgrid label {
+ width: 70%;
+ }
+}
+
+.sendgrid-subscription-widget.coa-sendgrid label>span {
+ display: none;
+}
+
+.sendgrid-subscription-widget.coa-sendgrid input[type="submit"] {
+ background: #29abe2;
+ padding: 9px 20px;
+ border-radius: 4px;
+ color: white;
+ text-transform: uppercase;
+ border: 2px solid #29abe2;
+}
+
+.sendgrid-subscription-widget.coa-sendgrid input[type="submit"]:hover {
+ background: #30739C;
+ border: 2px solid #30739C;
+}
+
+.sendgrid-subscription-widget.coa-sendgrid input::-webkit-input-placeholder,
+.sendgrid-subscription-widget.coa-sendgrid input::-webkit-input-placeholder,
+.sendgrid-subscription-widget.coa-sendgrid input::-webkit-input-placeholder {
+ color: #C8CDD3;
+ font-size: 14px;
+ text-transform: lowercase;
+ font-weight: 400;
+}
+/*End COA Page*/
+
+/*Austin Guide*/
+.guide-header {
+ text-align: center;
+}
+
+.guide-header h1 {
+ margin-bottom: 20px;
+}
+
+@media (max-width: 767px) {
+ .guide-header h1 {
+ margin-top: 20px;
+ }
+}
+
+.guide-nav-title {
+ text-align: center;
+ margin: 35px 0 10px;
+ font-size: 20px;
+ font-weight: 400;
+}
+
+.guide-nav {
+ margin: 20px 0 0;
+ padding: 20px 0;
+ background: #29abe2;
+}
+
+.guide-nav.fixed {
+ position: fixed;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: 1000;
+ margin-top: 0;
+}
+
+.guide-nav.fixed a.guide-icons {
+ font-size: 12px;
+}
+
+.guide-nav.fixed a.guide-icons i {
+ display: inline-block;
+ margin-right: 3px;
+ font-size: 15px;
+}
+
+@media (max-width: 767px) {
+ .guide-nav.fixed a.guide-icons {
+ font-size: 10px;
+ }
+
+ .guide-nav.fixed a.guide-icons i {
+ display: block;
+ margin-right: 0;
+ font-size: 17px;
+ }
+}
+
+.guide-icons-wrapper {
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+}
+
+a.guide-icons {
+ -webkit-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ text-align: center;
+ text-transform: uppercase;
+ font-size: 12px;
+ font-weight: 400;
+ color: white;
+ opacity: 0.8;
+}
+
+a.guide-icons i {
+ text-align: center;
+ display: block;
+ margin-bottom: 10px;
+ font-size: 2.2em;
+}
+
+a.guide-icons:hover,
+a.guide-icons:focus {
+ text-decoration: none;
+ opacity: 1;
+}
+
+@media (max-width: 767px) {
+ a.guide-icons {
+ font-size: 10px;
+ }
+
+ a.guide-icons i {
+ font-size: 30px;
+ margin-bottom: 10px;
+ }
+}
+
+h5.section-title {
+ color: #2A4E68;
+ font-weight: 600;
+ text-transform: capitalize;
+ border-bottom: 1px solid #dae5ee;
+ padding-bottom: 20px;
+ margin: 50px 0 30px;
+}
+
+h5.section-title span {
+ float: right;
+ text-transform: uppercase;
+ color: #a9a9a9;
+ font-weight: 700;
+ font-size: 0.8em;
+ position: relative;
+}
+
+h5.section-title span i {
+ font-size: 1.3em;
+ margin-right: 5px;
+}
+
+h5.section-title span:hover {
+ color: #29abe2;
+ cursor: default;
+}
+
+h5.section-title .tooltip {
+ font-weight: 400;
+ width: auto;
+ white-space: normal;
+}
+
+h5.section-title.guide {
+ margin: 60px 0.75% 10px;
+ color: #2A4E68;
+ text-transform: uppercase;
+ background: #edf2f7;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 4px;
+}
+
+.guide-quotes {
+ margin: 20px 0 30px;
+}
+
+.guide-quotes blockquote {
+ font-family: Georgia, serif;
+ font-weight: normal;
+ font-size: 1.1em;
+ border: none;
+ position: relative;
+ text-align: center;
+ margin: 20px 20px 10px;
+}
+
+.guide-quotes blockquote:before,
+.guide-quotes blockquote:after {
+ color: #edf2f7;
+ font-size: 5em;
+ position: absolute;
+ top: -30px;
+}
+
+.guide-quotes blockquote:before {
+ content: open-quote;
+ left: 0px;
+}
+
+.guide-quotes blockquote:after {
+ content: close-quote;
+ right: 0px;
+}
+
+.guide-quotes .guide-quote-name {
+ text-align: center;
+ font-size: 0.9em;
+}
+
+.featured-guide-row {
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ margin-bottom: 30px;
+}
+
+.featured-guide-row .featured {
+ position: relative;
+ -webkit-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ margin: 10px;
+ border: 1px solid #D4DCE5;
+ border-radius: 4px;
+}
+
+.featured-guide-row .featured .image-wrapper {
+ overflow: hidden;
+ width: 100%;
+ height: 180px;
+ background-size: cover;
+ background-position: center center;
+ background-repeat: no-repeat;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
+
+.featured-guide-row .featured .featured-content {
+ padding: 5px 10px 10px;
+}
+
+.featured-guide-row .featured .featured-content h5 {
+ color: #2A4E68;
+ font-weight: 600;
+ margin-bottom: 10px;
+}
+
+.featured-guide-row .featured .featured-content .address {
+ font-size: 0.9em;
+ font-weight: 600;
+ margin: 5px 0 10px;
+}
+
+.featured-guide-row .featured .featured-content p {
+ font-size: 0.9em;
+}
+
+.featured-guide-row .featured .featured-content a.featured-site {
+ color: #30739C;
+ padding: 3px 7px;
+ border-radius: 4px;
+ border: 1px solid #30739C;
+ display: inline-block;
+ margin-top: 5px;
+}
+
+.featured-guide-row .featured .featured-content a.featured-site:hover {
+ text-decoration: none;
+ border-color: #29abe2;
+ color: #29abe2;
+}
+
+@media (max-width: 767px) {
+ .featured-guide-row {
+ display: block;
+ margin-bottom: 30px;
+ }
+
+ .featured-guide-row .featured {
+ display: block;
+ width: 95%;
+ float: left;
+ }
+
+ .featured-guide-row .featured .image-wrapper {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 30%;
+ min-height: 100%;
+ height: 100%;
+ float: left;
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ border-top-right-radius: 0;
+ }
+
+ .featured-guide-row .featured .featured-content {
+ padding: 10px;
+ margin-left: 31%;
+ }
+}
+
+.guide-listing {
+ border-bottom: 1px solid #edf2f7;
+ padding: 10px 0;
+ margin-bottom: 10px;
+}
+
+.guide-listing .guide-name {
+ font-weight: 600;
+}
+
+/*PTG*/
+/*Visa Steps*/
+.visa-steps-wrapper {
+ background: #edf2f7;
+ padding: 15px 0 0 0;
+ margin-bottom: 30px;
+ text-align: center;
+ position: relative;
+ display: block;
+}
+
+.visa-steps-wrapper h3 {
+ margin: 15px 0 10px;
+ font-weight: 300;
+}
+
+.visa-steps-wrapper h5 {
+ color: #2A4E68;
+ font-size: 1.1em;
+ margin-bottom: 20px;
+}
+
+.visa-steps-wrapper h5 i {
+ margin-right: 7px;
+ font-size: 1.1em;
+}
+
+.visa-steps-wrapper h5 strong {
+ text-transform: uppercase;
+}
+
+.visa-steps-wrapper .visa-steps-row {
+ position: relative;
+ width: 100%;
+ padding: 10px 1% 0;
+}
+
+.visa-steps-wrapper .visa-steps-row .visa-step {
+ width: 19%;
+ display: table-cell;
+ padding: 0 0.5%;
+ line-height: 1.3;
+}
+
+.visa-steps-wrapper .visa-docs {
+ background: #2A4E68;
+ margin: 10px -1% 0 -1%;
+ color: white;
+ padding: 10px 20px;
+ position: relative;
+}
+
+.visa-steps-wrapper .visa-docs:after {
+ bottom: 95%;
+ left: 86%;
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+ border-color: rgba(42, 78, 104, 0);
+ border-bottom-color: #2A4E68;
+ border-width: 20px;
+ margin-left: -20px;
+}
+
+.visa-steps-wrapper .visa-docs h4 {
+ color: white;
+ text-align: left;
+ font-weight: 300;
+ font-size: 1.4em;
+}
+
+.visa-steps-wrapper .visa-docs ul {
+ margin: 10px 0 15px 15px;
+ padding: 0;
+ text-align: left;
+}
+
+.visa-steps-wrapper .visa-docs ul li {
+ list-style: normal;
+ font-size: 0.9em;
+ font-weight: 300;
+}
+/*End Visa Steps*/
+/*End PTG */
+
+/* OpenDev Promo */
+.opendev-earth {
+ max-width: 35%;
+ position: absolute;
+ left: -10%;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+@media (max-width: 768px) {
+ .opendev-earth {
+ top: 45%;
+ left: 50%;
+ transform: translateX(-50%);
+ opacity: 0.5;
+ width: 100%;
+ max-width: 100%;
+ }
+}
+/* End OpenDev Promo */
diff --git a/3rd_party/static/onap-ui/assets/css/cvp-style.css b/3rd_party/static/onap-ui/assets/css/cvp-style.css
new file mode 100644
index 0000000..e544458
--- /dev/null
+++ b/3rd_party/static/onap-ui/assets/css/cvp-style.css
@@ -0,0 +1,125 @@
+.nav>li>a {
+ font-size: 18px;
+}
+
+.nav>li>a:hover,
+.nav>li>a:focus,
+.nav>li.active,
+.nav>li.active>a {
+ background-color: #1080A7;
+ color: #E7E6E6;
+}
+
+table {
+ font-size: 15px;
+}
+
+th {
+ font-size: 16px;
+}
+
+.dropdown-menu {
+ padding: 0px;
+}
+
+.dropdown-menu>li>a,
+.dropdown-menu>li>span {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: 400;
+ line-height: 1.42857143;
+ white-space: nowrap;
+ background-color: #1C1C1C;
+ color: #ccc;
+}
+
+.dropdown-menu>li>a:focus,
+.dropdown-menu>li>a:hover {
+ font-size: 15px;
+ color: #1080A7;
+ text-decoration: none;
+ background-color: #1C1C1C;
+}
+
+.pagination>.active>a,
+.pagination>.active>a:focus,
+.pagination>.active>a:hover,
+.pagination>.active>span,
+.pagination>.active>span:focus,
+.pagination>.active>span:hover {
+ color: #fff;
+ background-color: #1080A7;
+ border-color: #1080A7;
+}
+
+
+.dropdown-menu {
+ border: 0px solid #ccc;
+}
+
+.dropdown-menu>li>span>i {
+ cursor: pointer;
+}
+
+.heading .logo {
+ display: inline-block;
+ padding-bottom: 30px;
+}
+
+.heading .slogan {
+ display: inline-block;
+ margin-left: 20px;
+}
+
+.badge-info {
+ color: #fff;
+ background-color: #1080A7;
+ border-color: #1080A7;
+}
+
+.opnfv-blue {
+ color: #1080A7;
+}
+
+.field {
+ margin-bottom: 10px;
+}
+
+table .btn.medium {
+ padding: 0px 15px;
+}
+
+.cvp-btn {
+ background-color: #1080A7;
+ font-weight: 700;
+ font-size: 12px;
+}
+
+.common-main-container {
+ padding-left: 5%;
+ padding-right: 5%;
+}
+
+a {
+ cursor: pointer;
+}
+
+.btn-success-cust {
+color: #fff;
+background-color: #1080A7;
+border-color: #1080A7;
+}
+
+.btn-success-cust:focus, .btn-success-cust:hover {
+border-color: #27CCC0;
+background-color: #27CCC0;
+}
+
+input:invalid {
+ border: 2px dashed red;
+}
+
+input:valid {
+ border: 2px solid black;
+} \ No newline at end of file
diff --git a/3rd_party/static/onap-ui/assets/css/header.css b/3rd_party/static/onap-ui/assets/css/header.css
new file mode 100644
index 0000000..3f39b8a
--- /dev/null
+++ b/3rd_party/static/onap-ui/assets/css/header.css
@@ -0,0 +1,37 @@
+.header-container-1 {
+ height: 80px;
+ background-color: #E7E6E6;
+}
+
+.header-container-row {
+ margin-left: 0px;
+ margin-right: 0px;
+}
+
+.header-container-2 {
+ height: 30px;
+ background-color: #E7E6E6;
+}
+
+.header-logo {
+ height: 80px;
+ padding-left: 15px;
+ padding-top: 20px;
+}
+
+.header-title {
+ font-size: 40px;
+ font-weight: bold;
+ color: #2C2B2F;
+ padding-top: 30px;
+}
+
+.header-login {
+ margin-top: -20px;
+ margin-right: 10px;
+}
+
+.header-splitline {
+ height: 5px;
+ background-color: #1080A7;
+}
diff --git a/3rd_party/static/onap-ui/assets/css/home/home.css b/3rd_party/static/onap-ui/assets/css/home/home.css
new file mode 100644
index 0000000..66b3a8e
--- /dev/null
+++ b/3rd_party/static/onap-ui/assets/css/home/home.css
@@ -0,0 +1,72 @@
+.home-container {
+ padding-left: 0px;
+ padding-right: 0px;
+}
+
+.home-category {
+ border-right-style: solid;
+ border-left-style: solid;
+ padding-left: 0px;
+ padding-right: 0px;
+}
+
+.home-content-title {
+ margin-top: 40px;
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+.home-content {
+ margin-top: 100px;
+}
+
+.home-content-text {
+ text-align: justify;
+ font-size: 22px;
+}
+
+.home-content-img {
+ width: 80%;
+}
+
+#directory_inner th {
+ font-size: 20px;
+}
+
+#directory_inner tbody {
+ font-size: 18px;
+}
+
+#directory_inner tr {
+ vertical-align: middle;
+}
+
+#directory_inner {
+ width: 100%;
+ max-width: 100%;
+ margin-bottom: 20px;
+}
+
+#directory_break {
+ height: 1px;
+ display: flex;
+ border: 1px solid #ccc;
+}
+
+#directory_inner > thead > tr > th {
+ border-bottom: 2px solid #ddd;
+ padding-bottom: 8px;
+}
+
+#directory_inner > tbody > tr > td {
+ border-bottom: 1px solid #ddd;
+}
+
+.company_logo {
+ padding: 20px 30px 20px 20px;
+}
+
+.company_row:hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
diff --git a/3rd_party/static/onap-ui/assets/css/index.css b/3rd_party/static/onap-ui/assets/css/index.css
new file mode 100644
index 0000000..3140fce
--- /dev/null
+++ b/3rd_party/static/onap-ui/assets/css/index.css
@@ -0,0 +1,4 @@
+.index-header {
+ padding-left: 0px;
+ padding-right: 0px;
+}
diff --git a/3rd_party/static/onap-ui/assets/img/icon.png b/3rd_party/static/onap-ui/assets/img/icon.png
new file mode 100644
index 0000000..6ec8ca7
--- /dev/null
+++ b/3rd_party/static/onap-ui/assets/img/icon.png
Binary files differ
diff --git a/3rd_party/static/onap-ui/assets/img/logo.png b/3rd_party/static/onap-ui/assets/img/logo.png
new file mode 100644
index 0000000..c6f6857
--- /dev/null
+++ b/3rd_party/static/onap-ui/assets/img/logo.png
Binary files differ
diff --git a/3rd_party/static/onap-ui/components/application/application.html b/3rd_party/static/onap-ui/components/application/application.html
new file mode 100644
index 0000000..2238ca4
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/application/application.html
@@ -0,0 +1,111 @@
+<div class="container-fluid common-main-container">
+ <div class="results-table" style="margin-top: 30px; overflow: scroll;">
+ <table class="table table-striped table-hover">
+ <thead>
+ <tr>
+ <th>Create Date</th>
+ <th>Owner</th>
+ <th>ONAP version</th>
+ <th>Company Name</th>
+ <th>Company logo</th>
+ <th>Company Website</th>
+ <th>Approve date</th>
+ <th>Test ID</th>
+ <th>xNF Version</th>
+ <th>xNF Name</th>
+ <th>xNF Type</th>
+ <th>xNF Description</th>
+ <th>xNF Id</th>
+ <th>xNF Model Language</th>
+ <th>xNF Checksum (SHA256)</th>
+ <th>Test Period</th>
+ <th>Location</th>
+ <th>Operation</th>
+ </tr>
+ </thead>
+ <script type="text/ng-template" id="product.tpl.html">
+ <div class="input-group">
+ <span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
+ <input type="text" value="{{app.product_spec}}">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon">@</span>
+ <input type="text" value="{{app.product_documentation}}">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="glyphicon glyphicon-map-marker"></i></span>
+ <input type="text" value="{{app.product_categories | category}}">
+ </div>
+ </script>
+ <script type="text/ng-template" id="lab.tpl.html">
+ <div class="input-group">
+ <span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
+ <input type="text" value="{{app.lab_name}}">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon">@</span>
+ <input type="text" value="{{app.lab_email}}">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="glyphicon glyphicon-map-marker"></i></span>
+ <input type="text" value="{{app.lab_address}}">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon"><i class="glyphicon glyphicon-phone"></i></span>
+ <input type="text" value="{{app.lab_phone}}">
+ </div>
+ </script>
+ <tbody style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
+ <tr ng-if="results.status != 'review'" ng-repeat="app in ctrl.applications">
+ <td>{{ app.creation_date | limitTo: 10 }}</td>
+ <td>{{ app.owner }}</td>
+ <td>{{ app.onap_version }}</td>
+ <td>{{ app.company_name }}</td>
+ <td>{{ app.company_logo }}</td>
+ <td>{{ app.company_website }}</td>
+ <td>{{ app.approve_date | limitTo: 10 }}</td>
+ <td>{{ app.test_id }}</td>
+ <td>{{ app.xnf_version }}</td>
+ <td>{{ app.xnf_name }}</td>
+ <td>{{ app.xnf_type }}</td>
+ <td>{{ app.xnf_description }}</td>
+ <td>{{ app.xnfd_id }}</td>
+ <td>{{ app.xnfd_model_lang }}</td>
+ <td>{{ app.xnf_checksum }}</td>
+ <td>{{ app.xnf_test_period }}</td>
+ <td>
+ <span popover-enable="app.lab_location != 'internal'" uib-popover-template="ctrl.lab_tpl"
+ popover-title="Lab Info" popover-placement="top"
+ popover-trigger="mouseenter">{{ app.lab_location | labLocation}}</span>
+ <i ng-if="app.lab_location != 'internal'" class="glyphicon glyphicon-info-sign opnfv-blue"></i>
+ </td>
+ <td>
+ <a ng-click="ctrl.toggleApproveApp(app._id, 'true')" class="badge badge-info"
+ ng-if="app.approved == 'false'"
+ data-toggle="tooltip" title="Approve Application">
+ <i class="glyphicon glyphicon-ok" ></i>
+ </a>
+ <a ng-click="ctrl.deleteApp(app._id)" class="badge badge-info"
+ data-toggle="tooltip" title="Delete Application">
+ <i class="glyphicon glyphicon-remove" ></i>
+ </a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <div class="pages">
+ <uib-pagination
+ total-items="ctrl.totalItems"
+ ng-model="ctrl.currentPage"
+ items-per-page="ctrl.itemsPerPage"
+ max-size="ctrl.maxSize"
+ class="pagination-sm"
+ boundary-links="true"
+ rotate="false"
+ num-pages="ctrl.numPages"
+ ng-change="ctrl.updatePage()">
+ </uib-pagination>
+ </div>
+ </div>
+</div>
diff --git a/3rd_party/static/onap-ui/components/application/applicationController.js b/3rd_party/static/onap-ui/components/application/applicationController.js
new file mode 100644
index 0000000..094ffdc
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/application/applicationController.js
@@ -0,0 +1,110 @@
+/*
+ * 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('testapiApp')
+ .controller('ApplicationController', ApplicationController);
+
+ ApplicationController.$inject = [
+ '$http', '$stateParams', '$window', '$sce',
+ '$uibModal', 'testapiApiUrl', 'raiseAlert', 'ngDialog', '$scope'
+ ];
+
+ /**
+ */
+ function ApplicationController($http, $stateParams, $window, $sce,
+ $uibModal, testapiApiUrl, raiseAlert, ngDialog, $scope) {
+
+ var ctrl = this;
+
+ function init() {
+ ctrl.applications = [];
+
+ ctrl.totalItems = null;
+ ctrl.currentPage = 1;
+ ctrl.itemsPerPage = 5;
+ ctrl.numPages = null;
+
+ ctrl.lab_tpl = "lab.tpl.html";
+ ctrl.product_tpl = "product.tpl.html";
+
+ getApplication();
+ }
+
+ ctrl.updatePage = function() {
+ getApplication();
+ }
+
+ ctrl.deleteApp = function(id) {
+ var resp = confirm('Are you sure you want to delete this application?');
+ if (!resp)
+ return;
+
+ var delUrl = testapiApiUrl + "/cvp/applications/" + id;
+ $http.delete(delUrl)
+ .then(function(ret) {
+ if (ret.data.code && ret.data.code != 0) {
+ alert(ret.data.msg);
+ return;
+ }
+ getApplication();
+ });
+ }
+
+ ctrl.toggleApproveApp = function(id, approved) {
+ if (approved === 'true') {
+ var text = 'Are you sure you want to approve this application?';
+ } else {
+ var text = 'Are you sure you want to remove approval of this application?';
+ }
+
+ var resp = confirm(text);
+ if (!resp)
+ return;
+
+ var updateUrl = testapiApiUrl + "/cvp/applications/" + id;
+ var data = {};
+ data['item'] = 'approved';
+ data['approved'] = approved;
+
+ $http.put(updateUrl, JSON.stringify(data), {
+ transformRequest: angular.identity,
+ headers: {'Content-Type': 'application/json'}}).then(function(ret) {
+ if (ret.data.code && ret.data.code != 0) {
+ alert(ret.data.msg);
+ return;
+ }
+ getApplication();
+ }, function(error) {
+ alert('Error when update data');
+ });
+ }
+
+ function getApplication() {
+ $http.get(testapiApiUrl + "/onap/cvp/applications?page=" + ctrl.currentPage + "&signed&per_page=" + ctrl.itemsPerPage).then(function(response) {
+ ctrl.applications = response.data.applications;
+ ctrl.totalItems = response.data.pagination.total_pages * ctrl.itemsPerPage;
+ ctrl.currentPage = response.data.pagination.current_page;
+ ctrl.numPages = response.data.pagination.total_pages;
+ }, function(error) {
+ /* do nothing */
+ });
+ }
+
+ init();
+ }
+})();
diff --git a/3rd_party/static/onap-ui/components/auth-failure/authFailureController.js b/3rd_party/static/onap-ui/components/auth-failure/authFailureController.js
new file mode 100644
index 0000000..29d1d70
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/auth-failure/authFailureController.js
@@ -0,0 +1,33 @@
+/*
+ * 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('testapiApp')
+ .controller('AuthFailureController', AuthFailureController);
+
+ AuthFailureController.$inject = ['$location', '$state', 'raiseAlert'];
+ /**
+ * TestAPI Auth Failure Controller
+ * This controller handles messages from TestAPI API if user auth fails.
+ */
+ function AuthFailureController($location, $state, raiseAlert) {
+ var ctrl = this;
+ ctrl.message = $location.search().message;
+ raiseAlert('danger', 'Authentication Failure:', ctrl.message);
+ $state.go('home');
+ }
+})();
diff --git a/3rd_party/static/onap-ui/components/directory/directory.html b/3rd_party/static/onap-ui/components/directory/directory.html
new file mode 100644
index 0000000..4a04bd7
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/directory/directory.html
@@ -0,0 +1,34 @@
+<div class="container-fluid common-main-container">
+ <h3>ONAP Verified Product Directory</h3>
+ <div>
+ <h4>Compliance Marks Granted to {{ctrl.companyID}}</h4>
+ <img src="api/v1/cvp/applications/getlogo/{{ctrl.company_logo}}" alt="Company Logo">
+ <table class="table table-striped table-hover">
+ <thead>
+ <tr>
+ <th>Product Name</th>
+ <th>Description</th>
+ <th>Model Language</th>
+ <th>Artifact Checksum (SHA256)</th>
+ <th>Product Version</th>
+ <th>Version</th>
+ <th>Date</th>
+ <th>Product Info</th>
+ </tr>
+ </thead>
+ <tbody class="directory_inner" style="overflow: hidden; text-overflow: ellipsis;">
+ <tr style="vertical-align: center;" ng-repeat="prod in ctrl.directory"
+ ng-if="prod.company_name==ctrl.companyID && prod.approved=='true'">
+ <td style="width: 250px;">{{ prod.xnf_name }}</td>
+ <td style="width: 350px;">{{ prod.xnf_description }}</td>
+ <td style="width: 350px;">{{ prod.xnfd_model_lang }}</td>
+ <td style="width: 350px;">{{ prod.xnf_checksum }}</td>
+ <td style="width: 150px;">{{ prod.xnf_version }}</td>
+ <td style="width: 150px;">{{ prod.onap_version }}</td>
+ <td style="width: 150px;">{{ prod.approve_date | limitTo: 10 }}</td>
+ <td style="width: 150px;"><a href="{{ prod.company_website }}" target="_blank" rel="noopener">{{ prod.company_website }}</a></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/3rd_party/static/onap-ui/components/directory/directoryController.js b/3rd_party/static/onap-ui/components/directory/directoryController.js
new file mode 100644
index 0000000..a4fc6f3
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/directory/directoryController.js
@@ -0,0 +1,45 @@
+/*
+ * 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('testapiApp')
+ .controller('DirectoryController', DirectoryController);
+
+ DirectoryController.$inject = ['$http', '$stateParams',
+ 'testapiApiUrl'
+ ];
+
+ /**
+ * This controller handles the directory page
+ */
+ function DirectoryController($http, $stateParams, testapiApiUrl) {
+ var ctrl = this;
+
+ ctrl.companyID = $stateParams.companyID;
+ ctrl.company_logo = $stateParams.logo;
+ getDirectory();
+
+ function getDirectory() {
+ $http.get(testapiApiUrl + "/onap/cvp/applications").then(function(response) {
+ ctrl.directory = response.data.applications;
+ }, function(error) {
+ /* do nothing */
+ });
+ }
+ }
+
+})();
diff --git a/3rd_party/static/onap-ui/components/home/home.html b/3rd_party/static/onap-ui/components/home/home.html
new file mode 100644
index 0000000..4db08b0
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/home/home.html
@@ -0,0 +1,170 @@
+<div class="container-fluid">
+ <div class="row">
+ <div class="col-md-2 home-category" ng-style="{'height': ctrl.height}">
+ <div class="panel-group" id="accordion">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <div class="panel-title">
+ <a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
+ Governance &amp; Workflow
+ </a>
+ </div>
+ </div>
+ <div id="collapseOne" class="panel-collapse collapse">
+ <div class="panel-body">
+ <div>
+ <a href="https://www.opnfv.org/verified"
+ target="_blank" rel="noopener">Overview&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="https://www.opnfv.org/wp-content/uploads/sites/12/2018/09/LFN_CVP_Guidelines-1.0.0.pdf"
+ target="_blank" rel="noopener">Governance Guidelines&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="https://www.opnfv.org/wp-content/uploads/sites/12/2018/09/OVP-Terms-and-Conditions-092418.pdf"
+ target="_blank" rel="noopener">Terms &amp; Conditions&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="http://docs.opnfv.org/en/stable-fraser/submodules/dovetail/docs/testing/user/certificationworkflow/index.html"
+ target="_blank" rel="noopener">Process Workflow&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="https://na3.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=dc24bf38-ea41-40d4-9e58-9babc6eec778"
+ target="_blank" rel="noopener">Participation Form&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="https://wiki.onap.org/display/DW/VNF+Badging"
+ target="_blank" rel="noopener">ONAP Verified Brand Guidelines&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="panel panel-default" style="margin-top: 0px;">
+ <div class="panel-heading">
+ <div class="panel-title">
+ <a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo">
+ Release 2018.11
+ </a>
+ </div>
+ </div>
+ <div id="collapseTwo" class="panel-collapse collapse">
+ <div class="panel-body">
+ <div>
+ <a href="http://docs.opnfv.org/en/stable-fraser/submodules/dovetail/docs/testing/user/userguide/testing_guide.html"
+ target="_blank" rel="noopener">ONAPVP / Dovetail User Guide&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="http://docs.opnfv.org/en/stable-fraser/submodules/dovetail/docs/testing/user/userguide/cli_reference.html"
+ target="_blank" rel="noopener">Dovetail CLI&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="https://wiki.onap.org/display/DW/ONAP+Modeling+specification"
+ target="_blank" rel="noopener">Test Specifications&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="https://wiki.onap.org/display/DW/Release+Calendar"
+ target="_blank" rel="noopener">Release Notes&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="http://docs.opnfv.org/en/stable-fraser/submodules/dovetail/docs/testing/user/reviewerguide/index.html"
+ target="_blank" rel="noopener">Reviewer Guide&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ <div>
+ <a href="http://docs.opnfv.org/en/stable-fraser/submodules/dovetail/docs/testing/user/ovpaddendum/index.html"
+ target="_blank" rel="noopener">Guidelines Addendum&nbsp;
+ <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-md-10">
+ <div class="common-main-container">
+ <div class="home-content-title">
+ <h2>ONAP Verified Program verifies products and services with the "ONAP Verified" mark.</h2>
+ </div>
+ <div class="home-content">
+ <div class="col-md-2">
+ <img class="home-content-img" src="onap-ui/assets/img/icon.png" alt="ONAP">
+ </div>
+ <div class="col-md-10">
+ <p class="home-content-text" style="padding-bottom: 40px;">
+ The ONAP Verified Program demonstrates the readiness and availability of commercial VNF/PNF
+ products and services by implementing Specification defined by Compliance Verification
+ Committee(CVC). This open source community-led initiative allows vendors and service
+ providers to establish baseline conformance and interoperability while retaining distinct
+ and value-added innovations across features and capabilities and requires verified products
+ to complete additional functional, high-availability tests. View the directory of verified
+ products and services below and navigate through the links in the left-hand menu to learn
+ more and get started. You will find step-by-step instructions as well as a participation form.
+ Use this portal to upload your test results when ready. Please send any questions to
+ <a href="mailto:compliance@lists.lfnetworking.org">compliance@lists.lfnetworking.org</a><sup>
+ <span class="glyphicon glyphicon-envelope" style="font-size: 60%;" aria-hidden="true"></span></sup>.
+ </p>
+ </div>
+ </div>
+ <div id="directory_break">
+ </div>
+ <div class="home-content-title">
+ <h1>ONAP Verified Products Directory</h1>
+ Click on rows for more product verification details per company.
+ </div>
+ <div class="directory_main">
+ <table id="directory_inner" class="">
+ <thead>
+ <tr>
+ <th>Company Name &amp; Logo</th>
+ <th>Test Agency</th>
+ <th>Product Name</th>
+ <th>Product Version</th>
+ <th>Modeling Language</th>
+ <th>Type</th>
+ <th>Version</th>
+ </tr>
+ </thead>
+ <tbody style="overflow: hidden; text-overflow: ellipsis;">
+ <tr class="company_row" ng-click="ctrl.getCompany(app)"
+ ng-repeat="app in ctrl.applications | filter:{approved:true} | orderBy : '-approve_date'">
+ <td style="width: 400px;">
+ <img class="company_logo" ng-src="api/v1/cvp/applications/getlogo/{{app.company_logo}}" alt="{{app.company_logo}}">
+ {{ app.company_name }}
+ </td>
+ <td style="width: 300px;">{{ app.lab_location }}</td>
+ <td style="width: 300px;">{{ app.xnf_name }}</td>
+ <td style="width: 150px;">{{ app.xnf_version }}</td>
+ <td style="width: 150px;">{{ app.xnfd_model_lang }}</td>
+ <td style="width: 150px;">N/A</td>
+ <td style="width: 150px;">{{ app.onap_version}}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/3rd_party/static/onap-ui/components/home/homeController.js b/3rd_party/static/onap-ui/components/home/homeController.js
new file mode 100644
index 0000000..678dd2a
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/home/homeController.js
@@ -0,0 +1,58 @@
+/*
+ * 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('testapiApp')
+ .controller('HomeController', HomeController);
+
+ HomeController.$inject = [
+ '$http', '$rootScope', '$state', 'testapiApiUrl'
+ ];
+
+ /**
+ * TestAPI Results Controller
+ * This controller is for the '/results' page where a user can browse
+ * a listing of community uploaded results.
+ */
+ function HomeController($http, $rootScope, $state, testapiApiUrl) {
+ var ctrl = this;
+ getApplication();
+
+ ctrl.height = $(document).height() + 500;
+
+ ctrl.gotoApplication = function() {
+ if ($rootScope.auth.isAuthenticated) {
+ $state.go('application');
+ } else {
+ $rootScope.auth.doSignIn('cas');
+ }
+ }
+
+ function getApplication() {
+ $http.get(testapiApiUrl + "/onap/cvp/applications").then(function(response) {
+ ctrl.applications = response.data.applications;
+ }, function(error) {
+ /* do nothing */
+ });
+ }
+
+ ctrl.getCompany = function(row) {
+ $state.go('directory', {'companyID': row.company_name, 'logo': row.company_logo});
+ }
+
+ }
+})();
diff --git a/3rd_party/static/onap-ui/components/logout/logout.html b/3rd_party/static/onap-ui/components/logout/logout.html
new file mode 100644
index 0000000..38a5c36
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/logout/logout.html
@@ -0,0 +1 @@
+<div cg-busy="{promise:ctrl.redirectWait,message:'Logging you out...'}"></div>
diff --git a/3rd_party/static/onap-ui/components/logout/logoutController.js b/3rd_party/static/onap-ui/components/logout/logoutController.js
new file mode 100644
index 0000000..1b6d78c
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/logout/logoutController.js
@@ -0,0 +1,44 @@
+/*
+ * 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('testapiApp')
+ .controller('LogoutController', LogoutController);
+
+ LogoutController.$inject = [
+ '$location', '$window', '$timeout'
+ ];
+
+ /**
+ * TestAPI Logout Controller
+ * This controller handles logging out. In order to fully logout, the
+ * openstackid_session cookie must also be removed. The way to do that
+ * is to have the user's browser make a request to the openstackid logout
+ * page. We do this by placing the logout link as the src for an html
+ * image. After some time, the user is redirected home.
+ */
+ function LogoutController($location, $window, $timeout) {
+ var ctrl = this;
+
+ ctrl.openid_logout_url = $location.search().openid_logout;
+ var img = new Image(0, 0);
+ img.src = ctrl.openid_logout_url;
+ ctrl.redirectWait = $timeout(function() {
+ $window.location.href = '/';
+ }, 500);
+ }
+})();
diff --git a/3rd_party/static/onap-ui/components/profile/profile.html b/3rd_party/static/onap-ui/components/profile/profile.html
new file mode 100644
index 0000000..cb73335
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/profile/profile.html
@@ -0,0 +1,88 @@
+<div class="container-fluid common-main-container">
+ <h3>User profile</h3>
+ <div cg-busy="{promise:ctrl.authRequest,message:'Loading'}"></div>
+ <div>
+ <table class="table table-striped table-hover">
+ <tbody>
+ <tr>
+ <td>User name</td>
+ <td>{{auth.currentUser.fullname}}</td>
+ </tr>
+ <tr>
+ <td>User OpenId</td>
+ <td>{{auth.currentUser.openid}}</td>
+ </tr>
+ <tr>
+ <td>Email</td>
+ <td>{{auth.currentUser.email}}</td>
+ </tr>
+ <tr>
+ <td>Role</td>
+ <td>{{auth.currentUser.role}}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <br>
+ <br>
+ <h3>Organization Details</h3>
+ <table class="table table-striped table-hover">
+ <tbody>
+ <tr>
+ <td>Company Name</td>
+ <td>
+ <div class="popover-wrapper">
+ <a editable-theme="bs3" onbeforesave="ctrl.changeProfileDetails(ctrl.profile, 'companyName', $data)"
+ editable-text="ctrl.profile.companyName">{{ ctrl.profile.companyName || "None" }}</a>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td>Company website</td>
+ <td>
+ <div class="popover-wrapper">
+ <a editable-theme="bs3" onbeforesave="ctrl.changeProfileDetails(ctrl.profile, 'companyWebsite', $data)"
+ editable-url="ctrl.profile.companyWebsite">{{ ctrl.profile.companyWebsite || "None" }}</a>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td>Primary Contact Name</td>
+ <td>
+ <div class="popover-wrapper">
+ <a editable-theme="bs3" onbeforesave="ctrl.changeProfileDetails(ctrl.profile, 'primaryContactName', $data)"
+ editable-text="ctrl.profile.primaryContactName">{{ ctrl.profile.primaryContactName || "None" }}</a>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td>Primary Contact Business email</td>
+ <td>
+ <div class="popover-wrapper">
+ <a editable-theme="bs3" onbeforesave="ctrl.changeProfileDetails(ctrl.profile, 'primaryBusinessEmail', $data)"
+ editable-email="ctrl.profile.primaryBusinessEmail">{{ ctrl.profile.primaryBusinessEmail || "None" }}</a>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td>Primary Contact Postal Address</td>
+ <td>
+ <div class="popover-wrapper">
+ <a editable-theme="bs3" onbeforesave="ctrl.changeProfileDetails(ctrl.profile, 'primaryPostalAddress', $data)"
+ editable-text="ctrl.profile.primaryPostalAddress">{{ ctrl.profile.primaryPostalAddress || "None" }}</a>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td>Primary Contact Phone Number</td>
+ <td>
+ <div class="popover-wrapper">
+ <a editable-theme="bs3" onbeforesave="ctrl.changeProfileDetails(ctrl.profile, 'primaryPhoneNumber', $data)"
+ editable-tel="ctrl.profile.primaryPhoneNumber">{{ ctrl.profile.primaryPhoneNumber || "None" }}</a>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/3rd_party/static/onap-ui/components/profile/profileController.js b/3rd_party/static/onap-ui/components/profile/profileController.js
new file mode 100644
index 0000000..1b84d76
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/profile/profileController.js
@@ -0,0 +1,68 @@
+/*
+ *
+ * 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('testapiApp')
+ .controller('ProfileController', ProfileController);
+
+ ProfileController.$inject = [
+ '$scope', '$state', '$http', 'testapiApiUrl'
+ ];
+
+ /**
+ * TestAPI Profile Controller
+ * This controller handles user's profile page, where a user can view
+ * account-specific information.
+ */
+ function ProfileController($scope, $state, $http, testapiApiUrl) {
+
+ var ctrl = this;
+
+ ctrl.changeProfileDetails = changeProfileDetails;
+
+ // Must be authenticated to view this page.
+ if (!$scope.auth.isAuthenticated) {
+ $state.go('home');
+ }
+
+ ctrl.authRequest = $scope.auth.doSignCheck();
+ ctrl.profile = $scope.auth.currentUser;
+
+ function changeProfileDetails(profile, key, newValue){
+ if (profile[key] === newValue) {
+ return;
+ }
+ var updateUrl = testapiApiUrl + "/profile";
+
+ var data = {};
+ data[key] = newValue;
+
+ $http.put(updateUrl, JSON.stringify(data), {
+ transformRequest: angular.identity,
+ headers: {'Content-Type': 'application/json'}}).then(function(ret) {
+ if (ret.data.code && ret.data.code != 0) {
+ alert(ret.data.msg);
+ } else {
+ profile[key] = newValue;
+ }
+ }, function(error) {
+ alert("Error when update data");
+ });
+ }
+ }
+})();
diff --git a/3rd_party/static/onap-ui/components/results-report/data/2019.04/heat-testcases.json b/3rd_party/static/onap-ui/components/results-report/data/2019.04/heat-testcases.json
new file mode 100644
index 0000000..1f584f4
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results-report/data/2019.04/heat-testcases.json
@@ -0,0 +1,13 @@
+{
+ "mandatory": {
+ "onap-vvp.validate.heat": {
+ "cases": [
+ "onap-vvp.validate.heat"
+ ],
+ "total": 1
+ }
+ },
+ "optional": {
+
+ }
+}
diff --git a/3rd_party/static/onap-ui/components/results-report/data/2019.04/tosca-testcases.json b/3rd_party/static/onap-ui/components/results-report/data/2019.04/tosca-testcases.json
new file mode 100644
index 0000000..de01e83
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results-report/data/2019.04/tosca-testcases.json
@@ -0,0 +1,13 @@
+{
+ "mandatory": {
+ "onap-vtp.validate.csar": {
+ "cases": [
+ "onap-vtp.validate.csar"
+ ],
+ "total": 1
+ }
+ },
+ "optional": {
+
+ }
+}
diff --git a/3rd_party/static/onap-ui/components/results-report/partials/reportDetails.html b/3rd_party/static/onap-ui/components/results-report/partials/reportDetails.html
new file mode 100644
index 0000000..3f3e9c9
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results-report/partials/reportDetails.html
@@ -0,0 +1,60 @@
+<!--
+HTML for each accordion group that separates the status types on the results
+report page.
+-->
+
+Test Filters:<br>
+<div class="btn-toolbar" role="toolbar">
+ <div class="btn-group button-margin" data-toggle="buttons">
+ <label class="btn btn-default" ng-click="ctrl.changeStatus('total')"
+ ng-class="{'active': ctrl.testStatus === 'total'}">
+ <input type="radio" ng-model="ctrl.testStatus" value="total" name="total">
+ <span class="text-primary">All</span>
+ </label>
+ <label class="btn btn-default" ng-click="ctrl.changeStatus('passed')"
+ ng-class="{'active': ctrl.testStatus === 'passed'}">
+ <input type="radio" ng-model="ctrl.testStatus" value="passed" name="passed">
+ <span class="text-success">Passed</span>
+ </label>
+ <label class="btn btn-default" ng-click="ctrl.changeStatus('not passed')"
+ ng-class="{'active': ctrl.testStatus === 'not passed'}">
+ <input type="radio" ng-model="ctrl.testStatus" value="not passed" name="not passed">
+ <span class="text-danger">Not Passed</span>
+ </label>
+ </div>
+ <div class="btn-group button-margin" style="float: right;">
+ <button type="button" class="btn btn-default" ng-click="ctrl.openAll()">Expand</button>
+ <button type="button" class="btn btn-default" ng-click="ctrl.folderAll()">Collapse</button>
+ </div>
+</div>
+
+<uib-accordion-group ng-repeat="(type,data) in ctrl.data" is-open="isOpen">
+ <uib-accordion-heading>
+ {{ type }}: {{ ctrl.statistics[type].total }} tests
+ <i class="pull-right glyphicon"
+ ng-class="{'glyphicon-chevron-down': isOpen, 'glyphicon-chevron-right': !isOpen}"></i>
+ </uib-accordion-heading>
+ <ol class="capabilities">
+ <li ng-repeat="(area, value) in data"
+ ng-show="(ctrl.testStatus == 'passed' && value.pass != 0) || (ctrl.testStatus == 'not passed' && value.fail != 0) || ctrl.testStatus == 'total'">
+ <a ng-click="value.folder = !value.folder">
+ {{ area }}
+ <span ng-if="ctrl.testStatus == 'total'"
+ ng-class="{'text-success': value.total == value.pass, 'text-warning': (value.pass < value.total && value.pass > 0), 'text-danger': value.pass == 0}">
+ [{{ value.pass }}/{{ value.total }}]
+ </span>
+ <span ng-if="ctrl.testStatus == 'passed'" class="text-success">[{{ value.pass }}]</span>
+ <span ng-if="ctrl.testStatus == 'not passed'" class="text-danger">[{{ value.fail }}]</span>
+ </a>
+ <a uib-tooltip="view log" ng-click="ctrl.gotoResultLog(area)"><span class="glyphicon glyphicon-cog"></span></a>
+ <ul class="list-unstyled" uib-collapse="value.folder">
+ <li ng-repeat="case in value.cases"
+ ng-if="(ctrl.testStatus=='passed' && ctrl.case_list.indexOf(case) > -1) || (ctrl.testStatus=='not passed' && ctrl.case_list.indexOf(case) == -1) || ctrl.testStatus=='total'">
+ <span ng-class="{'glyphicon glyphicon-ok text-success':ctrl.case_list.indexOf(case) > -1, 'glyphicon glyphicon-remove text-warning':ctrl.case_list.indexOf(case) == -1}"
+ aria-hidden="true"></span>
+ <a ng-click="ctrl.gotoDoc(case)">{{ case }}</a>
+ </li>
+ </ul>
+ </li>
+ </ol>
+</uib-accordion-group>
diff --git a/3rd_party/static/onap-ui/components/results-report/resultsReport.html b/3rd_party/static/onap-ui/components/results-report/resultsReport.html
new file mode 100644
index 0000000..38e602d
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results-report/resultsReport.html
@@ -0,0 +1,33 @@
+<div class="container-fluid common-main-container">
+ <h3>Test Run Results</h3>
+
+ <div ng-show="ctrl.testId" class="container-fluid">
+ <div class="row">
+ <div class="pull-left">
+ <div class="test-report">
+ <strong>ONAP Version:</strong> {{ctrl.version}}<br>
+ <strong>Test ID:</strong> {{ctrl.testId}}<br>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <strong>Total: {{ctrl.statistics.total}}, Pass: {{ ctrl.statistics.pass}}, Rate: {{ ctrl.statistics.pass / ctrl.statistics.total * 100 | number:2 }}%</strong><br>
+ <strong>Mandatory Total: {{ctrl.statistics.mandatory.total}}, Pass: {{ ctrl.statistics.mandatory.pass }}, Rate: {{ ctrl.statistics.mandatory.pass / ctrl.statistics.mandatory.total * 100 | number:2 }}%</strong><br>
+ <strong>Optional Total: {{ctrl.statistics.optional.total}}, Pass: {{ ctrl.statistics.optional.pass }}, Rate: {{ ctrl.statistics.optional.pass / ctrl.statistics.optional.total * 100 | number:2 }}%</strong><br>
+ <hr>
+ <strong>{{ ctrl.validation }}</strong><br>
+
+ <div>
+ <hr>
+ <h4>Test Result Overview</h4>
+ <uib-accordion close-others=false>
+ <!-- The ng-repeat is used to pass in a local variable to the template. -->
+ <ng-include
+ src="ctrl.detailsTemplate"
+ onload="isOpen = true">
+ </ng-include>
+ <br>
+ </uib-accordion>
+ </div>
+</div>
diff --git a/3rd_party/static/onap-ui/components/results-report/resultsReportController.js b/3rd_party/static/onap-ui/components/results-report/resultsReportController.js
new file mode 100644
index 0000000..09601ad
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results-report/resultsReportController.js
@@ -0,0 +1,224 @@
+/*
+ * 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('testapiApp')
+ .controller('ResultsReportController', ResultsReportController);
+
+ ResultsReportController.$inject = [
+ '$scope', '$http', '$stateParams', '$window',
+ 'testapiApiUrl'
+ ];
+
+ /**
+ * TestAPI Results Report Controller
+ * This controller is for the '/results/<test run ID>' page where a user can
+ * view details for a specific test run.
+ */
+ function ResultsReportController($scope, $http, $stateParams, $window,
+ testapiApiUrl) {
+
+ var ctrl = this;
+
+ ctrl.testStatus = 'total';
+ ctrl.case_list = [];
+ ctrl.data = {};
+ ctrl.statistics = {
+ 'total': 0, 'pass': 0, 'fail': 0,
+ 'mandatory': {'total': 0, 'pass': 0, 'fail': 0, 'area': 0},
+ 'optional': {'total': 0, 'pass': 0, 'fail': 0, 'area': 0}
+ };
+
+ ctrl.gotoDoc = gotoDoc;
+ ctrl.openAll = openAll;
+ ctrl.folderAll = folderAll;
+ ctrl.gotoResultLog = gotoResultLog;
+ ctrl.changeStatus = changeStatus;
+
+ /** The testID extracted from the URL route. */
+ ctrl.testId = $stateParams.testID;
+ ctrl.innerId = $stateParams.innerID;
+ ctrl.validation = '';
+ ctrl.vnf_type = '';
+ ctrl.vnf_checksum = '';
+ ctrl.version = '';
+
+ /** The HTML template that all accordian groups will use. */
+ ctrl.detailsTemplate = 'onap-ui/components/results-report/partials/' +
+ 'reportDetails.html';
+
+ $scope.load_finish = false;
+
+ function changeStatus(value) {
+ ctrl.testStatus = value;
+ }
+
+ function extend(case_list) {
+ angular.forEach(case_list, function(ele) {
+ ctrl.case_list.push(ele);
+ });
+ }
+
+ function strip(word) {
+ return word.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+ }
+
+ function gotoResultLog(case_name) {
+ var case_area = case_name.split(".")[0];
+ var log_url = "/logs/" + ctrl.testId + "/results/";
+ log_url += case_area + "_logs/" + case_name + ".out";
+ var is_reachable = false;
+
+ $.ajax({
+ url: log_url,
+ async: false,
+ success: function (response) {
+ is_reachable = true;
+ },
+ error: function (response) {
+ alert("Log file could not be found. Please confirm this case has been executed successfully.");
+ }
+ });
+
+ if (is_reachable == true) {
+ window.open(log_url);
+ }
+ }
+
+ $scope.$watch('load_finish', function() {
+ if ($scope.load_finish == true) {
+ var case_url = 'onap-ui/components/results-report/data/' + ctrl.version + '/' + ctrl.vnf_type + '-testcases.json'
+ $http.get(case_url).then(function(response) {
+ ctrl.data = response.data;
+
+ angular.forEach(ctrl.data.mandatory, function(value, name) {
+ ctrl.data.mandatory[name].folder = true;
+ ctrl.data.mandatory[name].pass = 0;
+ ctrl.data.mandatory[name].fail = 0;
+ angular.forEach(value.cases, function(sub_case) {
+ ctrl.statistics.total += 1;
+ ctrl.statistics.mandatory.total += 1;
+ if (ctrl.case_list.indexOf(sub_case) > -1) {
+ ctrl.data.mandatory[name].pass += 1;
+ ctrl.statistics.mandatory.pass += 1;
+ ctrl.statistics.pass += 1;
+ } else {
+ ctrl.data.mandatory[name].fail += 1;
+ ctrl.statistics.mandatory.fail += 1;
+ ctrl.statistics.fail += 1;
+ }
+ });
+ });
+
+ angular.forEach(ctrl.data.optional, function(value, name) {
+ ctrl.data.optional[name].folder = true;
+ ctrl.data.optional[name].pass = 0;
+ ctrl.data.optional[name].fail = 0;
+ angular.forEach(value.cases, function(sub_case) {
+ ctrl.statistics.total += 1;
+ ctrl.statistics.optional.total += 1;
+ if (ctrl.case_list.indexOf(sub_case) > -1) {
+ ctrl.data.optional[name].pass += 1;
+ ctrl.statistics.optional.pass += 1;
+ ctrl.statistics.pass += 1;
+ } else {
+ ctrl.data.optional[name].fail += 1;
+ ctrl.statistics.optional.fail += 1;
+ ctrl.statistics.fail += 1;
+ }
+
+ });
+ });
+
+ ctrl.statistics.mandatory.area = Object.keys(ctrl.data.mandatory).length;
+ ctrl.statistics.optional.area = Object.keys(ctrl.data.optional).length;
+ }, function(error) {
+ alert('error to get test case info');
+ });
+ }
+ });
+
+ function generate_format_data() {
+ var test_url = testapiApiUrl + '/onap/tests/' + ctrl.innerId;
+ $http.get(test_url).then(function(test_resp) {
+ ctrl.validation = test_resp.data.validation;
+
+ angular.forEach(test_resp.data.results, function(result, index) {
+ var result_url = testapiApiUrl + '/results/' + result;
+ $http.get(result_url).then(function(result_resp) {
+
+ ctrl.version = result_resp.data.version;
+ ctrl.vnf_type = result_resp.data.vnf_type;
+
+ angular.forEach(result_resp.data.testcases_list, function(testcase, index) {
+ var sub_case_list = get_sub_case_list_2019_04(testcase);
+ extend(sub_case_list);
+ });
+
+ if (index == test_resp.data.results.length - 1) {
+ $scope.load_finish = true;
+ }
+ }, function(result_error) {
+ /* do nothing */
+ });
+ });
+
+ }, function(test_error) {
+ alert('Error when get test record');
+ });
+ }
+
+ function get_sub_case_list_2019_04(result) {
+ var case_list = [];
+ if (result.sub_testcase.length == 0 && result.result == "PASS") {
+ case_list.push(result.name);
+ } else {
+ angular.forEach(result.sub_testcase, function(subtest, index) {
+ if (subtest.result == "PASS") {
+ case_list.push(subtest.name)
+ }
+ });
+ }
+ return case_list;
+ }
+
+ function gotoDoc(sub_case) {
+ /* not implemented */
+ }
+
+ function openAll() {
+ angular.forEach(ctrl.data.mandatory, function(ele, id) {
+ ele.folder = false;
+ });
+ angular.forEach(ctrl.data.optional, function(ele, id) {
+ ele.folder = false;
+ });
+ }
+
+ function folderAll() {
+ angular.forEach(ctrl.data.mandatory, function(ele, id) {
+ ele.folder = true;
+ });
+ angular.forEach(ctrl.data.optional, function(ele, id) {
+ ele.folder = true;
+ });
+ }
+
+ generate_format_data();
+ }
+
+})();
diff --git a/3rd_party/static/onap-ui/components/results/modal/applicationModal.html b/3rd_party/static/onap-ui/components/results/modal/applicationModal.html
new file mode 100644
index 0000000..0ca4b84
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results/modal/applicationModal.html
@@ -0,0 +1,158 @@
+<div class="container-fluid common-main-container">
+ <div class="top-site-banner">
+ <div class="container">
+ <p class="p1">Application Details</p>
+ </div>
+ </div>
+
+ <div class="row" style="margin-top: 20px;">
+ <div class="col-lg-12 container">
+ <p class="message" style="display: none;"></p>
+ <fieldset>
+ <div class="field text col-md-4">
+ <label class="left">Company Name</label>
+ <i uib-tooltip="Company Name" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input type="text" class="text form-control" ng-model="ctrl.company_name"
+ ng-init="ctrl.company_name=auth.currentUser.companyName" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Company Logo</label>
+ <i uib-tooltip="Required dimensions (Max Values): {width: 165pixels, height: 40pixels}" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input class="form-control btn btn-success-cust cvp-btn medium accent-color regular-button"
+ modal-file-model="logoFile" type="file" style="padding: 0;" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Company Website</label>
+ <i uib-tooltip="Company Website" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input type="url" class="text form-control" ng-model="ctrl.company_website"
+ ng-init="ctrl.company_website=auth.currentUser.companyWebsite" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Primary Contact Name</label>
+ <i uib-tooltip="Primary Contact name (optional)"
+ class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input type="text" class="text form-control" ng-model="ctrl.primary_contact_name"
+ ng-init="ctrl.primary_contact_name=auth.currentUser.primaryContactName" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Primary Contact Number</label>
+ <i uib-tooltip="Primary Contact phone number(optional)"
+ class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input type="tel" class="text form-control" ng-model="ctrl.primary_phone_number"
+ ng-init="ctrl.primary_phone_number=auth.currentUser.primaryPhoneNumber" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Primary Contact Email</label>
+ <i uib-tooltip="Primary Contact email (optional)"
+ class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input type="email" class="text form-control" ng-model="ctrl.primary_business_email"
+ ng-init="ctrl.primary_business_email=auth.currentUser.primaryBusinessEmail" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Test Agency</label>
+ <i uib-tooltip="Location" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <select class="form-control" ng-model="ctrl.lab_location" ng-init="ctrl.lab_location='internal'" required>
+ <option value="internal">1st Party - Submitter</option>
+ <option value="third">3rd Party - External Lab</option>
+ </select>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNF Version</label>
+ <i uib-tooltip="xNF Version"
+ class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input type="text" class="text form-control" ng-model="ctrl.xnf_version" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNF Name</label>
+ <i uib-tooltip="xNF Name"
+ class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input type="text" class="text form-control" ng-model="ctrl.xnf_name" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Test Type</label>
+ <i uib-tooltip="xNF Type" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <select class="form-control" ng-model="ctrl.xnf_type" ng-init="ctrl.xnf_type='VNF'" required>
+ <option value="VNF">VNF</option>
+ </select>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNF Description</label>
+ <i uib-tooltip="xNF Description"
+ class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input type="text" class="text form-control" ng-model="ctrl.xnf_description" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNFD Id</label>
+ <i uib-tooltip="xNFD Id" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <input type="text" class="text form-control" ng-model="ctrl.xnfd_id" required>
+ </div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Test Category</label>
+ <i uib-tooltip="Define test category<" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <div class="middleColumn">
+ <select class="form-control" ng-model="ctrl.xnf_test_period" ng-init="ctrl.xnf_test_period='Packaging Compliance Test'" required>
+ <option value="Packaging Compliance Test">Packaging Compliance Test</option>
+ <option value="Lifecycle">Lifecycle Test</option>
+ <option value="Functional">Functional Test</option>
+ <option value="Performance">Performance Test</option>
+ </select>
+ </div>
+ </div>
+ <div ng-if="ctrl.lab_location=='third'" class="field text">
+ <div class="field text col-md-4">
+ <label class="left">Lab Name</label>
+ <i uib-tooltip="Lab Name" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <input type="text" class="text form-control" ng-model="ctrl.lab_name" required>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Lab Email</label>
+ <i uib-tooltip="Lab Email" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <input type="email" class="text form-control" ng-model="ctrl.lab_email" required>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Lab Address</label>
+ <i uib-tooltip="Lab Address" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <input type="text" class="text form-control" ng-model="ctrl.lab_address" required>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Lab Phone Number</label>
+ <i uib-tooltip="Lab Phone Number" class="glyphicon glyphicon-question-sign opnfv-blue"></i>
+ <input type="tel" class="text form-control" ng-model="ctrl.lab_phone" required>
+ </div>
+ </div>
+ </fieldset>
+ </div>
+ </div>
+ <button class="btn btn-default"
+ ng-click="ctrl.openConfirmModal(ctrl.tempResult)">Submit</button>
+</div>
+
+<style type="text/css">
+ .ngdialog.custom-background .ngdialog-content {
+ background: #ffffff;
+ }
+</style>
diff --git a/3rd_party/static/onap-ui/components/results/modal/applicationView.html b/3rd_party/static/onap-ui/components/results/modal/applicationView.html
new file mode 100644
index 0000000..79341f8
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results/modal/applicationView.html
@@ -0,0 +1,103 @@
+<div class="container-fluid common-main-container">
+ <div class="top-site-banner">
+ <div class="container">
+ <p class="p1">Application Details</p>
+ </div>
+ </div>
+
+ <div class="row" style="margin-top: 20px;">
+ <div class="col-lg-12 container">
+ <p class="message" style="display: none;"></p>
+ <fieldset>
+ <div class="field text col-md-4">
+ <label class="left">Company Name</label>
+ <div class="middleColumn">{{ ctrl.application.company_name }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Company Logo</label>
+ <div class="middleColumn">{{ ctrl.application.company_logo }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Company Website</label>
+ <div class="middleColumn">{{ ctrl.application.company_website }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">ONAP Version</label>
+ <div class="middleColumn">{{ ctrl.application.onap_version }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Primary Contact Name</label>
+ <div class="middleColumn">{{ ctrl.application.primary_contact_name }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Primary Contact Number</label>
+ <div class="middleColumn">{{ ctrl.application.primary_phone_number }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Primary Contact Email</label>
+ <div class="middleColumn">{{ ctrl.application.primary_business_email }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Test Agency</label>
+ <div class="middleColumn">{{ ctrl.application.lab_location }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNF Version</label>
+ <div class="middleColumn">{{ ctrl.application.xnf_version }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNF Name</label>
+ <div class="middleColumn">{{ ctrl.application.xnf_name }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Test Type</label>
+ <div class="middleColumn">{{ ctrl.application.xnf_type }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNF Description</label>
+ <div class="middleColumn">{{ ctrl.application.xnf_description }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNFD Id</label>
+ <div class="middleColumn">{{ ctrl.application.xnfd_id }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNF Model Language</label>
+ <div class="middleColumn">{{ ctrl.application.xnfd_model_lang }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Test Category</label>
+ <div class="middleColumn">{{ ctrl.application.xnf_test_period }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">xNF Checksum (SHA256)</label>
+ <div class="middleColumn">{{ ctrl.application.xnf_checksum }}</div>
+ </div>
+ <div ng-if="ctrl.lab_location=='third'" class="field text">
+ <div class="field text col-md-4">
+ <label class="left">Lab Name</label>
+ <div>{{ ctrl.application.lab_name }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Lab Email</label>
+ <div>{{ ctrl.application.lab_email }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Lab Address</label>
+ <div>{{ ctrl.application.lab_address }}</div>
+ </div>
+ <div class="field text col-md-4">
+ <label class="left">Lab Phone Number</label>
+ <div>{{ ctrl.application.lab_phone }}</div>
+ </div>
+ </div>
+ </fieldset>
+ </div>
+ </div>
+</div>
+
+<style type="text/css">
+ .ngdialog.custom-background .ngdialog-content {
+ background: #ffffff;
+ }
+</style>
diff --git a/3rd_party/static/onap-ui/components/results/modal/reviewsModal.html b/3rd_party/static/onap-ui/components/results/modal/reviewsModal.html
new file mode 100644
index 0000000..c93d1ef
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results/modal/reviewsModal.html
@@ -0,0 +1,41 @@
+<div class="container-fluid common-main-container">
+ <div class="top-site-banner">
+ <div class="container">
+ <p class="p1">Community Reviews</p>
+ </div>
+ </div>
+
+ <div class="row" style="margin-top: 20px;">
+ <div class="col-lg-12 container">
+ <div cg-busy="{promise:ctrl.reviewsRequest,message:'Loading'}"></div>
+ <div ng-show="ctrl.reviews" class="results-table" style="width: 100%; overflow-x: scroll;">
+ <table ng-data="ctrl.reviews" ng-show="ctrl.reviews" class="table table-striped table-hover">
+ <thead>
+ <tr>
+ <th>Reviewer</th>
+ <th>Linux Foundation OpenId</th>
+ <th>Email</th>
+ <th>Review Date</th>
+ <th>Outcome</th>
+ </tr>
+ </thead>
+ <tbody style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
+ <tr ng-repeat="(index, review) in ctrl.reviews">
+ <td>{{ review.reviewer_name }}</td>
+ <td>{{ review.reviewer_openid }}</td>
+ <td>{{ review.reviewer_email }}</td>
+ <td>{{ review.creation_date | limitTo:19}}</td>
+ <td>{{ review.outcome }}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+</div>
+
+<style type="text/css">
+ .ngdialog.custom-background .ngdialog-content {
+ background: #ffffff;
+ }
+</style>
diff --git a/3rd_party/static/onap-ui/components/results/modal/sharedModal.html b/3rd_party/static/onap-ui/components/results/modal/sharedModal.html
new file mode 100644
index 0000000..021a355
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results/modal/sharedModal.html
@@ -0,0 +1,16 @@
+<div>
+ <h4>Enter user name or email</h4>
+ <input type="text" ng-model="ctrl.userName">
+ <div style="text-align: center; margin-top: 20px;">
+ <button class="btn btn-default" ng-disabled="ctrl.userName==null || ctrl.userName==''"
+ ng-click="ctrl.addSharedUser(ctrl.tempResult, ctrl.userName)">Commit</button>
+ </div>
+</div>
+
+<style>
+ input {
+ border-radius: 10px;
+ border: 1px solid #eeeeee;
+ width: 100%;
+ }
+</style>
diff --git a/3rd_party/static/onap-ui/components/results/results.html b/3rd_party/static/onap-ui/components/results/results.html
new file mode 100644
index 0000000..ce43036
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results/results.html
@@ -0,0 +1,168 @@
+<div class="container-fluid common-main-container">
+ <h3>{{ctrl.pageHeader}}</h3>
+ <p>{{ctrl.pageParagraph}}</p>
+ <form class="form-inline" ng-show="ctrl.isUserResults">
+ <h4>Upload Results
+ <i class="glyphicon glyphicon-question-sign opnfv-blue"
+ uib-tooltip="results file is logs.xxx.tar.gz under your dovetail installation path"></i>
+ </h4>
+ <div class="form-group col-m-3">
+ <input class="form-contrl btn btn-success-cust cvp-btn medium accent-color regular-button" type="file"
+ file-model="resultFile">
+ </div>
+ <div class="form-group col-m-3">
+ <a class="btn btn-success-cust cvp-btn medium accent-color regular-button" ng-click="ctrl.uploadFile()">
+ <span>upload result</span>
+ </a>
+ </div>
+ <div>
+ <label>{{ctrl.uploadState}}</label>
+ </div>
+ </form>
+ <div class="row" style="margin-bottom: 24px;"></div>
+ <div cg-busy="{promise:ctrl.authRequest,message:'Loading'}"></div>
+ <div cg-busy="{promise:ctrl.resultsRequest,message:'Loading'}"></div>
+
+ <div ng-show="ctrl.data" class="results-table" style="width: 100%; overflow-x: scroll;">
+ <table ng-data="ctrl.data.result" ng-show="ctrl.data" class="table table-striped table-hover">
+ <thead>
+ <tr>
+ <th>Upload Date</th>
+ <th>Test ID</th>
+ <th>ONAP Version</th>
+ <th>Modeling Language</th>
+ <th>Owner</th>
+ <th>File Name</th>
+ <th>Label</th>
+ <th>Status</th>
+ <th>Log</th>
+ <th>Application</th>
+ <th>Review Status</th>
+ <th class="col-md-2">Operation</th>
+ <th class="col-md-2">Share List</th>
+ </tr>
+ </thead>
+
+ <tbody style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
+ <tr ng-repeat="(index, result) in ctrl.data.tests">
+ <td>{{ result.upload_date | limitTo:19}}</td>
+ <td>
+ <a uib-tooltip="{{ result.id }}" tooltip-placement="top" tooltip-append-to-body="true"
+ ng-click="ctrl.gotoResultDetail(result.id, result._id)">{{ result.id | limitTo:8 }}</a>
+ </td>
+ <td>{{ result.version || "2019.04" }}</td>
+ <td>{{ result.vnf_type.toUpperCase() }}</td>
+ <td>{{ result.owner }}</td>
+ <td>{{ result.filename || "None"}}</td>
+ <td>
+ <div class="popover-wrapper">
+ <a editable-theme="bs3" onbeforesave="ctrl.changeLabel(result, 'label', $data)"
+ editable-text="result.label">{{ result.label || "None" }}</a>
+ </div>
+ </td>
+ <td>{{ result.status }}</td>
+ <td><a ng-click="ctrl.downloadLogs(result.id)">logs</a></td>
+ <td>
+ <a ng-if="result.status !='private'" ng-click="ctrl.openApplicationView(result)">View Application</a>
+ <div ng-if="result.status == 'private'">Not created</div>
+ </td>
+ <td><a ng-if="result.status !='private'" ng-click="ctrl.openReviewsModal(result.id)">View Reviews</a>
+ <div ng-if="result.status == 'private'"></div>
+ </td>
+ <td>
+ <div class="btn-group" uib-dropdown>
+ <a id="single-button" type="button" class="btn btn-success-cust cvp-btn medium accent-color regular-button"
+ uib-dropdown-toggle>
+ Operation<span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button">
+ <li role="menuitem" ng-if="auth.currentUser.openid == result.owner && ctrl.isUserResults"
+ class="menu-item menu-item-type-post_type menu-item-object-page">
+ <a ng-class="{'hide': result.status != 'review'}"
+ ng-click="ctrl.deleteApplication(result)">withdraw submit</a>
+ </li>
+ <li role="menuitem" ng-if="auth.currentUser.openid == result.owner && ctrl.isUserResults"
+ class="menu-item menu-item-type-post_type menu-item-object-page">
+ <a ng-class="{'hide': result.status != 'private'}"
+ ng-click="ctrl.openApplicationModal(result)">submit to review</a>
+ </li>
+ <li role="menuitem"
+ ng-if="auth.currentUser.role.indexOf('reviewer') != -1 && !ctrl.isUserResults"
+ class="menu-item menu-item-type-post_type menu-item-object-page">
+ <a ng-class="{'hide': (result.voted == 'true') || (result.status != 'review')}"
+ ng-click="ctrl.toApprove(result)">approve</a>
+ </li>
+ <li role="menuitem"
+ ng-if="auth.currentUser.role.indexOf('reviewer') != -1 && !ctrl.isUserResults"
+ class="menu-item menu-item-type-post_type menu-item-object-page">
+ <a ng-class="{'hide': (result.voted == 'true') || (result.status != 'review')}"
+ ng-click="ctrl.toDisapprove(result)">not approve</a>
+ </li>
+ <li role="menuitem"
+ ng-if="auth.currentUser.role.indexOf('reviewer') != -1 && !ctrl.isUserResults"
+ class="menu-item menu-item-type-post_type menu-item-object-page">
+ <a ng-class="{'hide': (result.voted == 'false') || (result.status != 'review')}"
+ ng-click="ctrl.toUndo(result)">undo</a>
+ </li>
+ <li role="menuitem" ng-if="auth.currentUser.openid == result.owner && ctrl.isUserResults"
+ class="menu-item menu-item-type-post_type menu-item-object-page">
+ <a ng-click="ctrl.openSharedModal(result)">share with</a>
+ </li>
+ <li role="menuitem" ng-if="auth.currentUser.openid == result.owner && ctrl.isUserResults"
+ class="menu-item menu-item-type-post_type menu-item-object-page">
+ <a ng-click="ctrl.deleteTest(result._id)">delete</a>
+ </li>
+ </ul>
+ </div>
+ </td>
+ <td>
+ <div class="btn-group" uib-dropdown>
+ <a id="single-button-two" type="button"
+ class="btn btn-success-cust cvp-btn medium accent-color regular-button" style="width: 130px;"
+ uib-dropdown-toggle>
+ Share List<span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button-two"
+ style="min-width: 200%;">
+ <li class="menu-item menu-item-type-post_type menu-item-object-page" role="menuitem"
+ ng-repeat="share in result.shared track by $index">
+ <span>
+ {{ share }}
+ <i ng-if="auth.currentUser.openid == result.owner" class="pull-right glyphicon glyphicon-remove"
+ ng-click="ctrl.removeSharedUser(result, share)"></i>
+ </span>
+ </li>
+ </ul>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div class="pages">
+ <uib-pagination
+ total-items="ctrl.totalItems"
+ ng-model="ctrl.currentPage"
+ items-per-page="ctrl.itemsPerPage"
+ max-size="ctrl.maxSize"
+ class="pagination-sm"
+ boundary-links="true"
+ rotate="false"
+ num-pages="ctrl.numPages"
+ ng-change="ctrl.update()">
+ </uib-pagination>
+ </div>
+ </div>
+</div>
+
+<div ng-show="ctrl.showError" class="alert alert-danger" role="alert">
+ <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+ <span class="sr-only">Error:</span>
+ {{ctrl.error}}
+</div>
+
+
+<style>
+ .button-disabled {
+ pointer-events: none;
+ }
+</style>
diff --git a/3rd_party/static/onap-ui/components/results/resultsController.js b/3rd_party/static/onap-ui/components/results/resultsController.js
new file mode 100644
index 0000000..d459495
--- /dev/null
+++ b/3rd_party/static/onap-ui/components/results/resultsController.js
@@ -0,0 +1,614 @@
+/*
+ * 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('testapiApp')
+ .controller('ResultsController', ResultsController);
+
+ angular
+ .module('testapiApp')
+ .directive('fileModel', ['$parse', function ($parse) {
+ return {
+ restrict: 'A',
+ link: function(scope, element, attrs) {
+ var model = $parse(attrs.fileModel);
+ var modelSetter = model.assign;
+
+ element.bind('change', function(){
+ scope.$apply(function(){
+ modelSetter(scope, element[0].files[0]);
+ });
+ });
+ }
+ };
+ }]);
+
+ angular
+ .module('testapiApp')
+ .directive('modalFileModel', ['$parse', function ($parse) {
+ return {
+ restrict: 'A',
+ link: function(scope, element, attrs) {
+ var model = $parse(attrs.modalFileModel);
+ var modelSetter = model.assign;
+
+ element.bind('change', function(){
+ scope.$apply(function(){
+ modelSetter(scope.$parent, element[0].files[0]);
+ });
+ });
+ }
+ };
+ }]);
+
+ ResultsController.$inject = [
+ '$scope', '$http', '$filter', '$state', 'testapiApiUrl','raiseAlert', 'ngDialog', '$resource'
+ ];
+
+ /**
+ * TestAPI Results Controller
+ * This controller is for the '/results' page where a user can browse
+ * a listing of community uploaded results.
+ */
+ function ResultsController($scope, $http, $filter, $state, testapiApiUrl, raiseAlert, ngDialog, $resource) {
+ var ctrl = this;
+
+ ctrl.uploadFile=uploadFile;
+ ctrl.update = update;
+ ctrl.open = open;
+ ctrl.clearFilters = clearFilters;
+ ctrl.associateMeta = associateMeta;
+ ctrl.gotoResultDetail = gotoResultDetail;
+ ctrl.toggleCheck = toggleCheck;
+ ctrl.changeLabel = changeLabel;
+ ctrl.toApprove = toApprove;
+ ctrl.toDisapprove = toDisapprove;
+ ctrl.toUndo = toUndo;
+ ctrl.toReview = toReview;
+ ctrl.toPrivate = toPrivate;
+ ctrl.removeSharedUser = removeSharedUser;
+ ctrl.addSharedUser = addSharedUser;
+ ctrl.openSharedModal = openSharedModal;
+ ctrl.downloadLogs = downloadLogs;
+ ctrl.deleteApplication = deleteApplication;
+ ctrl.deleteTest = deleteTest;
+ ctrl.openApplicationModal = openApplicationModal;
+ ctrl.openApplicationView = openApplicationView;
+ ctrl.submitApplication = submitApplication;
+ ctrl.openConfirmModal = openConfirmModal;
+ ctrl.openReviewsModal = openReviewsModal;
+
+ /** Mappings of Interop WG components to marketing program names. */
+ ctrl.targetMappings = {
+ 'platform': 'Openstack Powered Platform',
+ 'compute': 'OpenStack Powered Compute',
+ 'object': 'OpenStack Powered Object Storage'
+ };
+
+ /** Initial page to be on. */
+ ctrl.currentPage = 1;
+
+ /**
+ * How many results should display on each page. Since pagination
+ * is server-side implemented, this value should match the
+ * 'results_per_page' configuration of the TestAPI server which
+ * defaults to 20.
+ */
+ ctrl.itemsPerPage = 20;
+
+ /**
+ * How many page buttons should be displayed at max before adding
+ * the '...' button.
+ */
+ ctrl.maxSize = 5;
+
+ /** The upload date lower limit to be used in filtering results. */
+ ctrl.startDate = '';
+
+ /** The upload date upper limit to be used in filtering results. */
+ ctrl.endDate = '';
+
+ /** The date format for the date picker. */
+ ctrl.format = 'yyyy-MM-dd';
+
+ ctrl.userName = null;
+
+ /** Check to see if this page should display user-specific results. */
+ ctrl.isUserResults = $state.current.name === 'userResults';
+
+ /** Check to see if this page should display community results. */
+ ctrl.isReviewer = $scope.auth.currentUser.role.indexOf('reviewer') != -1;
+ ctrl.isAdministrator = $scope.auth.currentUser.role.indexOf('administrator') != -1;
+
+ ctrl.currentUser = $scope.auth.currentUser ? $scope.auth.currentUser.openid : null;
+
+ // Should only be on user-results-page if authenticated.
+ if (!$scope.auth.isAuthenticated) {
+ $state.go('home');
+ }
+ // Should only be on community-results if reviewer
+ if (!ctrl.isUserResults && !ctrl.isReviewer) {
+ $state.go('home');
+ }
+
+ ctrl.pageHeader = ctrl.isUserResults ?
+ 'Private test results' : 'Community test results';
+
+ ctrl.pageParagraph = ctrl.isUserResults ?
+ 'Your most recently uploaded test results are listed here.' :
+ 'The most recently uploaded community test results are listed ' +
+ 'here.';
+
+ ctrl.uploadState = '';
+
+ ctrl.authRequest = $scope.auth.doSignCheck().then(ctrl.update);
+
+ function downloadLogs(id) {
+ // var logsUrl = testapiApiUrl + "/logs/log_" + id+".tar.gz";
+ var logsUrl = "/logs/" + id + "/results/";
+ window.location.href = logsUrl;
+ // $http.get(logsUrl);
+ }
+
+ function deleteTest(inner_id) {
+ var resp = confirm('Are you sure to delete this test?');
+ if (!resp)
+ return;
+
+ var delUrl = testapiApiUrl + "/onap/tests/" + inner_id;
+ $http.get(delUrl)
+ .then( function(resp) {
+ var results = resp.data.results;
+ $http.delete(delUrl)
+ .then( function(ret) {
+ if(ret.data.code && ret.data.code != 0) {
+ alert(ret.data.msg);
+ return;
+ }
+ ctrl.update();
+ angular.forEach(results, function(ele) {
+ delUrl = testapiApiUrl + "/results/" + ele;
+ $http.delete(delUrl);
+ });
+ });
+ });
+ }
+
+ function deleteApplication (result) {
+ var resp = confirm('Are you sure you want to delete this application?');
+ if (!resp)
+ return;
+
+ $http.get(testapiApiUrl + "/onap/cvp/applications?test_id=" + result.id).then(function(response) {
+ ctrl.application = response.data.applications[0];
+ var app_id = ctrl.application._id;
+ var delUrl = testapiApiUrl + "/cvp/applications/" + app_id;
+ $http.delete(delUrl)
+ .then(function(ret) {
+ if (ret.data.code && ret.data.code != 0) {
+ alert(ret.data.msg);
+ return;
+ }
+ result['status'] = 'private';
+ });
+
+ }, function(error) {
+ /* do nothing */
+ });
+
+ }
+
+ function submitApplication(result) {
+ var file = $scope.logoFile;
+ var logo_name = null;
+ if (typeof file !== 'undefined') {
+
+ var fd = new FormData();
+ fd.append('file', file);
+ fd.append('company_name', ctrl.company_name)
+
+ $http.post(testapiApiUrl + "/cvp/applications/uploadlogo", fd, {
+ transformRequest: angular.identity,
+ headers: {'Content-Type': undefined}
+ }).then(function(resp) {
+ if (resp.data.code && resp.data.code != 0) {
+ alert(resp.data.msg);
+ return;
+ } else {
+ logo_name = resp.data.filename;
+ var data = {
+ "description": ctrl.description,
+ "onap_version": result.version,
+ "company_name": ctrl.company_name,
+ "company_logo": logo_name,
+ "company_website": ctrl.company_website,
+ "approve_date": "",
+ "approved": "false",
+ "test_id": result.id,
+ "lab_location": ctrl.lab_location,
+ "lab_email": ctrl.lab_email,
+ "lab_address": ctrl.lab_address,
+ "lab_phone": ctrl.lab_phone,
+ "xnf_version": ctrl.xnf_version,
+ "certification_type": ctrl.certification_type,
+ "xnf_name": ctrl.xnf_name,
+ "xnf_type": ctrl.xnf_type,
+ "xnf_description": ctrl.xnf_description,
+ "xnfd_id": ctrl.xnfd_id,
+ "xnfd_model_lang": result.vnf_type.toUpperCase(),
+ "xnf_checksum": result.vnf_checksum,
+ "xnf_test_period": ctrl.xnf_test_period,
+ "primary_contact_name": ctrl.primary_contact_name,
+ "primary_phone_number": ctrl.primary_phone_number,
+ "primary_business_email": ctrl.primary_business_email
+ };
+
+ $http.post(testapiApiUrl + "/onap/cvp/applications", data).then(function(resp) {
+ if (resp.data.code && resp.data.code != 0) {
+ alert(resp.data.msg);
+ return;
+ }
+ toggleCheck(result, 'status', 'review');
+ }, function(error) {
+ /* do nothing */
+ });
+ }
+ }, function(error) {
+ /* do nothing */
+ });
+ logo_name = file.name;
+ }
+ ngDialog.close();
+ }
+
+ function openConfirmModal(result) {
+ var resp = confirm("Are you sure to submit?");
+ if (resp) {
+ ctrl.submitApplication(result);
+ }
+ }
+
+ function openApplicationModal(result) {
+ ctrl.tempResult = result;
+ ngDialog.open({
+ preCloseCallback: function(value) {
+ },
+ template: 'onap-ui/components/results/modal/applicationModal.html',
+ scope: $scope,
+ className: 'ngdialog-theme-default custom-background',
+ width: 950,
+ showClose: true,
+ closeByDocument: true
+ });
+ }
+
+ function openApplicationView(result) {
+
+ $http.get(testapiApiUrl + "/onap/cvp/applications?test_id=" + result.id).then(function(response) {
+ ctrl.application = response.data.applications[0];
+ }, function(error) {
+ /* do nothing */
+ });
+
+ ctrl.tempResult = result;
+ ngDialog.open({
+ preCloseCallback: function(value) {
+ },
+ template: 'onap-ui/components/results/modal/applicationView.html',
+ scope: $scope,
+ className: 'ngdialog-theme-default custom-background',
+ width: 950,
+ showClose: true,
+ closeByDocument: true
+ });
+ }
+
+ function getReviews(test) {
+ var reviews_url = testapiApiUrl + '/onap/reviews?test_id=' + test;
+ ctrl.reviewsRequest =
+ $http.get(reviews_url).success(function (data) {
+ ctrl.reviews = data.reviews;
+ }).error(function (error) {
+ ctrl.reviews = null;
+ });
+ }
+
+ function openReviewsModal(test) {
+ getReviews(test);
+ ngDialog.open({
+ preCloseCallback: function(value) {
+ },
+ template: 'onap-ui/components/results/modal/reviewsModal.html',
+ scope: $scope,
+ className: 'ngdialog-theme-default custom-background',
+ width: 950,
+ showClose: true,
+ closeByDocument: true
+ });
+ }
+
+ function toggleCheck(result, item, newValue) {
+ var id = result._id;
+ var updateUrl = testapiApiUrl + "/onap/tests/"+ id;
+
+ var data = {};
+ data['item'] = item;
+ data[item] = newValue;
+
+ $http.put(updateUrl, JSON.stringify(data), {
+ transformRequest: angular.identity,
+ headers: {'Content-Type': 'application/json'}}).then(function(ret) {
+ if(ret.data.code && ret.data.code != 0) {
+ alert(ret.data.msg);
+ } else {
+ result[item] = newValue;
+ }
+ }, function(error) {
+ alert('Error when update data');
+ });
+ }
+
+ function changeLabel(result, key, data){
+ if (result[key] !== data) {
+ toggleCheck(result, key, data);
+ }
+ }
+
+ function doReview(test, outcome) {
+ var createUrl = testapiApiUrl + "/onap/reviews";
+ var data = {
+ 'test_id': test.id,
+ 'outcome': outcome
+ };
+
+ $http.post(createUrl, JSON.stringify(data), {
+ transformRequest: angular.identity,
+ headers: {'Content-Type': 'application/json'}}).then(function(ret) {
+ if (ret.data.code && ret.data.code != 0) {
+ alert(ret.data.msg);
+ } else {
+ if (outcome === null) {
+ test.voted = 'false';
+ } else {
+ test.voted = 'true';
+ }
+ }
+ }, function(error) {
+ alert('Error when creating review');
+ });
+ }
+
+ function toApprove(test) {
+ var resp = confirm('Once you approve a test result, your action will become visible. Do you want to proceed?');
+ if (resp) {
+ doReview(test, 'positive');
+ }
+ }
+
+ function toDisapprove(test) {
+ var resp = confirm('Once you disapprove a test result, your action will become visible. Do you want to proceed?');
+ if (resp) {
+ doReview(test, 'negative');
+ }
+ }
+
+ function toUndo(test) {
+ var resp = confirm('Once you undo your previous vote, your action will become visible. Do you want to proceed?');
+ if (resp) {
+ doReview(test, null);
+ }
+ }
+
+ function toReview(result, value){
+ var resp = confirm('Once you submit a test result for review, it will become readable to all ONAPVP reviewers. Do you want to proceed?');
+ if(resp){
+ toggleCheck(result, 'status', value);
+ }
+ }
+
+ function toPrivate(result, value){
+ var resp = confirm('Do you want to proceed?');
+ if(resp){
+ toggleCheck(result, 'status', value);
+ }
+ }
+
+ function openSharedModal(result){
+ ctrl.tempResult = result;
+ ngDialog.open({
+ preCloseCallback: function(value) {
+ },
+ template: 'onap-ui/components/results/modal/sharedModal.html',
+ scope: $scope,
+ className: 'ngdialog-theme-default',
+ width: 950,
+ showClose: true,
+ closeByDocument: true
+ });
+ }
+
+ function addSharedUser(result, userId){
+ var tempList = copy(result.shared);
+ tempList.push(userId);
+ toggleCheck(result, 'shared', tempList);
+ ngDialog.close();
+ }
+
+ function removeSharedUser(result, userId){
+ var tempList = copy(result.shared);
+ var idx = tempList.indexOf(userId);
+ if(idx != -1){
+ tempList.splice(idx, 1);
+ toggleCheck(result, 'shared', tempList);
+ }
+ }
+
+ function copy(arrList){
+ var tempList = [];
+ angular.forEach(arrList, function(ele){
+ tempList.push(ele);
+ });
+ return tempList;
+ }
+
+ function uploadFileToUrl(file, uploadUrl){
+ var fd = new FormData();
+ fd.append('file', file);
+
+ $http.post(uploadUrl, fd, {
+ transformRequest: angular.identity,
+ headers: {'Content-Type': undefined}
+ }).then(function(data){
+
+ if(data.data.code && data.data.code != 0){
+ alert(data.data.msg);
+ return;
+ }
+
+ ctrl.uploadState = "";
+ data.data.filename = file.name;
+ var createTestUrl = testapiApiUrl + "/onap/tests"
+
+ $http.post(createTestUrl, data.data).then(function(data){
+ if (data.data.code && data.data.code != 0) {
+ alert(data.data.msg);
+ } else {
+ ctrl.update();
+ }
+ }, function(error){
+ });
+
+ }, function(error){
+ ctrl.uploadState = "Upload failed. Error code is " + error.status;
+ });
+ }
+
+ function uploadFile(){
+ var file = $scope.resultFile;
+
+ var uploadUrl = testapiApiUrl + "/onap/results/upload";
+ uploadFileToUrl(file, uploadUrl);
+ };
+
+ /**
+ * This will contact the TestAPI API to get a listing of test run
+ * results.
+ */
+ function update() {
+ ctrl.showError = false;
+ // Construct the API URL based on user-specified filters.
+ var content_url = testapiApiUrl + '/onap/tests';
+ var start = $filter('date')(ctrl.startDate, 'yyyy-MM-dd');
+ var end = $filter('date')(ctrl.endDate, 'yyyy-MM-dd');
+
+ content_url += '?page=' + ctrl.currentPage;
+ content_url += '&per_page=' + ctrl.itemsPerPage;
+ if (start) {
+ content_url += '&from=' + start + ' 00:00:00';
+ }
+ if (end) {
+ content_url += '&to=' + end + ' 23:59:59';
+ }
+ if (ctrl.isUserResults) {
+ content_url += '&signed';
+ } else {
+ content_url += '&status={"$ne":"private"}&review';
+ }
+
+ ctrl.resultsRequest =
+ $http.get(content_url).success(function (data) {
+ ctrl.data = data;
+ ctrl.totalItems = ctrl.data.pagination.total_pages * ctrl.itemsPerPage;
+ ctrl.currentPage = ctrl.data.pagination.current_page;
+ ctrl.numPages = ctrl.data.pagination.total_pages;
+ }).error(function (error) {
+ ctrl.data = null;
+ ctrl.totalItems = 0;
+ ctrl.showError = true;
+ ctrl.error =
+ 'Error retrieving results listing from server: ' +
+ angular.toJson(error);
+ });
+ }
+
+ /**
+ * This is called when the date filter calendar is opened. It
+ * does some event handling, and sets a scope variable so the UI
+ * knows which calendar was opened.
+ * @param {Object} $event - The Event object
+ * @param {String} openVar - Tells which calendar was opened
+ */
+ function open($event, openVar) {
+ $event.preventDefault();
+ $event.stopPropagation();
+ ctrl[openVar] = true;
+ }
+
+ /**
+ * This function will clear all filters and update the results
+ * listing.
+ */
+ function clearFilters() {
+ ctrl.startDate = null;
+ ctrl.endDate = null;
+ ctrl.update();
+ }
+
+ /**
+ * This will send an API request in order to associate a metadata
+ * key-value pair with the given testId
+ * @param {Number} index - index of the test object in the results list
+ * @param {String} key - metadata key
+ * @param {String} value - metadata value
+ */
+ function associateMeta(index, key, value) {
+ var testId = ctrl.data.results[index].id;
+ var metaUrl = [
+ testapiApiUrl, '/results/', testId, '/meta/', key
+ ].join('');
+
+ var editFlag = key + 'Edit';
+ if (value) {
+ ctrl.associateRequest = $http.post(metaUrl, value)
+ .success(function () {
+ ctrl.data.results[index][editFlag] = false;
+ }).error(function (error) {
+ raiseAlert('danger', error.title, error.detail);
+ });
+ }
+ else {
+ ctrl.unassociateRequest = $http.delete(metaUrl)
+ .success(function () {
+ ctrl.data.results[index][editFlag] = false;
+ }).error(function (error) {
+ if (error.code == 404) {
+ // Key doesn't exist, so count it as a success,
+ // and don't raise an alert.
+ ctrl.data.results[index][editFlag] = false;
+ }
+ else {
+ raiseAlert('danger', error.title, error.detail);
+ }
+ });
+ }
+ }
+
+ function gotoResultDetail(testId, innerID) {
+ $state.go('resultsDetail', {'testID': testId, 'innerID': innerID});
+ }
+ }
+})();
diff --git a/3rd_party/static/onap-ui/config.json b/3rd_party/static/onap-ui/config.json
new file mode 100644
index 0000000..ab9c32d
--- /dev/null
+++ b/3rd_party/static/onap-ui/config.json
@@ -0,0 +1 @@
+{"testapiApiUrl": "/api/v1"}
diff --git a/3rd_party/static/onap-ui/favicon.ico b/3rd_party/static/onap-ui/favicon.ico
new file mode 100644
index 0000000..bbcf83c
--- /dev/null
+++ b/3rd_party/static/onap-ui/favicon.ico
Binary files differ
diff --git a/3rd_party/static/onap-ui/index.html b/3rd_party/static/onap-ui/index.html
new file mode 100644
index 0000000..55ba933
--- /dev/null
+++ b/3rd_party/static/onap-ui/index.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<!--
+ 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 id="ng-app">
+ <head>
+ <meta charset="utf-8">
+ <meta name="description" content="TestAPI">
+ <meta name="viewport" content="width=device-width">
+ <title>ONAP</title>
+
+ <link rel="icon" type="image/png" href="onap-ui/favicon.ico" sizes="16x16">
+ <link rel="icon" type="image/png" href="onap-ui/favicon.ico" sizes="32x32">
+
+ <!-- CSS Libraries -->
+ <link rel="stylesheet" href="onap-ui/node_modules/bootstrap/dist/css/bootstrap.min.css">
+ <link rel="stylesheet" href="onap-ui/node_modules/angular-busy/dist/angular-busy.min.css">
+ <link rel="stylesheet" href="onap-ui/node_modules/ng-dialog/css/ngDialog.min.css">
+ <link rel="stylesheet" href="onap-ui/node_modules/ng-dialog/css/ngDialog-theme-default.min.css">
+ <link rel="stylesheet" href="onap-ui/node_modules/angular-xeditable/dist/css/xeditable.min.css">
+
+ <!-- CSS Internal Code -->
+ <link rel="stylesheet" href="onap-ui/assets/css/cvp-style.css">
+ <link rel="stylesheet" href="onap-ui/assets/css/ascend.css">
+ <link rel="stylesheet" href="onap-ui/assets/css/index.css">
+ <link rel="stylesheet" href="onap-ui/assets/css/header.css">
+ <link rel="stylesheet" href="onap-ui/assets/css/home/home.css">
+ <link rel="stylesheet" href="onap-ui/assets/css/combine.css">
+
+ <!-- JS Libraries -->
+ <script src="onap-ui/node_modules/jquery/dist/jquery.min.js"></script>
+ <script src="onap-ui/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
+ <script src="onap-ui/node_modules/angular/angular.min.js"></script>
+ <script src="onap-ui/node_modules/angular-ui-router/release/angular-ui-router.min.js"></script>
+ <script src="onap-ui/node_modules/angular-resource/angular-resource.min.js"></script>
+ <script src="onap-ui/node_modules/angular-ui-bootstrap/ui-bootstrap-tpls.min.js"></script>
+ <script src="onap-ui/node_modules/angular-busy/dist/angular-busy.min.js"></script>
+ <script src="onap-ui/node_modules/angular-confirm/angular-confirm.min.js"></script>
+ <script src="onap-ui/node_modules/ng-dialog/js/ngDialog.min.js"></script>
+ <script src="onap-ui/node_modules/angular-xeditable/dist/js/xeditable.min.js"></script>
+
+ <!-- Application Root -->
+ <script src="onap-ui/app.js"></script>
+
+ <!-- Controllers -->
+ <script src="onap-ui/shared/header/headerController.js"></script>
+ <script src="onap-ui/shared/alerts/alertModalFactory.js"></script>
+ <script src="onap-ui/components/home/homeController.js"></script>
+ <script src="onap-ui/components/directory/directoryController.js"></script>
+ <script src="onap-ui/components/results/resultsController.js"></script>
+ <script src="onap-ui/components/results-report/resultsReportController.js"></script>
+ <script src="onap-ui/components/application/applicationController.js"></script>
+ <script src="onap-ui/components/profile/profileController.js"></script>
+ <script src="onap-ui/components/auth-failure/authFailureController.js"></script>
+ <script src="onap-ui/components/logout/logoutController.js"></script>
+
+ <!-- Filters -->
+ <script src="onap-ui/shared/filters.js"></script>
+
+ </head>
+
+ <body class="container-fluid home page-template-default page page-id-6 do-etfw tribe-no-js ascend wpb-js-composer
+ js-comp-ver-5.2.1 vc_responsive index-header"
+ ng-controller="HeaderController as header">
+ <header ng-include src="'onap-ui/shared/header/header.html'"></header>
+ <div ui-view></div>
+ <footer ng-include src="'onap-ui/shared/footer/footer.html'"></footer>
+ </body>
+</html>
diff --git a/3rd_party/static/onap-ui/package.json b/3rd_party/static/onap-ui/package.json
new file mode 100644
index 0000000..128d354
--- /dev/null
+++ b/3rd_party/static/onap-ui/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "onap-ui",
+ "version": "2.0.0",
+ "description": "Frontend Angular Application for ONAP Verified Program",
+ "main": "app.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://git.opnfv.org/dovetail-webportal.git"
+ },
+ "keywords": [
+ "ONAP"
+ ],
+ "author": "Stamatis Katsaounis",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://jira.opnfv.org/projects/DOVETAIL/issues/"
+ },
+ "homepage": "https://wiki.opnfv.org/display/dovetail/Dovetail+Home",
+ "dependencies": {
+ "angular": "1.3.15",
+ "angular-animate": "1.3.20",
+ "angular-busy": "4.1.3",
+ "angular-confirm": "1.2.3",
+ "angular-mocks": "1.3.15",
+ "angular-resource": "1.3.15",
+ "angular-ui-bootstrap": "0.14.3",
+ "angular-ui-router": "0.2.13",
+ "angular-xeditable": "0.8.0",
+ "bootstrap": "3.3.2",
+ "jquery": "3.2.1",
+ "ng-dialog": "1.4.0"
+ }
+}
diff --git a/3rd_party/static/onap-ui/robots.txt b/3rd_party/static/onap-ui/robots.txt
new file mode 100644
index 0000000..93c4420
--- /dev/null
+++ b/3rd_party/static/onap-ui/robots.txt
@@ -0,0 +1,4 @@
+# robotstxt.org
+
+User-agent: *
+
diff --git a/3rd_party/static/onap-ui/shared/alerts/alertModal.html b/3rd_party/static/onap-ui/shared/alerts/alertModal.html
new file mode 100644
index 0000000..5d1a097
--- /dev/null
+++ b/3rd_party/static/onap-ui/shared/alerts/alertModal.html
@@ -0,0 +1,8 @@
+<div class="modal-body" style="padding:0px">
+ <div class="alert alert-{{alert.data.mode}}" style="margin-bottom:0px">
+ <button type="button" class="close" data-ng-click="alert.close()" >
+ <span class="glyphicon glyphicon-remove-circle"></span>
+ </button>
+ <strong>{{alert.data.title}}</strong> {{alert.data.text}}
+ </div>
+</div>
diff --git a/3rd_party/static/onap-ui/shared/alerts/alertModalFactory.js b/3rd_party/static/onap-ui/shared/alerts/alertModalFactory.js
new file mode 100644
index 0000000..49dd5fa
--- /dev/null
+++ b/3rd_party/static/onap-ui/shared/alerts/alertModalFactory.js
@@ -0,0 +1,74 @@
+/*
+ * 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('testapiApp')
+ .factory('raiseAlert', raiseAlert);
+
+ raiseAlert.$inject = ['$uibModal'];
+
+ /**
+ * This allows alert pop-ups to be raised. Just inject it as a dependency
+ * in the calling controller.
+ */
+ function raiseAlert($uibModal) {
+ return function(mode, title, text) {
+ $uibModal.open({
+ templateUrl: 'onap-ui/shared/alerts/alertModal.html',
+ controller: 'RaiseAlertModalController as alert',
+ backdrop: true,
+ keyboard: true,
+ backdropClick: true,
+ size: 'md',
+ resolve: {
+ data: function () {
+ return {
+ mode: mode,
+ title: title,
+ text: text
+ };
+ }
+ }
+ });
+ };
+ }
+
+ angular
+ .module('testapiApp')
+ .controller('RaiseAlertModalController', RaiseAlertModalController);
+
+ RaiseAlertModalController.$inject = ['$uibModalInstance', 'data'];
+
+ /**
+ * This is the controller for the alert pop-up.
+ */
+ function RaiseAlertModalController($uibModalInstance, data) {
+ var ctrl = this;
+
+ ctrl.close = close;
+ ctrl.data = data;
+
+ /**
+ * This method will close the alert modal. The modal will close
+ * when the user clicks the close button or clicks outside of the
+ * modal.
+ */
+ function close() {
+ $uibModalInstance.close();
+ }
+ }
+})();
diff --git a/3rd_party/static/onap-ui/shared/filters.js b/3rd_party/static/onap-ui/shared/filters.js
new file mode 100644
index 0000000..538c02e
--- /dev/null
+++ b/3rd_party/static/onap-ui/shared/filters.js
@@ -0,0 +1,100 @@
+/*
+ * 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';
+
+ /**
+ * Convert an object of objects to an array of objects to use with ng-repeat
+ * filters.
+ */
+ angular
+ .module('testapiApp')
+ .filter('arrayConverter', arrayConverter);
+
+ /**
+ * Convert an object of objects to an array of objects to use with ng-repeat
+ * filters.
+ */
+ function arrayConverter() {
+ return function (objects) {
+ var array = [];
+ angular.forEach(objects, function (object, key) {
+ if (!('id' in object)) {
+ object.id = key;
+ }
+ array.push(object);
+ });
+ return array;
+ };
+ }
+
+ angular
+ .module('testapiApp')
+ .filter('capitalize', capitalize);
+
+ /**
+ * Angular filter that will capitalize the first letter of a string.
+ */
+ function capitalize() {
+ return function (string) {
+ return string.substring(0, 1).toUpperCase() + string.substring(1);
+ };
+ }
+
+ angular
+ .module('testapiApp')
+ .filter('tagExtractor', tagExtractor);
+
+ function tagExtractor() {
+ return function (string) {
+ return string.substring(13, string.indexOf('dovetail')-1);
+ };
+ }
+
+ angular
+ .module('testapiApp')
+ .filter('checkFlag', checkFlag);
+
+ function checkFlag() {
+ return function (string) {
+ return string == undefined || string == "true";
+ };
+ }
+
+ angular
+ .module('testapiApp')
+ .filter('category', category);
+
+ function category() {
+ return function (string) {
+ if (string == "soft&hard")
+ return "software and hardware";
+ return "software and third party hardware";
+ };
+ }
+
+ angular
+ .module('testapiApp')
+ .filter('labLocation', labLocation);
+
+ function labLocation() {
+ return function (string) {
+ if (string == "internal")
+ return "internal vendor lab";
+ return "third-party lab";
+ };
+ }
+
+})();
diff --git a/3rd_party/static/onap-ui/shared/footer/footer.html b/3rd_party/static/onap-ui/shared/footer/footer.html
new file mode 100644
index 0000000..c0f1b70
--- /dev/null
+++ b/3rd_party/static/onap-ui/shared/footer/footer.html
@@ -0,0 +1,3 @@
+<div>
+ <span class="hide">version: web.cvp.0.7.0</span>
+</div>
diff --git a/3rd_party/static/onap-ui/shared/header/header.html b/3rd_party/static/onap-ui/shared/header/header.html
new file mode 100644
index 0000000..3a87a73
--- /dev/null
+++ b/3rd_party/static/onap-ui/shared/header/header.html
@@ -0,0 +1,48 @@
+<div class="header-container-1">
+ <div class="row header-container-row">
+ <div class="col-md-3">
+ <a href="/#/">
+ <img class="stnd dark-version header-logo" alt="ONAP" src="onap-ui/assets/img/logo.png">
+ </a>
+ </div>
+ <div class="col-md-offset-1 header-title">
+ <span class="header-title">ONAP Verified Program</span>
+ </div>
+ </div>
+</div>
+
+<div class="header-container-2">
+ <div class="row header-container-row">
+ <div>
+ <ul class="nav navbar-nav navbar-right header-login">
+ <li ng-class="{ active: header.isActive('/application')}"
+ ng-if="auth.isAuthenticated && auth.currentUser.role.indexOf('administrator') != -1">
+ <a ui-sref="application">Applications</a>
+ </li>
+ <li ng-class="{ active: header.isActive('/community_results')}"
+ ng-if="auth.isAuthenticated && auth.canReview(auth.currentUser)">
+ <a ui-sref="communityResults">Incoming Reviews</a>
+ </li>
+ <li ng-class="{ active: header.isActive('/user_results')}"
+ ng-if="auth.isAuthenticated && auth.currentUser.role.indexOf('user') != -1">
+ <a ui-sref="userResults">My Results</a>
+ </li>
+ <li ng-class="{ active: header.isActive('/profile')}" ng-if="auth.isAuthenticated">
+ <a ui-sref="profile">Profile</a>
+ </li>
+ <li ng-if="auth.isAuthenticated" style="margin-right:10px">
+ <a href="" ng-click="auth.doSignOut()">Sign Out</a>
+ </li>
+ <li ng-if="!auth.isAuthenticated" style="margin-right:10px;">
+ <a href="" ng-click="auth.doSignIn('cas')">
+ <span class="glyphicon glyphicon-user" aria-hidden="true">&nbsp;</span>Sign In / Sign Up
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+</div>
+
+<div class="container-fluid header-splitline">
+ <div class="row header-container-row"></div>
+</div>
diff --git a/3rd_party/static/onap-ui/shared/header/headerController.js b/3rd_party/static/onap-ui/shared/header/headerController.js
new file mode 100644
index 0000000..0a14a41
--- /dev/null
+++ b/3rd_party/static/onap-ui/shared/header/headerController.js
@@ -0,0 +1,63 @@
+/*
+ * 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('testapiApp')
+ .controller('HeaderController', HeaderController);
+
+ HeaderController.$inject = ['$location'];
+
+ /**
+ * TestAPI Header Controller
+ * This controller is for the header template which contains the site
+ * navigation.
+ */
+ function HeaderController($location) {
+ var ctrl = this;
+
+ ctrl.isActive = isActive;
+ ctrl.isCatalogActive = isCatalogActive;
+
+ /** Whether the Navbar is collapsed for small displays. */
+ ctrl.navbarCollapsed = true;
+
+ /**
+ * This determines whether a button should be in the active state based
+ * on the URL.
+ */
+ function isActive(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;
+ }
+
+ /** This determines the active state for the catalog dropdown. Type
+ * parameter should be passed in to specify if the catalog is the
+ * public or user one.
+ */
+ function isCatalogActive(type) {
+ return ctrl.isActive('/' + type + '_vendors')
+ || ctrl.isActive('/' + type + '_products');
+ }
+ }
+})();
diff --git a/docker/.gitignore b/docker/.gitignore
index af398fe..5ef677d 100644
--- a/docker/.gitignore
+++ b/docker/.gitignore
@@ -1,2 +1,3 @@
config.env
-vhost.env
+vhost-opnfv.env
+vhost-onap.env
diff --git a/docker/Dockerfile.api b/docker/Dockerfile.api
index d40562a..016fa33 100644
--- a/docker/Dockerfile.api
+++ b/docker/Dockerfile.api
@@ -44,14 +44,16 @@ RUN apt-get update && apt-get install -y \
libssl-dev \
libxml2-dev \
libxslt1-dev \
+ libjpeg-dev \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
RUN pip install -U setuptools
-RUN git clone https://gerrit.opnfv.org/gerrit/dovetail-webportal $HOME/testapi && \
- cd $HOME/testapi && \
- git checkout -f $BRANCH && \
+RUN git init $HOME/testapi && \
+ (cd $HOME/testapi && \
+ git fetch --tags https://gerrit.opnfv.org/gerrit/dovetail-webportal $BRANCH && \
+ git checkout FETCH_HEAD) && \
mkdir -p $HOME/testapi/logs/api && \
mkdir -p $HOME/testapi/media/companies
diff --git a/docker/Dockerfile.web b/docker/Dockerfile.web
index 853e0f8..e5c39a1 100644
--- a/docker/Dockerfile.web
+++ b/docker/Dockerfile.web
@@ -10,6 +10,8 @@ MAINTAINER Leo Wang <grakiss.wanglei@huawei.com>
LABEL version="v2" description="OVP nginx"
ARG BRANCH=master
+ARG GUI=testapi-ui
+ARG CONTAINER=opnfv
ENV HOME /home
WORKDIR $HOME
@@ -22,16 +24,17 @@ RUN apt-get update && apt-get install -y \
npm \
&& rm -rf /var/lib/apt/lists/*
-RUN git clone https://gerrit.opnfv.org/gerrit/dovetail-webportal $HOME/testapi && \
- cd $HOME/testapi && \
- git checkout -f $BRANCH && \
- cd $HOME/testapi/3rd_party/static/testapi-ui && \
+RUN git init $HOME/testapi && \
+ (cd $HOME/testapi && \
+ git fetch --tags https://gerrit.opnfv.org/gerrit/dovetail-webportal $BRANCH && \
+ git checkout FETCH_HEAD) && \
+ cd $HOME/testapi/3rd_party/static/$GUI && \
npm install && \
- mkdir /www && \
- cp -r $HOME/testapi/3rd_party/static /www/
+ mkdir -p /www/static && \
+ cp -r $HOME/testapi/3rd_party/static/$GUI /www/static
ADD nginx/nginx.conf /etc/nginx/nginx.conf
-ADD nginx/sites-available/default /etc/nginx/sites-available/default
+ADD nginx/sites-available/default-$CONTAINER /etc/nginx/sites-available/default
ADD supervisor/conf.d/nginx.conf /etc/supervisor/conf.d/nginx.conf
ADD start-nginx.sh $HOME/start-nginx.sh
diff --git a/docker/config.env.sample b/docker/config.env.sample
index 003d92f..f824c22 100644
--- a/docker/config.env.sample
+++ b/docker/config.env.sample
@@ -1,3 +1,4 @@
mongodb_url=mongodb://mongodb:27017/
base_url=http://ovp.localhost
-testapi_url=cvpapi:8010
+testapi_url=lfnapi:8010
+PYTHONUNBUFFERED=True
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 4edf4cc..15e28f7 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -3,47 +3,62 @@ version: '3'
services:
mongodb:
image: mongo:3.2.1
- container_name: cvp-db
+ container_name: lfn-db
volumes:
- - cvp-db:/data/db
+ - lfn-db:/data/db
expose:
- "27017"
- web:
+ webopnfv:
image: opnfv/dovetail-webportal-web:latest
- container_name: cvp-web
+ container_name: web-opnfv
restart: always
env_file:
- config.env
- - vhost.env
+ - vhost-opnfv.env
volumes:
- - cvp-testapi-logs:/home/testapi/logs
+ - lfn-testapi-logs:/home/testapi/logs
links:
- mongodb
- - cvpapi
+ - lfnapi
ports:
- "8000:8000"
- cvpapi:
+ webonap:
+ image: opnfv/dovetail-webportal-web-onap:latest
+ container_name: web-onap
+ restart: always
+ env_file:
+ - config.env
+ - vhost-onap.env
+ volumes:
+ - lfn-testapi-logs:/home/testapi/logs
+ links:
+ - mongodb
+ - lfnapi
+ ports:
+ - "8001:8000"
+ lfnapi:
image: opnfv/dovetail-webportal-api:latest
- container_name: cvp-cvpapi
+ container_name: lfn-api
env_file:
- config.env
volumes:
- - cvp-testapi-logs:/home/testapi/logs
- - cvp-company-logos:/home/testapi/media/companies
+ - lfn-testapi-logs:/home/testapi/logs
+ - lfn-company-logos:/home/testapi/media/companies
ports:
- "8010:8010"
nginx:
image: jwilder/nginx-proxy
- container_name: cvp-nginx
+ container_name: lfn-nginx
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./nginx-proxy/custom_proxy_settings.conf:/etc/nginx/conf.d/custom_proxy_settings.conf
depends_on:
- - web
+ - webopnfv
+ - webonap
ports:
- "80:80"
volumes:
- cvp-db:
- cvp-company-logos:
- cvp-testapi-logs:
+ lfn-db:
+ lfn-company-logos:
+ lfn-testapi-logs:
diff --git a/docker/nginx/sites-available/default-onap b/docker/nginx/sites-available/default-onap
new file mode 100644
index 0000000..e6aecb1
--- /dev/null
+++ b/docker/nginx/sites-available/default-onap
@@ -0,0 +1,64 @@
+upstream lfnapi {
+ server lfnapi:8010;
+}
+
+server {
+ listen 8000 default_server;
+ listen [::]:8000 default_server ipv6only=on;
+
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+
+ server_name localhost;
+
+ location ~* /onap-ui/ {
+ root /www/static;
+ expires 1d;
+ }
+
+ location ~* /logs/.*\.(log|out|yaml|yml|txt|conf|json|sh|)$ {
+ root /home/testapi;
+ add_header Content-Type text/plain;
+ }
+
+ location ~* /logs/.*/results {
+ root /home/testapi;
+ expires 1d;
+ autoindex on;
+ autoindex_exact_size off;
+ autoindex_localtime on;
+ }
+
+ location ~* /logs/api {
+ root /home/testapi;
+ expires 1d;
+ autoindex on;
+ autoindex_exact_size on;
+ autoindex_localtime on;
+ }
+
+ location = /api/v1/onap/results/upload {
+ client_max_body_size 20m;
+ proxy_pass http://lfnapi/api/v1/onap/results/upload;
+ proxy_set_header X-Real_IP $remote_addr;
+ proxy_set_header Host $host;
+ }
+
+ location /api/v1/ {
+ proxy_pass http://lfnapi/api/v1/;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ }
+
+ location / {
+ root /www/static/onap-ui;
+ expires 1d;
+ }
+
+ error_page 413 =200 /413.json;
+
+ location /413.json {
+ return 200 '{"msg": "Please upload a file less than 20MB.", "code": 413}';
+ }
+
+}
diff --git a/docker/nginx/sites-available/default b/docker/nginx/sites-available/default-opnfv
index 7652eb1..f271fb4 100644
--- a/docker/nginx/sites-available/default
+++ b/docker/nginx/sites-available/default-opnfv
@@ -1,5 +1,5 @@
-upstream cvpapi {
- server cvpapi:8010;
+upstream lfnapi {
+ server lfnapi:8010;
}
server {
@@ -38,50 +38,50 @@ server {
}
location /api/v1/cvp {
- proxy_pass http://cvpapi/api/v1/cvp;
+ proxy_pass http://lfnapi/api/v1/cvp;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
location /api/v1/auth {
- proxy_pass http://cvpapi/api/v1/auth;
+ proxy_pass http://lfnapi/api/v1/auth;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
location /api/v1/profile {
- proxy_pass http://cvpapi/api/v1/profile;
+ proxy_pass http://lfnapi/api/v1/profile;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
location /api/v1/test {
- proxy_pass http://cvpapi/api/v1/test;
+ proxy_pass http://lfnapi/api/v1/test;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
location = /api/v1/results {
- proxy_pass http://cvpapi/api/v1/results;
+ proxy_pass http://lfnapi/api/v1/results;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
location ~* /api/v1/results/([a-zA-Z0-9]+) {
client_max_body_size 20m;
- proxy_pass http://cvpapi/api/v1/results/$1;
+ proxy_pass http://lfnapi/api/v1/results/$1;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header Host $host;
}
location ~* /api/v1/suts/hardware/([a-zA-Z0-9\-]+) {
- proxy_pass http://cvpapi/api/v1/suts/hardware/$1;
+ proxy_pass http://lfnapi/api/v1/suts/hardware/$1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
location /api/v1/ {
- proxy_pass http://cvpapi/api/v1/;
+ proxy_pass http://lfnapi/api/v1/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
diff --git a/docker/prepare-env.sh b/docker/prepare-env.sh
index 61ff233..762e741 100755
--- a/docker/prepare-env.sh
+++ b/docker/prepare-env.sh
@@ -11,5 +11,4 @@ if [ "$base_url" != "" ]; then
sudo crudini --set --existing $FILE swagger base_url $base_url
sudo crudini --set --existing $FILE ui url $base_url
sudo crudini --set --existing $FILE jira OAUTH_CALLBACK_URL $base_url/api/v1/auth/signin_return_jira
- sudo crudini --set --existing $FILE lfid return_url $base_url/api/v1/auth/signin_return_cas
fi
diff --git a/docker/start-nginx.sh b/docker/start-nginx.sh
index c9949be..5f33847 100755
--- a/docker/start-nginx.sh
+++ b/docker/start-nginx.sh
@@ -1,8 +1,8 @@
#!/bin/bash
-FILE=/etc/nginx/sites-enabled/default
+NGINX_CONF=/etc/nginx/sites-enabled/default
if [ "$testapi_url" != "" ]; then
- sed -i "s/server localhost:8010/server $testapi_url/" $FILE
+ sed -i "s/server lfnapi:8010/server $testapi_url/" $NGINX_CONF
fi
service supervisor start
diff --git a/etc/config.ini b/etc/config.ini
index 13e6de5..0bcae0c 100644
--- a/etc/config.ini
+++ b/etc/config.ini
@@ -92,4 +92,4 @@ OAUTH_CALLBACK_URL = http://localhost:9999/api/v1/auth/signin_return_jira
[lfid]
url = https://identity.linuxfoundation.org/cas/
-return_url = http://localhost:9999/api/v1/auth/signin_return_cas
+return_url = api/v1/auth/signin_return_cas
diff --git a/opnfv_testapi/cmd/server.py b/opnfv_testapi/cmd/server.py
index fee5877..5dcc60a 100644
--- a/opnfv_testapi/cmd/server.py
+++ b/opnfv_testapi/cmd/server.py
@@ -31,6 +31,7 @@ TODOs :
import tornado.ioloop
import logging
+import sys
from opnfv_testapi.common.config import CONF
from opnfv_testapi.router import url_mappings
@@ -42,12 +43,19 @@ handler = logging.handlers.RotatingFileHandler(
my_logger.setLevel(logging.DEBUG)
my_logger.addHandler(handler)
+ch = logging.StreamHandler(sys.stdout)
+ch.setLevel(logging.DEBUG)
+formatter = logging.Formatter(
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ch.setFormatter(formatter)
+my_logger.addHandler(ch)
+
def make_app():
swagger.docs(base_url=CONF.swagger_base_url,
static_path=CONF.ui_static_path)
return swagger.Application(
- url_mappings.mappings,
+ url_mappings.mappings + url_mappings.onap_mappings,
debug=CONF.api_debug,
auth=CONF.api_authenticate,
cookie_secret='opnfv-testapi',
diff --git a/opnfv_testapi/resources/application_handlers.py b/opnfv_testapi/resources/application_handlers.py
index 7cecd3e..7d823b8 100644
--- a/opnfv_testapi/resources/application_handlers.py
+++ b/opnfv_testapi/resources/application_handlers.py
@@ -6,15 +6,20 @@
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
+from datetime import datetime
import logging
import json
+import os
from tornado import web
from tornado import gen
from bson import objectid
+from slugify import slugify
+from PIL import Image
from opnfv_testapi.common.config import CONF
from opnfv_testapi.common import utils
+from opnfv_testapi.db import api as dbapi
from opnfv_testapi.resources import handlers
from opnfv_testapi.resources import application_models
from opnfv_testapi.tornado_swagger import swagger
@@ -34,19 +39,28 @@ class ApplicationsLogoHandler(GenericApplicationHandler):
@web.asynchronous
@gen.coroutine
def post(self):
- role = self.get_secure_cookie(auth_const.ROLE)
- if role.find('administrator') == -1:
- msg = 'Only administrator is allowed to upload logos'
- self.finish_request({'code': '-1', 'msg': msg})
- return
-
fileinfo = self.request.files['file'][0]
- fname = fileinfo['filename']
+ company_logo_name = self.request.arguments['company_name'][0]
+ extension_name = fileinfo['filename'].split('.')[-1]
+ company_logo_name = slugify(company_logo_name)
+ fileinfo['filename'] = company_logo_name
location = 'media/companies/'
- fh = open(location + fname, 'w')
+ full_name_path = location + company_logo_name + '.' + extension_name
+ fh = open(full_name_path, 'w')
fh.write(fileinfo['body'])
- msg = 'Successfully uploaded logo: ' + fname
- resp = {'code': '1', 'msg': msg}
+ fh.close()
+ img = Image.open(full_name_path)
+ if (img.size[0] > 165) or (img.size[1] > 40):
+ os.remove(full_name_path)
+ msg = 'The size of the image is not according to the compliance' \
+ ' program. Please try again, loading an image with proper' \
+ ' dimensions (Max Values: 165px width and 40px height).'
+ self.finish_request({'code': 403, 'msg': msg})
+ return
+
+ msg = 'Successfully uploaded logo: ' + company_logo_name
+ resp = {'code': 0, 'msg': msg,
+ 'filename': company_logo_name + '.' + extension_name}
self.finish_request(resp)
@@ -130,6 +144,8 @@ class ApplicationsCLHandler(GenericApplicationHandler):
openid = self.get_secure_cookie(auth_const.OPENID)
if openid:
self.json_args['owner'] = openid
+ if self.is_onap:
+ self.json_args['is_onap'] = 'true'
self._post()
@@ -138,22 +154,21 @@ class ApplicationsCLHandler(GenericApplicationHandler):
miss_fields = []
carriers = []
- role = self.get_secure_cookie(auth_const.ROLE)
- if role.find('administrator') == -1:
- self.finish_request({'code': '403', 'msg': 'Only administrator \
- is allowed to submit application.'})
- return
-
- query = {"openid": self.json_args['user_id']}
- table = "users"
- ret, msg = yield self._check_if_exists(table=table, query=query)
+ query = {'openid': self.json_args['owner']}
+ ret, msg = yield self._check_if_exists(table='users', query=query)
logging.debug('ret:%s', ret)
if not ret:
- self.finish_request({'code': '403', 'msg': msg})
+ self.finish_request({'code': 403, 'msg': msg})
+ return
+ query = {'test_id': self.json_args['test_id']}
+ ret, _ = yield self._check_if_exists(table=self.table, query=query)
+ if ret:
+ msg = 'An application for these test results already exists'
+ self.finish_request({'code': 403, 'msg': msg})
return
self._create(miss_fields=miss_fields, carriers=carriers)
- self._send_email()
+ # self._send_email()
def _send_email(self):
@@ -173,7 +188,6 @@ This is a new application:
Primary Email: {},
Primary Address: {},
Primary Phone: {},
- User ID Type: {},
User ID: {}
Best Regards,
@@ -188,19 +202,26 @@ CVP Team
data.prim_email,
data.prim_address,
data.prim_phone,
- data.id_type,
- data.user_id)
+ data.owner)
utils.send_email(subject, content)
class ApplicationsGURHandler(GenericApplicationHandler):
@swagger.operation(nickname="deleteAppById")
+ @gen.coroutine
def delete(self, id):
query = {'_id': objectid.ObjectId(id)}
+ application = yield dbapi.db_find_one(self.table, query)
+ test_id = application['test_id']
+ t_query = {'id': test_id}
+ yield dbapi.db_delete('reviews', {'test_id': test_id})
+ yield dbapi.db_update('tests', t_query,
+ {'$set': {'status': 'private'}})
self._delete(query=query)
@swagger.operation(nickname="updateApplicationById")
+ @web.asynchronous
def put(self, application_id):
"""
@description: update a single application by id
@@ -222,12 +243,23 @@ class ApplicationsGURHandler(GenericApplicationHandler):
logging.error('except:%s', e)
return
- @web.asynchronous
@gen.coroutine
def update(self, application_id, item, value):
self.json_args = {}
self.json_args[item] = value
- query = {'_id': application_id, 'owner':
+ query = {'_id': objectid.ObjectId(application_id), 'owner':
self.get_secure_cookie(auth_const.OPENID)}
db_keys = ['_id', 'owner']
+ if item == 'approved':
+ if value == 'true':
+ status = 'verified'
+ self.json_args['approve_date'] = str(datetime.now())
+ else:
+ status = 'review'
+ self.json_args['approve_date'] = ''
+ application = yield dbapi.db_find_one(self.table, query)
+ test_id = application['test_id']
+ t_query = {'id': test_id}
+ yield dbapi.db_update('tests', t_query,
+ {'$set': {'status': status}})
self._update(query=query, db_keys=db_keys)
diff --git a/opnfv_testapi/resources/handlers.py b/opnfv_testapi/resources/handlers.py
index e8c81f3..559e689 100644
--- a/opnfv_testapi/resources/handlers.py
+++ b/opnfv_testapi/resources/handlers.py
@@ -41,6 +41,7 @@ DEFAULT_REPRESENTATION = "application/json"
class GenericApiHandler(web.RequestHandler):
def __init__(self, application, request, **kwargs):
+ self.is_onap = False
super(GenericApiHandler, self).__init__(application, request, **kwargs)
self.json_args = None
self.table = None
@@ -52,6 +53,9 @@ class GenericApiHandler(web.RequestHandler):
self.db_scenarios = 'scenarios'
self.auth = self.settings["auth"]
+ def initialize(self, is_onap=False):
+ self.is_onap = is_onap
+
def get_int(self, key, value):
try:
value = int(value)
@@ -97,6 +101,11 @@ class GenericApiHandler(web.RequestHandler):
if role.find("reviewer") != -1:
query['$or'].append({"status": {"$ne": "private"}})
+ elif k == 'status':
+ if v.startswith('{'):
+ query[k] = json.loads(v)
+ else:
+ query[k] = v
elif k not in ['last', 'page', 'descend', 'per_page']:
query[k] = v
if date_range:
@@ -107,6 +116,8 @@ class GenericApiHandler(web.RequestHandler):
if 'start_date' in query and '$lt' not in query['start_date']:
query['start_date'].update({'$lt': str(datetime.now())})
+ query['is_onap'] = 'true' if self.is_onap else None
+
logging.debug("query:%s", query)
raise gen.Return((query))
@@ -184,7 +195,7 @@ class GenericApiHandler(web.RequestHandler):
if query and table:
data = yield dbapi.db_find_one(table, query)
if data:
- raise gen.Return((True, 'Data alreay exists. %s' % (query)))
+ raise gen.Return((True, 'Data already exists. %s' % (query)))
raise gen.Return((False, 'Data does not exist. %s' % (query)))
# @web.asynchronous
@@ -214,7 +225,7 @@ class GenericApiHandler(web.RequestHandler):
if res_op is None:
res = {self.table: data}
else:
- res = res_op(data, *args)
+ res = yield res_op(data, *args)
if page > 0:
res.update({
'pagination': {
diff --git a/opnfv_testapi/resources/result_handlers.py b/opnfv_testapi/resources/result_handlers.py
index 38109ad..9501bfd 100644
--- a/opnfv_testapi/resources/result_handlers.py
+++ b/opnfv_testapi/resources/result_handlers.py
@@ -20,6 +20,7 @@ from bson import objectid
from opnfv_testapi.common.config import CONF
from opnfv_testapi.common import message
from opnfv_testapi.common import raises
+from opnfv_testapi.db import api as dbapi
from opnfv_testapi.resources import handlers
from opnfv_testapi.resources import result_models
from opnfv_testapi.tornado_swagger import swagger
@@ -41,6 +42,7 @@ class GenericResultHandler(handlers.GenericApiHandler):
raises.BadRequest(message.must_int(key))
return value
+ @gen.coroutine
def set_query(self):
query = dict()
date_range = dict()
@@ -82,11 +84,15 @@ class GenericResultHandler(handlers.GenericApiHandler):
if 'start_date' in query and '$lt' not in query['start_date']:
query['start_date'].update({'$lt': str(datetime.now())})
- return query
+ query['is_onap'] = 'true' if self.is_onap else None
+
+ raise gen.Return((query))
class ResultsCLHandler(GenericResultHandler):
@swagger.operation(nickname="queryTestResults")
+ @web.asynchronous
+ @gen.coroutine
def get(self):
"""
@description: Retrieve result(s) for a test project
@@ -195,7 +201,8 @@ class ResultsCLHandler(GenericResultHandler):
'per_page': CONF.api_results_per_page
}
- self._list(query=self.set_query(), **limitations)
+ query = yield self.set_query()
+ yield self._list(query=query, **limitations)
@swagger.operation(nickname="createTestResult")
def post(self):
@@ -267,10 +274,19 @@ class ResultsUploadHandler(ResultsCLHandler):
results = results.split('\n')
result_ids = []
version = ''
+ vnf_type = None
+ vnf_checksum = None
for result in results:
if result == '':
continue
self.json_args = json.loads(result).copy()
+ openid = self.get_secure_cookie(auth_const.OPENID)
+ if openid:
+ self.json_args['owner'] = openid
+ if self.is_onap:
+ self.json_args['is_onap'] = 'true'
+ vnf_type = self.json_args['vnf_type']
+ vnf_checksum = self.json_args['vnf_checksum']
# the result files used in the first release of OVP did not
# specify an OVP version
if (self.json_args['version'] == 'master'
@@ -288,14 +304,30 @@ class ResultsUploadHandler(ResultsCLHandler):
with open(log_filename, "wb") as tar_out:
tar_out.write(fileinfo['body'])
resp = {'id': test_id, 'results': result_ids, 'version': version}
+ if vnf_type:
+ resp['vnf_type'] = vnf_type
+ resp['vnf_checksum'] = vnf_checksum
self.finish_request(resp)
class ResultsGURHandler(GenericResultHandler):
@swagger.operation(nickname='DeleteTestResultById')
+ @gen.coroutine
def delete(self, result_id):
- query = {'_id': objectid.ObjectId(result_id)}
- self._delete(query=query)
+ curr_user = self.get_secure_cookie(auth_const.OPENID)
+ curr_user_role = self.get_secure_cookie(auth_const.ROLE)
+ if curr_user is not None:
+ query = {'_id': objectid.ObjectId(result_id)}
+ test_data = yield dbapi.db_find_one(self.table, query)
+ if not test_data:
+ raises.NotFound(message.not_found(self.table, query))
+ if curr_user == test_data['owner'] or \
+ curr_user_role.find('administrator') != -1:
+ self._delete(query=query)
+ else:
+ raises.Forbidden(message.no_auth())
+ else:
+ raises.Unauthorized(message.no_auth())
@swagger.operation(nickname='getTestResultById')
def get(self, result_id):
diff --git a/opnfv_testapi/resources/review_handlers.py b/opnfv_testapi/resources/review_handlers.py
new file mode 100644
index 0000000..9731e0f
--- /dev/null
+++ b/opnfv_testapi/resources/review_handlers.py
@@ -0,0 +1,119 @@
+##############################################################################
+# Copyright (c) 2019 Intracom Telecom
+# mokats@intracom-telecom.com
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from datetime import datetime
+import logging
+
+from tornado import web, gen
+
+from opnfv_testapi.common.config import CONF
+from opnfv_testapi.common import message, raises
+from opnfv_testapi.db import api as dbapi
+from opnfv_testapi.resources import handlers, review_models
+from opnfv_testapi.tornado_swagger import swagger
+from opnfv_testapi.ui.auth import constants as auth_const
+
+
+class GenericReviewHandler(handlers.GenericApiHandler):
+ def __init__(self, review, request, **kwargs):
+ super(GenericReviewHandler, self).__init__(review, request, **kwargs)
+ self.table = 'reviews'
+ self.table_cls = review_models.Review
+
+
+class ReviewsCLHandler(GenericReviewHandler):
+ @swagger.operation(nickname="queryReviews")
+ @web.asynchronous
+ @gen.coroutine
+ def get(self):
+ def descend_limit():
+ descend = self.get_query_argument('descend', 'true')
+ return -1 if descend.lower() == 'true' else 1
+
+ def last_limit():
+ return self.get_int('last', self.get_query_argument('last', 0))
+
+ def page_limit():
+ return self.get_int('page', self.get_query_argument('page', 0))
+
+ limitations = {
+ 'sort': {'_id': descend_limit()},
+ 'last': last_limit(),
+ 'page': page_limit(),
+ 'per_page': CONF.api_results_per_page
+ }
+
+ query = yield self.set_query()
+ yield self._list(query=query, **limitations)
+ logging.debug('list end')
+
+ @swagger.operation(nickname="createReview")
+ @web.asynchronous
+ def post(self):
+ openid = self.get_secure_cookie(auth_const.OPENID)
+ if openid:
+ self.json_args['reviewer_openid'] = openid
+
+ if self.json_args['outcome'] is None:
+ self._del()
+ else:
+ self._post()
+
+ @gen.coroutine
+ def _post(self):
+ query = {'openid': self.json_args['reviewer_openid']}
+ user = yield dbapi.db_find_one('users', query)
+ if not user:
+ raises.Forbidden(message.unauthorized())
+ role = self.get_secure_cookie(auth_const.ROLE)
+ if 'reviewer' not in role.split(','):
+ raises.Unauthorized(message.no_auth())
+ test = yield dbapi.db_find_one(
+ 'tests', {'id': self.json_args['test_id']})
+ if test['owner'] == self.json_args['reviewer_openid']:
+ self.finish_request({'code': 403,
+ 'msg': 'No permision to review own results'})
+ return
+ query = {
+ 'reviewer_openid': self.json_args['reviewer_openid'],
+ 'test_id': self.json_args['test_id']
+ }
+ review = yield dbapi.db_find_one(self.table, query)
+ if review:
+ if review['outcome'] != self.json_args['outcome']:
+ yield dbapi.db_update(self.table, query,
+ {'$set': {
+ 'outcome': self.json_args['outcome'],
+ 'creation_date': datetime.now()}})
+ self.finish_request()
+ else:
+ self.json_args['reviewer_name'] = user['fullname']
+ self.json_args['reviewer_email'] = user['email']
+ self._create(miss_fields=[], carriers=[])
+
+ @gen.coroutine
+ def _del(self):
+ query = {'openid': self.json_args['reviewer_openid']}
+ user = yield dbapi.db_find_one('users', query)
+ if not user:
+ raises.Forbidden(message.unauthorized())
+ role = self.get_secure_cookie(auth_const.ROLE)
+ if 'reviewer' not in role.split(','):
+ raises.Unauthorized(message.no_auth())
+ test = yield dbapi.db_find_one(
+ 'tests', {'id': self.json_args['test_id']})
+ if test['owner'] == self.json_args['reviewer_openid']:
+ self.finish_request({'code': 403,
+ 'msg': 'No permision to review own results'})
+ return
+ query = {
+ 'reviewer_openid': self.json_args['reviewer_openid'],
+ 'test_id': self.json_args['test_id']
+ }
+ yield dbapi.db_delete(self.table, query)
+ self.finish_request()
diff --git a/opnfv_testapi/resources/review_models.py b/opnfv_testapi/resources/review_models.py
new file mode 100644
index 0000000..2aaa62c
--- /dev/null
+++ b/opnfv_testapi/resources/review_models.py
@@ -0,0 +1,39 @@
+##############################################################################
+# Copyright (c) 2019
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from opnfv_testapi.resources import models
+from opnfv_testapi.tornado_swagger import swagger
+
+from datetime import datetime
+
+
+@swagger.model()
+class Review(models.ModelBase):
+ def __init__(self, _id=None, test_id=None, reviewer_openid=None,
+ reviewer_email=None, reviewer_name=None, creation_date=None,
+ outcome=None):
+ self._id = _id
+ self.test_id = test_id
+ self.reviewer_openid = reviewer_openid
+ self.reviewer_email = reviewer_email
+ self.reviewer_name = reviewer_name
+ self.creation_date = datetime.now()
+ self.outcome = outcome
+
+
+@swagger.model()
+class Reviews(models.ModelBase):
+ """
+ @property reviews:
+ @ptype tests: C{list} of L{Review}
+ """
+ def __init__(self):
+ self.reviews = list()
+
+ @staticmethod
+ def attr_parser():
+ return {'reviews': Review}
diff --git a/opnfv_testapi/resources/test_handlers.py b/opnfv_testapi/resources/test_handlers.py
index 5ecb176..7ab20dc 100644
--- a/opnfv_testapi/resources/test_handlers.py
+++ b/opnfv_testapi/resources/test_handlers.py
@@ -78,10 +78,28 @@ class TestsCLHandler(GenericTestHandler):
if curr_user is None:
raises.Unauthorized(message.no_auth())
+ review = self.request.query_arguments.pop('review', None)
query = yield self.set_query()
- yield self._list(query=query, **limitations)
+ if review:
+ yield self._list(query=query, res_op=self.check_review,
+ **limitations)
+ else:
+ yield self._list(query=query, **limitations)
logging.debug('list end')
+ @gen.coroutine
+ def check_review(self, data, *args):
+ current_user = self.get_secure_cookie(auth_const.OPENID)
+ for test in data:
+ query = {'reviewer_openid': current_user, 'test_id': test['id']}
+ ret = yield dbapi.db_find_one('reviews', query)
+ if ret:
+ test['voted'] = 'true'
+ else:
+ test['voted'] = 'false'
+
+ raise gen.Return({self.table: data})
+
@swagger.operation(nickname="createTest")
@web.asynchronous
def post(self):
@@ -111,6 +129,8 @@ class TestsCLHandler(GenericTestHandler):
self.finish_request({'code': '403', 'msg': msg})
return
+ if self.is_onap:
+ self.json_args['is_onap'] = 'true'
self._create(miss_fields=miss_fields, carriers=carriers)
@@ -149,7 +169,6 @@ class TestsGURHandler(GenericTestHandler):
raise gen.Return('API response validation enabled')
@swagger.operation(nickname="deleteTestById")
- @web.asynchronous
@gen.coroutine
def delete(self, test_id):
curr_user = self.get_secure_cookie(auth_const.OPENID)
@@ -161,6 +180,9 @@ class TestsGURHandler(GenericTestHandler):
raises.NotFound(message.not_found(self.table, query))
if curr_user == test_data['owner'] or \
curr_user_role.find('administrator') != -1:
+ yield dbapi.db_delete('applications',
+ {'test_id': test_data['id']})
+ yield dbapi.db_delete('reviews', {'test_id': test_data['id']})
self._delete(query=query)
else:
raises.Forbidden(message.no_auth())
@@ -198,7 +220,7 @@ class TestsGURHandler(GenericTestHandler):
if query and table:
data = yield dbapi.db_find_one(table, query)
if data:
- raise gen.Return((True, 'Data alreay exists. %s' % (query),
+ raise gen.Return((True, 'Data already exists. %s' % (query),
data.get("openid")))
raise gen.Return((False, 'Data does not exist. %s' % (query), None))
@@ -255,9 +277,9 @@ class TestsGURHandler(GenericTestHandler):
return
if item == "status":
- if value in {'approved', 'not approved'}:
+ if value == 'verified':
if test['status'] == 'private':
- msg = 'Not allowed to approve/not approve'
+ msg = 'Not allowed to verify'
self.finish_request({'code': 403, 'msg': msg})
return
@@ -272,7 +294,7 @@ class TestsGURHandler(GenericTestHandler):
self.finish_request({'code': 403, 'msg': msg})
return
- if not test['sut_label']:
+ if not self.is_onap and not test['sut_label']:
msg = 'Please fill out SUT version before submission'
self.finish_request({'code': 403, 'msg': msg})
return
@@ -284,8 +306,7 @@ class TestsGURHandler(GenericTestHandler):
'id': test['id'],
'$or': [
{'status': 'review'},
- {'status': 'approved'},
- {'status': 'not approved'}
+ {'status': 'verified'}
]
}
record = yield dbapi.db_find_one("tests", test_query)
@@ -311,12 +332,12 @@ class TestsGURHandler(GenericTestHandler):
logging.debug('check review')
query['user_id'] = user
data = yield dbapi.db_find_one('applications', query)
- if not data:
- logging.debug('not found')
+ if data:
+ logging.debug('results are bound to an application')
raise gen.Return((False, message.no_auth()))
- if value == "approve" or value == "not approved":
- logging.debug('check approve')
- query['role'] = {"$regex": ".*reviewer.*"}
+ if value == "verified":
+ logging.debug('check verify')
+ query['role'] = {"$regex": ".*administrator.*"}
query['openid'] = user
data = yield dbapi.db_find_one('users', query)
if not data:
diff --git a/opnfv_testapi/router/url_mappings.py b/opnfv_testapi/router/url_mappings.py
index 1eb74ad..65c8480 100644
--- a/opnfv_testapi/router/url_mappings.py
+++ b/opnfv_testapi/router/url_mappings.py
@@ -13,6 +13,7 @@ from opnfv_testapi.resources import test_handlers
from opnfv_testapi.resources import application_handlers
from opnfv_testapi.resources import pod_handlers
from opnfv_testapi.resources import project_handlers
+from opnfv_testapi.resources import review_handlers
from opnfv_testapi.resources import scenario_handlers
from opnfv_testapi.resources import sut_handlers
from opnfv_testapi.resources import testcase_handlers
@@ -57,3 +58,17 @@ mappings = [
(r'/api/v1/profile', user.ProfileHandler),
]
+
+onap_mappings = [
+ (r'/api/v1/onap/results', result_handlers.ResultsCLHandler,
+ dict(is_onap=True)),
+ (r'/api/v1/onap/results/upload', result_handlers.ResultsUploadHandler,
+ dict(is_onap=True)),
+ (r'/api/v1/onap/tests', test_handlers.TestsCLHandler,
+ dict(is_onap=True)),
+ (r"/api/v1/onap/tests/([^/]+)", test_handlers.TestsGURHandler,
+ dict(is_onap=True)),
+ (r'/api/v1/onap/cvp/applications',
+ application_handlers.ApplicationsCLHandler, dict(is_onap=True)),
+ (r'/api/v1/onap/reviews', review_handlers.ReviewsCLHandler),
+]
diff --git a/opnfv_testapi/tornado_swagger/swagger.py b/opnfv_testapi/tornado_swagger/swagger.py
index 83f389a..9afdb17 100644
--- a/opnfv_testapi/tornado_swagger/swagger.py
+++ b/opnfv_testapi/tornado_swagger/swagger.py
@@ -255,7 +255,8 @@ class operation(DocParser):
def _parse_args(self, func):
argspec = inspect.getargspec(func)
- argspec.args.remove("self")
+ if 'self' in argspec.args:
+ argspec.args.remove('self')
defaults = []
if argspec.defaults:
diff --git a/opnfv_testapi/ui/auth/sign.py b/opnfv_testapi/ui/auth/sign.py
index dbb40ed..028816a 100644
--- a/opnfv_testapi/ui/auth/sign.py
+++ b/opnfv_testapi/ui/auth/sign.py
@@ -45,7 +45,8 @@ class SigninHandler(base.BaseHandler):
renew=False,
extra_login_params=False,
server_url=CONF.lfid_url,
- service_url=CONF.lfid_return_url
+ service_url='http://{0}/{1}'.format(self.request.host,
+ CONF.lfid_return_url)
)
redirect_url = client.get_login_url()
self.redirect(url=redirect_url, permanent=False)
@@ -154,7 +155,8 @@ class SigninReturnCasHandler(base.BaseHandler):
renew=False,
extra_login_params=False,
server_url=CONF.lfid_url,
- service_url=CONF.lfid_return_url
+ service_url='http://{0}/{1}'.format(self.request.host,
+ CONF.lfid_return_url)
)
user, attrs, _ = client.verify_ticket(ticket)
logging.debug("user:%s", user)
@@ -180,7 +182,7 @@ class SigninReturnCasHandler(base.BaseHandler):
self.set_secure_cookie(const.ROLE, role)
self.set_secure_cookie('ticket', ticket)
- self.redirect("/")
+ self.redirect('http://{0}'.format(self.request.host))
class SigninReturnJiraHandler(base.BaseHandler):
@@ -275,7 +277,12 @@ class SignoutHandler(base.BaseHandler):
renew=False,
extra_login_params=False,
server_url=CONF.lfid_url,
- service_url=CONF.lfid_return_url
+ service_url='http://{0}/{1}'.format(self.request.host,
+ CONF.lfid_return_url)
)
- url = client.get_logout_url(CONF.ui_url)
+
+ self.clear_cookie('ticket')
+ self.clear_cookie('signin_type')
+
+ url = client.get_logout_url('http://{0}'.format(self.request.host))
self.redirect(url)
diff --git a/opnfv_testapi/ui/auth/user.py b/opnfv_testapi/ui/auth/user.py
index a695da4..5ac6f43 100644
--- a/opnfv_testapi/ui/auth/user.py
+++ b/opnfv_testapi/ui/auth/user.py
@@ -10,12 +10,19 @@
from tornado import gen
from tornado import web
+from opnfv_testapi.common import message
from opnfv_testapi.common import raises
from opnfv_testapi.db import api as dbapi
+from opnfv_testapi.resources import models
from opnfv_testapi.ui.auth import base
+from opnfv_testapi.ui.auth import constants as auth_const
class ProfileHandler(base.BaseHandler):
+ def __init__(self, application, request, **kwargs):
+ super(ProfileHandler, self).__init__(application, request, **kwargs)
+ self.table_cls = User
+
@web.asynchronous
@gen.coroutine
def get(self):
@@ -28,8 +35,51 @@ class ProfileHandler(base.BaseHandler):
"email": user.get('email'),
"fullname": user.get('fullname'),
"role": user.get('role', 'user'),
- "type": self.get_secure_cookie('signin_type')
+ "type": self.get_secure_cookie('signin_type'),
+ "companyName": user.get('companyName'),
+ "companyWebsite": user.get('companyWebsite'),
+ "primaryContactName": user.get('primaryContactName'),
+ "primaryBusinessEmail": user.get('primaryBusinessEmail'),
+ "primaryPostalAddress": user.get('primaryPostalAddress'),
+ "primaryPhoneNumber": user.get('primaryPhoneNumber')
+
})
except Exception:
pass
raises.Unauthorized('Unauthorized')
+
+ @gen.coroutine
+ def put(self):
+ db_keys = []
+ openid = self.get_secure_cookie(auth_const.OPENID)
+
+ if openid:
+ query = {'openid': openid}
+ user = yield dbapi.db_find_one(self.table, query)
+ if not user:
+ raises.NotFound(message.not_found(self.table, query))
+
+ self._update(query=query, db_keys=db_keys)
+ else:
+ raises.Unauthorized(message.no_auth())
+
+
+class User(models.ModelBase):
+ def __init__(self, _id=None, openid=None, email=None, fullname=None,
+ role='user', u_type=None, companyName=None,
+ companyWebsite=None, primaryContactName=None,
+ primaryBusinessEmail=None, primaryPostalAddress=None,
+ primaryPhoneNumber=None):
+ self._id = _id
+ self.openid = openid
+ self.email = email
+ self.fullname = fullname
+ self.role = role
+ self.type = u_type
+
+ self.companyName = companyName
+ self.companyWebsite = companyWebsite
+ self.primaryContactName = primaryContactName
+ self.primaryBusinessEmail = primaryBusinessEmail
+ self.primaryPostalAddress = primaryPostalAddress
+ self.primaryPhoneNumber = primaryPhoneNumber
diff --git a/opnfv_testapi/ui/root.py b/opnfv_testapi/ui/root.py
deleted file mode 100644
index 7f970b2..0000000
--- a/opnfv_testapi/ui/root.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from opnfv_testapi.resources.handlers import GenericApiHandler
-from opnfv_testapi.common.config import CONF
-
-
-class RootHandler(GenericApiHandler):
- def get_template_path(self):
- return CONF.ui_static_path
-
- def get(self):
- self.render('testapi-ui/index.html')
diff --git a/requirements.txt b/requirements.txt
index b705689..07a4fe6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,3 +15,5 @@ pyjwt>=1.5.2
cryptography==2.2.2
python-cas==1.2.0
futures==3.2.0
+python-slugify==2.0.1
+Pillow==3.1.2