Retire refstack repository
Remove the content following https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project Change-Id: I688aecc8ff228936217be0aba9b5fb163d38de7d Signed-off-by: Christian Berendt <berendt@osism.tech>
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
*.egg*
|
||||
*.py[cod]
|
||||
.coverage
|
||||
.testrepository/
|
||||
.tox/
|
||||
.venv/
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
build/
|
||||
cover/
|
||||
dist
|
||||
.git/
|
||||
@@ -1 +0,0 @@
|
||||
refstack-ui/app/assets/lib
|
||||
72
.eslintrc
72
.eslintrc
@@ -1,72 +0,0 @@
|
||||
{
|
||||
// For a detailed list of all options, please see here:
|
||||
// http://eslint.org/docs/configuring/
|
||||
"ecmaFeatures": {
|
||||
"arrowFunctions": false,
|
||||
"binaryLiterals": false,
|
||||
"blockBindings": false,
|
||||
"defaultParams": false,
|
||||
"forOf": false,
|
||||
"generators": false,
|
||||
"objectLiteralComputedProperties": false,
|
||||
"objectLiteralDuplicateProperties": false,
|
||||
"objectLiteralShorthandProperties": false,
|
||||
"octalLiterals": false,
|
||||
"regexUFlag": false,
|
||||
"superInFunctions": false,
|
||||
"templateStrings": false,
|
||||
"unicodeCodePointEscapes": false,
|
||||
"globalReturn": false,
|
||||
"jsx": false
|
||||
},
|
||||
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": false,
|
||||
"amd": false,
|
||||
"mocha": false,
|
||||
"jasmine": true,
|
||||
"phantomjs": false,
|
||||
"jquery": false,
|
||||
"prototypejs": false,
|
||||
"shelljs": false,
|
||||
"es6": true
|
||||
},
|
||||
|
||||
"extends": "openstack",
|
||||
|
||||
"globals": {
|
||||
"require": false,
|
||||
"exports": false,
|
||||
"angular": false, // AngularJS
|
||||
"module": false,
|
||||
"inject": false,
|
||||
"element": false,
|
||||
"by": false,
|
||||
"browser": false
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"angular"
|
||||
],
|
||||
|
||||
"rules": {
|
||||
"quotes": [2, "single"],
|
||||
"eol-last": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"camelcase": 0,
|
||||
"no-extra-boolean-cast": 0,
|
||||
"operator-linebreak": 0,
|
||||
"require-jsdoc": 2,
|
||||
"quote-props": 0,
|
||||
"valid-jsdoc": 0,
|
||||
|
||||
// Stylistic
|
||||
"indent": [2, 4, {SwitchCase: 1}],
|
||||
"max-len": [2, 80],
|
||||
"no-undefined": 2,
|
||||
|
||||
// Angular Plugin
|
||||
"angular/controller-as-vm": [1, "ctrl"]
|
||||
}
|
||||
}
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,17 +0,0 @@
|
||||
*.egg*
|
||||
*.py[cod]
|
||||
.coverage
|
||||
.stestr
|
||||
.tox/
|
||||
.venv/
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
build/
|
||||
cover/
|
||||
dist
|
||||
|
||||
.tmp
|
||||
node_modules
|
||||
npm-debug.log
|
||||
refstack-ui/app/assets/lib
|
||||
refstack-ui/app/config.json
|
||||
@@ -1,3 +0,0 @@
|
||||
[DEFAULT]
|
||||
test_path=./refstack/tests/unit
|
||||
top_dir=./
|
||||
38
.zuul.yaml
38
.zuul.yaml
@@ -1,38 +0,0 @@
|
||||
- project:
|
||||
templates:
|
||||
- nodejs18-jobs
|
||||
- openstack-cover-jobs
|
||||
check:
|
||||
jobs:
|
||||
- openstack-tox-pep8
|
||||
- openstack-tox-py38
|
||||
- openstack-tox-py39
|
||||
- openstack-tox-py310
|
||||
- openstack-tox-py311
|
||||
- refstack-tox-functional
|
||||
- opendev-tox-docs
|
||||
gate:
|
||||
jobs:
|
||||
- openstack-tox-pep8
|
||||
- openstack-tox-py38
|
||||
- openstack-tox-py39
|
||||
- openstack-tox-py310
|
||||
- openstack-tox-py311
|
||||
- refstack-tox-functional
|
||||
- opendev-tox-docs
|
||||
promote:
|
||||
jobs:
|
||||
- opendev-promote-docs
|
||||
|
||||
- job:
|
||||
name: refstack-tox-functional
|
||||
parent: openstack-tox-with-sudo
|
||||
description: |
|
||||
Run functional tests for an OpenStack Python project under cPython 3.
|
||||
Uses tox with the ``functional`` environment.
|
||||
irrelevant-files:
|
||||
- ^.*\.rst$
|
||||
- ^doc/.*$
|
||||
- ^releasenotes/.*$
|
||||
vars:
|
||||
tox_envlist: functional
|
||||
@@ -1,34 +0,0 @@
|
||||
The source repository for this project can be found at:
|
||||
|
||||
https://opendev.org/openinfra/refstack
|
||||
|
||||
To start contributing to OpenStack, follow the steps in the contribution guide
|
||||
to set up and use Gerrit:
|
||||
|
||||
https://docs.openstack.org/contributors/code-and-documentation/quick-start.html
|
||||
|
||||
Documentation of the project can be found at:
|
||||
|
||||
https://docs.opendev.org/openinfra/refstack/latest/
|
||||
|
||||
Bugs should be filed on Storyboard:
|
||||
|
||||
https://storyboard.openstack.org/#!/project/openinfra/refstack
|
||||
|
||||
Patches against this project can be found at:
|
||||
|
||||
https://review.opendev.org/q/project:openinfra/refstack
|
||||
|
||||
To communicate with us you may use one of the following means:
|
||||
|
||||
**Mailing List:**
|
||||
Get in touch with us via `email <mailto:openstack-discuss@lists.openstack.org>`_.
|
||||
Use [refstack] in your subject.
|
||||
|
||||
**IRC:**
|
||||
We're at #refstack channel on OFTC network.
|
||||
`Setup IRC <https://docs.openstack.org/contributors/common/irc.html>`_
|
||||
|
||||
**Meetings:**
|
||||
`Visit this link <https://meetings.opendev.org/#Interop_Working_Group_Meeting>`_
|
||||
for the meeting information.
|
||||
176
LICENSE
176
LICENSE
@@ -1,176 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
69
README.rst
69
README.rst
@@ -1,65 +1,6 @@
|
||||
========
|
||||
RefStack
|
||||
========
|
||||
This project is no longer maintained.
|
||||
|
||||
What is RefStack?
|
||||
#################
|
||||
|
||||
- Toolset for testing interoperability between OpenStack clouds.
|
||||
- Database backed website supporting collection and publication of
|
||||
Community Test results for OpenStack.
|
||||
- User interface to display individual test run results.
|
||||
|
||||
RefStack intends on being THE source of tools for interoperability testing
|
||||
of OpenStack clouds.
|
||||
|
||||
RefStack provides users in the OpenStack community with a Tempest wrapper,
|
||||
refstack-client, that helps to verify interoperability of their cloud
|
||||
with other OpenStack clouds. It does so by validating any cloud
|
||||
implementation against the OpenStack Tempest API tests.
|
||||
|
||||
Refstack's Use Case
|
||||
###################
|
||||
|
||||
**RefStack and Interop Working Group** - The prototypical use case for RefStack
|
||||
provides the Interop Working Group - formerly known as DefCore committee - the
|
||||
tools for vendors and other users to run API tests against their clouds to
|
||||
provide the WG with a reliable overview of what APIs and capabilities are
|
||||
being used in the marketplace. This will help to guide the Interop
|
||||
Working Group defined capabilities and help ensure interoperability across
|
||||
the entire OpenStack ecosystem. It can also be used to validate clouds
|
||||
against existing capability lists, giving you assurance that your cloud
|
||||
faithfully implements OpenStack standards.
|
||||
|
||||
**Value add for openstack distro or solution vendors** - Vendors can use
|
||||
RefStack to demonstrate that their distros, and/or their customers' installed
|
||||
clouds remain OpenStack compliant after their software has been incorporated
|
||||
into the distro or cloud.
|
||||
|
||||
**RefStack consists of two parts:**
|
||||
|
||||
* **refstack-api**
|
||||
Our API isn't just for us to collect data from private and public cloud
|
||||
vendors. It can be used by vendors in house to compare interoperability
|
||||
data over time.
|
||||
|
||||
* documentation: https://docs.opendev.org/openinfra/refstack/latest/
|
||||
* repository: https://opendev.org/openinfra/refstack
|
||||
* reviews: https://review.opendev.org/#/q/status:open+project:openinfra/refstack
|
||||
* bugs: https://storyboard.openstack.org/#!/project/openinfra/refstack
|
||||
* Web-site: https://refstack.openstack.org
|
||||
|
||||
* **refstack-client**
|
||||
refstack-client contains the tools you will need to run the
|
||||
Interop Working Group tests.
|
||||
|
||||
* documentation: https://docs.opendev.org/openinfra/refstack-client/latest/
|
||||
* repository: https://opendev.org/openinfra/refstack-client
|
||||
* reviews: https://review.opendev.org/#/q/status:open+project:openinfra/refstack-client
|
||||
* bugs: https://storyboard.openstack.org/#!/project/openinfra/refstack-client
|
||||
|
||||
Get Involved!
|
||||
#############
|
||||
|
||||
See the `CONTRIBUTING <https://docs.opendev.org/openinfra/refstack/latest/contributing.html>`_
|
||||
guide on how to get involved.
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Command-line launcher for Refstack API
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from pecan.commands import serve
|
||||
|
||||
from refstack.api import config as api_config
|
||||
|
||||
|
||||
def get_pecan_config():
|
||||
"""Get path to pecan configuration file"""
|
||||
filename = api_config.__file__.replace('.pyc', '.py')
|
||||
return filename
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config_path = get_pecan_config()
|
||||
sys.argv.append(config_path)
|
||||
serve.gunicorn_run()
|
||||
@@ -1,98 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
Command-line utility for database manage
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from refstack.db import migration
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
log.register_options(CONF)
|
||||
|
||||
|
||||
class DatabaseManager(object):
|
||||
|
||||
def version(self):
|
||||
print(migration.version())
|
||||
|
||||
def upgrade(self):
|
||||
migration.upgrade(CONF.command.revision)
|
||||
|
||||
def downgrade(self):
|
||||
migration.downgrade(CONF.command.revision)
|
||||
|
||||
def stamp(self):
|
||||
migration.stamp(CONF.command.revision)
|
||||
|
||||
def revision(self):
|
||||
migration.revision(CONF.command.message, CONF.command.autogenerate)
|
||||
|
||||
|
||||
def add_command_parsers(subparsers):
|
||||
db_manager = DatabaseManager()
|
||||
|
||||
parser = subparsers.add_parser('version',
|
||||
help='show current database version')
|
||||
parser.set_defaults(func=db_manager.version)
|
||||
|
||||
parser = subparsers.add_parser('upgrade',
|
||||
help='upgrade database to '
|
||||
'the specified version')
|
||||
parser.set_defaults(func=db_manager.upgrade)
|
||||
parser.add_argument('--revision', nargs='?',
|
||||
help='desired database version')
|
||||
|
||||
parser = subparsers.add_parser('downgrade',
|
||||
help='downgrade database '
|
||||
'to the specified version')
|
||||
parser.set_defaults(func=db_manager.downgrade)
|
||||
parser.add_argument('--revision', nargs='?',
|
||||
help='desired database version')
|
||||
|
||||
parser = subparsers.add_parser('stamp',
|
||||
help='stamp database with provided '
|
||||
'revision. Don\'t run any migrations')
|
||||
parser.add_argument('--revision', nargs='?',
|
||||
help='should match one from repository or head - '
|
||||
'to stamp database with most recent revision')
|
||||
parser.set_defaults(func=db_manager.stamp)
|
||||
|
||||
parser = subparsers.add_parser('revision',
|
||||
help='create template for migration')
|
||||
parser.add_argument('-m', '--message',
|
||||
help='text that will be used for migration title')
|
||||
parser.add_argument('--autogenerate', action='store_true',
|
||||
help='if True - generates diff based '
|
||||
'on current database state (True by default)')
|
||||
parser.set_defaults(func=db_manager.revision)
|
||||
|
||||
command_opt = cfg.SubCommandOpt('command',
|
||||
title='Available commands',
|
||||
handler=add_command_parsers)
|
||||
|
||||
CONF.register_cli_opt(command_opt)
|
||||
|
||||
if __name__ == '__main__':
|
||||
CONF(sys.argv[1:], project='refstack')
|
||||
log.setup(CONF, 'refstack')
|
||||
CONF.command.func()
|
||||
@@ -1,8 +0,0 @@
|
||||
# This is a cross-platform list tracking distribution packages needed for install and tests;
|
||||
# see https://docs.openstack.org/infra/bindep/ for additional information.
|
||||
|
||||
gcc [compile test]
|
||||
mariadb-client [test platform:dpkg]
|
||||
mariadb-server [test platform:dpkg]
|
||||
postgresql [test]
|
||||
postgresql-client [test platform:dpkg]
|
||||
20
bower.json
20
bower.json
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "refstack-ui",
|
||||
"version": "0.0.1",
|
||||
"description": "Refstack user interface",
|
||||
"dependencies": {
|
||||
"angular": "1.3.15",
|
||||
"angular-ui-router": "0.2.13",
|
||||
"angular-resource": "1.3.15",
|
||||
"angular-bootstrap": "0.14.3",
|
||||
"angular-busy": "4.1.3",
|
||||
"angular-confirm-modal": "1.2.3",
|
||||
"bootstrap": "3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.3.15"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "1.3.15"
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
sphinx>=1.6.2
|
||||
openstackdocstheme>=1.11.0 # Apache-2.0
|
||||
@@ -1,336 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Refstack documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Aug 5 01:41:59 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ['openstackdocstheme']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
#templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Refstack'
|
||||
copyright = u'2016, OpenStack Foundation'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0'
|
||||
|
||||
# openstackdocstheme options
|
||||
openstackdocs_repo_name = 'openinfra/refstack'
|
||||
openstackdocs_bug_project = '878'
|
||||
openstackdocs_bug_tag = ''
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['specs/prior/*', 'specs/README.rst', 'specs/template.rst']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
#html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# So that we can enable "log-a-bug" links from each output HTML page, this
|
||||
# variable must be set to a format that includes year, month, day, hours and
|
||||
# minutes.
|
||||
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Refstackdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'Refstack.tex', u'Refstack Documentation',
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'refstack', u'Refstack Documentation',
|
||||
[u'OpenStack Foundation'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Refstack', u'Refstack Documentation',
|
||||
u'OpenStack Foundation', 'Refstack', 'Toolset for testing interoperability'
|
||||
' between OpenStack clouds.', 'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# -- Options for Epub output ----------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = u'Refstack'
|
||||
epub_publisher = u'OpenStack Foundation'
|
||||
epub_copyright = u'2016, OpenStack Foundation'
|
||||
|
||||
# The basename for the epub file. It defaults to the project name.
|
||||
#epub_basename = u'Refstack'
|
||||
|
||||
# The HTML theme for the epub output. Since the default themes are not optimized
|
||||
# for small screen space, using the same theme for HTML and epub output is
|
||||
# usually not wise. This defaults to 'epub', a theme designed to save visual
|
||||
# space.
|
||||
#epub_theme = 'epub'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
#epub_language = ''
|
||||
|
||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
#epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
#epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
#epub_uid = ''
|
||||
|
||||
# A tuple containing the cover image and cover page html template filenames.
|
||||
#epub_cover = ()
|
||||
|
||||
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
|
||||
#epub_guide = ()
|
||||
|
||||
# HTML files that should be inserted before the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_pre_files = []
|
||||
|
||||
# HTML files shat should be inserted after the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_post_files = []
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
|
||||
# The depth of the table of contents in toc.ncx.
|
||||
#epub_tocdepth = 3
|
||||
|
||||
# Allow duplicate toc entries.
|
||||
#epub_tocdup = True
|
||||
|
||||
# Choose between 'default' and 'includehidden'.
|
||||
#epub_tocscope = 'default'
|
||||
|
||||
# Fix unsupported image types using the PIL.
|
||||
#epub_fix_images = False
|
||||
|
||||
# Scale large images.
|
||||
#epub_max_image_width = 0
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#epub_show_urls = 'inline'
|
||||
|
||||
# If false, no index is generated.
|
||||
#epub_use_index = True
|
||||
@@ -1,5 +0,0 @@
|
||||
============
|
||||
Contributing
|
||||
============
|
||||
|
||||
.. include:: ../../CONTRIBUTING.rst
|
||||
@@ -1,21 +0,0 @@
|
||||
.. Refstack documentation master file, created by
|
||||
sphinx-quickstart on Fri Aug 5 01:41:59 2016.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
====================================
|
||||
Welcome to RefStack's documentation!
|
||||
====================================
|
||||
|
||||
Content:
|
||||
--------
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
refstack_setup_via_ansible
|
||||
overview
|
||||
contributing
|
||||
refstack
|
||||
vendor_product_management/index
|
||||
uploading_private_results
|
||||
test_result_management
|
||||
@@ -1,2 +0,0 @@
|
||||
.. include:: ../../README.rst
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
===================
|
||||
RefStack Quickstart
|
||||
===================
|
||||
|
||||
You can use docker for `one-click setup <run_in_docker.html>`__ or follow
|
||||
step-by-step instructions below. These instructions have been tested on
|
||||
Ubuntu 14 and 16 LTS.
|
||||
|
||||
Install API dependencies
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
::
|
||||
|
||||
sudo apt-get install git python-dev python-virtualenv libssl-dev build-essential libffi-dev
|
||||
sudo apt-get install mysql-server python-mysqldb
|
||||
|
||||
Install RefStack UI dependencies
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
::
|
||||
|
||||
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
|
||||
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||
sudo apt-get update && sudo apt-get install -y nodejs yarn
|
||||
|
||||
Setup the RefStack database
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**Log into MySQL**::
|
||||
|
||||
mysql -u root -p
|
||||
|
||||
**After authentication, create the database**::
|
||||
|
||||
CREATE DATABASE refstack;
|
||||
|
||||
**Create a refstack user**::
|
||||
|
||||
CREATE USER 'refstack'@'localhost' IDENTIFIED BY '<your password>';
|
||||
|
||||
**or using hash value for your password**::
|
||||
|
||||
CREATE USER 'refstack'@'localhost' IDENTIFIED BY PASSWORD '<hash value of your password';
|
||||
|
||||
**Grant privileges**::
|
||||
|
||||
GRANT ALL PRIVILEGES ON refstack . * TO 'refstack'@'localhost';
|
||||
|
||||
**Reload privileges**::
|
||||
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
**Exit MySQL**::
|
||||
|
||||
quit
|
||||
|
||||
Clone the repository
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
::
|
||||
|
||||
git clone https://opendev.org/openinfra/refstack
|
||||
cd refstack
|
||||
|
||||
**Create virtual environment**::
|
||||
|
||||
virtualenv .venv --system-site-package
|
||||
|
||||
**Source to virtual environment**::
|
||||
|
||||
source .venv/bin/activate
|
||||
|
||||
**Update pip**::
|
||||
|
||||
pip install -U pip
|
||||
|
||||
**Install environment pre-requirements**::
|
||||
|
||||
pip install gunicorn==18 # App server
|
||||
pip install PyMySQL>=0.6.2,!=0.6.4 # python mysql connector
|
||||
|
||||
Install RefStack application
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
::
|
||||
|
||||
pip install .
|
||||
|
||||
Install needed RefStack UI library dependencies
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
::
|
||||
|
||||
yarn
|
||||
|
||||
API configuration file preparation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Make a copy of the sample config file (etc/refstack.conf.sample) and
|
||||
update it with the correct information of your environment. Examples
|
||||
of the config parameters with default values are included in the
|
||||
sample config file.
|
||||
|
||||
You should ensure that the following values in the config file are
|
||||
noted and properly set:
|
||||
|
||||
``connection`` field in the ``[database]``\ section.
|
||||
|
||||
For example, if the backend database is MySQL then update:
|
||||
|
||||
``#connection = <None>`` to
|
||||
``connection = mysql+pymysql://refstack:<your password>@x.x.x.x/refstack``
|
||||
|
||||
``ui_url`` field in the ``[DEFAULT]`` section.
|
||||
|
||||
This should be the URL that the UI can be accessed from. This will
|
||||
likely be in the form ``http://<your server IP>:8000`` (8000 being
|
||||
the default port RefStack is hosted on). For example:
|
||||
|
||||
``http://192.168.56.101:8000``
|
||||
|
||||
``api_url`` field in the ``[api]`` section.
|
||||
|
||||
This should be the URL that the API can be accessed from. This, in
|
||||
most cases, will be the same as the value for ``ui_url`` above.
|
||||
|
||||
``app_dev_mode`` field in the ``[api]`` section.
|
||||
|
||||
Set this field to true if you aren't creating a production-level
|
||||
RefStack deployment and are just trying things out or developing.
|
||||
Setting this field to true will allow you to quickly bring up both
|
||||
the API and UI together, with the UI files being served by a simple
|
||||
file server that comes with Pecan.
|
||||
|
||||
Create UI config file
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
From the RefStack project root directory, create a config.json file and
|
||||
specify your API endpoint inside this file. This will be something like
|
||||
{"refstackApiUrl": "http://192.168.56.101:8000/v1"}::
|
||||
|
||||
cp refstack-ui/app/config.json.sample refstack-ui/app/config.json
|
||||
|
||||
Openstack OpenID endpoint configuration (optional)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you are only interested in the uploading and viewing of result sets,
|
||||
then this section can be ignored. However, in order for user accounts
|
||||
and authentication to work, you need to make sure you are properly
|
||||
configured with an OpenStack OpenID endpoint. There are two options:
|
||||
|
||||
- Use the official endpoint
|
||||
`openstackid.org <https://openstackid.org>`__
|
||||
- Host your own openstackid endpoint
|
||||
|
||||
Since openstackid checks for valid top-level domains, in both options
|
||||
you will likely have to edit the hosts file of the system where your
|
||||
web-browser for viewing the RefStack site resides. On Linux systems, you
|
||||
would modify ``/etc/hosts``, adding a line like the following:
|
||||
|
||||
``<RefStack server IP> <some valid domain name>``
|
||||
|
||||
Example:
|
||||
|
||||
``192.168.56.101 myrefstack.com``
|
||||
|
||||
On Windows, you would do the same in
|
||||
``%SystemRoot%\System32\drivers\etc\hosts``. Alternatively, you can add
|
||||
a custom DNS record with the domain name mapping if possible.
|
||||
|
||||
Note that doing this requires you to modify the config.json file and the
|
||||
``api_url`` and ``ui_url`` fields in refstack.conf to use this domain
|
||||
name instead of the IP.
|
||||
|
||||
**Option 1 - Use Official Endpoint**
|
||||
|
||||
Using the official site is probably the easiest as no additional configuration
|
||||
is needed besides the hosts file modifications as noted above. RefStack, by
|
||||
default, points to this endpoint.
|
||||
|
||||
**Option 2 - Use Local Endpoint**
|
||||
|
||||
Instructions for setting this up are outside of the scope of this doc,
|
||||
but you can get started at
|
||||
`Openstackid project <https://github.com/OpenStackweb/openstackid>`__ .
|
||||
You would then need to modify the ``openstack_openid_endpoint`` field in
|
||||
the ``[osid]`` section in refstack.conf to match the local endpoint.
|
||||
|
||||
Database sync
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
**Check current revision**::
|
||||
|
||||
refstack-manage --config-file /path/to/refstack.conf version
|
||||
|
||||
The response will show the current database revision. If the revision is
|
||||
``None`` (indicating a clear database), the following command should be
|
||||
performed to upgrade the database to the latest revision:
|
||||
|
||||
**Upgrade database to latest revision**::
|
||||
|
||||
refstack-manage --config-file /path/to/refstack.conf upgrade --revision head
|
||||
|
||||
**Check current revision**::
|
||||
|
||||
refstack-manage --config-file /path/to/refstack.conf version
|
||||
|
||||
::
|
||||
|
||||
Now it should be some revision number other than `None`.
|
||||
|
||||
(Optional) Generate About Page Content
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The RefStack About page is populated with HTML templates generated from
|
||||
our RST documentation files. If you want this information displayed, then
|
||||
run the following command from the root of the project.
|
||||
|
||||
::
|
||||
|
||||
./tools/convert-docs.py -o ./refstack-ui/app/components/about/templates ./doc/source/*.rst
|
||||
|
||||
Ignore any unknown directive errors.
|
||||
|
||||
Start RefStack
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
A simple way to start refstack is to just kick off gunicorn using the
|
||||
``refstack-api`` executable::
|
||||
|
||||
refstack-api --env REFSTACK_OSLO_CONFIG=/path/to/refstack.conf
|
||||
|
||||
If ``app_dev_mode`` is set to true, this will launch both the UI and
|
||||
API.
|
||||
|
||||
Now available:
|
||||
|
||||
- ``http://<your server IP>:8000/v1/results`` with response JSON
|
||||
including records consisting of ``<test run id>`` and
|
||||
``<upload date>`` of the test runs. The default response is limited
|
||||
to one page of the most recent uploaded test run records. The number
|
||||
of records per page is configurable via the RefStack configuration
|
||||
file. Filtering parameters such as page, start\_date, and end\_date
|
||||
can also be used to specify the desired records. For example: GET
|
||||
``http://<your server IP>:8000/v1/results?page=n`` will return page
|
||||
*n* of the data.
|
||||
|
||||
- ``http://<your server IP>:8000/v1/results/<test run id>`` with
|
||||
response JSON including the detail test results of the specified
|
||||
``<test run id>``
|
||||
|
||||
(Optional) Configure Foundation organization and group
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Overall RefStack admin access is given to users belonging to a
|
||||
"Foundation" organization. To become a Foundation admin, first a
|
||||
"Foundation" organization must be created. Note that you must have
|
||||
logged into RefStack at least once so that a user record for your
|
||||
account is created.
|
||||
|
||||
**Log into MySQL**::
|
||||
|
||||
mysql -u root -p
|
||||
|
||||
**Create a group for the "Foundation" organization**::
|
||||
|
||||
INSERT INTO refstack.group (id, name, created_at) VALUES (UUID(), 'Foundation Group', NOW());
|
||||
|
||||
**Get the group ID for the group you just created**::
|
||||
|
||||
SELECT id from refstack.group WHERE name = 'Foundation Group';
|
||||
|
||||
**Get your OpenID**::
|
||||
|
||||
SELECT openid from refstack.user WHERE email = '<your email>';
|
||||
|
||||
**Add your user account to the previously created "Foundation" group.**
|
||||
|
||||
Replace ``<Group ID>`` and ``<Your OpenID>`` with the values
|
||||
retrieved in the two previous steps::
|
||||
|
||||
INSERT INTO refstack.user_to_group (created_by_user, user_openid, group_id, created_at) VALUES ('<Your OpenID>', '<Your OpenID>', '<Group ID>', NOW());
|
||||
|
||||
**Create the actual "Foundation" organization using this group**::
|
||||
|
||||
INSERT INTO refstack.organization (id, type, name, group_id, created_by_user, created_at) VALUES (UUID(), 0, 'Foundation', '<Group ID>', '<Your OpenID>', NOW());
|
||||
|
||||
(Optional) Build documentation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The RefStack documentation can be build using following commands::
|
||||
|
||||
cd ~/refstack; source .venv/bin/activate
|
||||
sudo apt-get install python3-dev python-tox
|
||||
tox -e docs
|
||||
|
||||
The documentation files will be build under ``~/refstack/build/sphinx``.
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
=============================================================
|
||||
Use Ansible playbook to set up a local refstack instance
|
||||
=============================================================
|
||||
These steps are meant for RefStack developers to help them with setting up
|
||||
a local refstack instance.
|
||||
|
||||
In production RefStack server is managed by a set of playbooks and Ansible roles
|
||||
defined in `system-config <https://opendev.org/opendev/system-config.git>`__
|
||||
repository. Below instructions use these Ansible capabilities.
|
||||
|
||||
The RefStack server runs on Ubuntu 20.04 LTS in the production.
|
||||
|
||||
You can find an Ansible playbook in ``playbooks`` directory which semi-automates
|
||||
the process of running refstack server in a container.
|
||||
|
||||
Execute the playbook by::
|
||||
|
||||
$ ansible-playbook playbooks/run-refstack-in-container.yaml
|
||||
|
||||
In order to avoid setting certificates and https protocol (it's simpler and more
|
||||
than enough for a testing instance), edit
|
||||
``/etc/apache2/sites-enabled/000-default.conf`` like following:
|
||||
|
||||
* remove VirtualHost section for the port 80 and change the port of VirtualHost from 443 to 80
|
||||
* Turn off the SSLEngine (`SSLEngine on -> SSLEngine off`)
|
||||
* Remove SSLCertificate lines
|
||||
|
||||
and then restart the apache service so that it loads the new configuration::
|
||||
|
||||
$ systemctl restart apache2
|
||||
|
||||
How to edit refstack files within the container
|
||||
-----------------------------------------------
|
||||
|
||||
List the running container by::
|
||||
|
||||
$ docker container list
|
||||
|
||||
You can enter the container by::
|
||||
|
||||
$ sudo docker exec -it <container name> /bin/bash
|
||||
|
||||
If you wanna install new packages like f.e. vim, do the following::
|
||||
$ apt update
|
||||
$ apt install vim
|
||||
|
||||
Edit what's needed, backend is installed under
|
||||
``/usr/local/lib/python3.7/site-packages/refstack/`` and frontend source files
|
||||
can be found at ``/refstack-ui``
|
||||
|
||||
After you made the changes, make pecan to reload the files served::
|
||||
|
||||
$ apt install procps # to install pkill command
|
||||
$ pkill pecan
|
||||
|
||||
Killing pecan will kick you out of the container, however, pecan serves the
|
||||
edited files now and you may re-enter the container.
|
||||
|
||||
Installing refstack with changes put for a review
|
||||
-------------------------------------------------
|
||||
|
||||
In order to do this, you will need to rebuild the refstack image built by the
|
||||
playbook.
|
||||
|
||||
Go to the location where the playbook downloaded system-config, default in
|
||||
``/tmp/refstack-docker`` and edit the refstack's Dockerfile::
|
||||
|
||||
$ cd /tmp/refstack-docker
|
||||
$ vim ./refstack-docker-files/Dockerfile
|
||||
|
||||
Replace::
|
||||
|
||||
$ RUN git clone https://opendev.org/openinfra/refstack /tmp/src
|
||||
|
||||
by::
|
||||
|
||||
$ RUN git clone https://opendev.org/openinfra/refstack.git /tmp/src \
|
||||
&& cd /tmp/src && git fetch "https://review.opendev.org/openinfra/refstack" \
|
||||
refs/changes/37/<change id/<patchset number> && git checkout -b \
|
||||
change-<change id>-<patchset number> FETCH_HEAD
|
||||
|
||||
Then rebuild the image::
|
||||
|
||||
$ docker image build -f Dockerfile -t <name:tag> .
|
||||
|
||||
Edit the ``docker-compose.yaml`` stored (by default) in
|
||||
``/etc/refstack-docker/docker-compose.yaml`` and change the the image
|
||||
(under `refstack-api`) to your image name and tag you set in the previous step.
|
||||
|
||||
After then spin a new container using the new image::
|
||||
|
||||
$ cd /etc/refstack-docker
|
||||
$ docker-compose down # if refstack container is already running
|
||||
$ docker-compose up -d
|
||||
|
||||
To see the server's logs use the following command::
|
||||
|
||||
$ docker container logs -f <container name>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
======================
|
||||
Test result management
|
||||
======================
|
||||
|
||||
Test result to product version association
|
||||
------------------------------------------
|
||||
|
||||
Test results uploaded by users can be associated to a version of a product. To
|
||||
perform this association, the user must be both the one who uploaded the result
|
||||
and also an admin of the vendor which owns the product. Once a test result is
|
||||
associated to a product, all admins of the vendor which owns the product can
|
||||
manage the test result.
|
||||
|
||||
Mark or unmark a test results as verified
|
||||
-----------------------------------------
|
||||
|
||||
Only Foundation admins can mark and un-mark a test as verified. A verified
|
||||
test result can not be updated or deleted.
|
||||
@@ -1,120 +0,0 @@
|
||||
======================================
|
||||
How to upload test results to RefStack
|
||||
======================================
|
||||
|
||||
RefStack allows test results contributors to submit test results and
|
||||
have them displayed either anonymously, or identified with a vendor. As
|
||||
such, test results should be uploaded with validated users. Users will
|
||||
first log into RefStack with their OpenStack ID to upload their public
|
||||
keys. RefStack test results need to be uploaded to RefStack using the
|
||||
corresponding private key. By default, the uploaded data isn't shared,
|
||||
but authorized users can decide to share the results with the community
|
||||
anonymously.
|
||||
|
||||
The following is a quick guide outlining the steps needed to upload your
|
||||
first set of test results.
|
||||
|
||||
Register an OpenStack ID
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The RefStack server uses OpenStack OpenID for user authentication.
|
||||
Therefore, the RefStack server requires that anyone who wants to upload
|
||||
test data to have an OpenStack ID. As you click on the Sign In/Sign Up
|
||||
link on the `RefStack pages <https://refstack.openstack.org/#/>`__, you
|
||||
will be redirected to the official OpenStack user log in page where you
|
||||
can either log in with your OpenStack ID or register for one.
|
||||
The registration page can also be found directly through:
|
||||
https://www.openstack.org/join/register.
|
||||
|
||||
Generate ssh keys locally
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You will need to generate ssh keys locally. If your operating system is
|
||||
a Linux distro, then you can use the following instructions.
|
||||
|
||||
First check for existing keys with command::
|
||||
|
||||
$ ls -al ~/.ssh
|
||||
|
||||
If you see you already have existing public and private keys that you
|
||||
want to use, you can skip this step; otherwise::
|
||||
|
||||
$ ssh-keygen -m PEM -t rsa -b 4096 -C "youropenstackid"
|
||||
|
||||
The `youropenstackid` string is the username you chose when you
|
||||
registered for your OpenStack ID account. Enter the file name in which
|
||||
to save the key (``/home/you/.ssh/id\_rsa``), then press enter. You will be
|
||||
asked to enter a passphrase. Just press enter again as passphrase
|
||||
protected keys currently aren't supported. Your ssh keys will then be
|
||||
generated.
|
||||
|
||||
Sign Key with RefStack Client
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**IMPORTANT** You must have the RefStack client on you computer to
|
||||
complete this step.
|
||||
Follow `its README <https://opendev.org/openinfra/refstack-client>`__ on how to
|
||||
install it.
|
||||
|
||||
Generate a signature for your public key using your private key with
|
||||
`refstack-client <https://opendev.org/openinfra/refstack-client>`__::
|
||||
|
||||
$ ./refstack-client sign /path-of-sshkey-folder/key-file-name
|
||||
|
||||
The ``/path-of-sshkey-folder`` string is the path of the folder where the
|
||||
generated ssh keys are stored locally. The 'key-file-name' portion
|
||||
refers to the private key file name. If the command runs correctly,
|
||||
there will be output like below:
|
||||
|
||||
::
|
||||
|
||||
Public key:
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDSGo2xNDcII1ZaM3H2uKh3iXBzvKIOa5W/5HxKF23yrzwho7nR7td0kgFtZ/4fe0zmkkUuKdUhOACCD3QVyi1N5wIhKAYN1fGt0/305jk7VJ+yYhUPlvo...
|
||||
|
||||
Self signature:
|
||||
19c28fc07e3fbe1085578bd6db2f1f75611dcd2ced068a2195bbca60ae98af7e27faa5b6968c3c5aef58b3fa91bae3df3...
|
||||
|
||||
Upload the ssh public key and the signature
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Sign into https://refstack.openstack.org with your OpenStack ID. Click
|
||||
the `Profile` link in the upper right corner. Now click the `Import
|
||||
public key` button on your profile page. A popup window with two entry
|
||||
fields will appear. Just copy and paste the key and signature generated
|
||||
in the previous step into the corresponding textboxes.
|
||||
|
||||
Note that the literal strings `Public key:` and `Self signature:` from
|
||||
the ``refstack-client sign`` command output **should not** be copied/pasted
|
||||
into the text boxes. Otherwise you will get an error like::
|
||||
|
||||
Bad Request Request doesn't correspond to schema
|
||||
|
||||
Once complete, click the `Import public key` button.
|
||||
|
||||
Upload the test result with refstack-client
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The results can be uploaded using the ``refstack-client`` by::
|
||||
|
||||
$ ./refstack-client upload /path_to_testresult_json_file \
|
||||
--url https://refstack.openstack.org/api -i ~/.ssh/id_rsa
|
||||
|
||||
**NOTE** Users may need to add the `--insecure` optional argument
|
||||
to the command string if certificate validation issues occur when
|
||||
uploading test result. Usage with the insecure argument::
|
||||
|
||||
$ ./refstack-client upload --insecure /path_to_testresult_json_file \
|
||||
--url https://refstack.openstack.org/api -i ~/.ssh/id_rsa``
|
||||
|
||||
The ``path_to_testresult_json_file`` there is the json file of the test result.
|
||||
By default, it's in ``.tempest/.stestr/<test-run-number>.json``. If the command
|
||||
runs correctly, there will be output like below:
|
||||
|
||||
::
|
||||
|
||||
Test results will be uploaded to https://refstack.openstack.org/api. Ok? (yes/y): y
|
||||
Test results uploaded!
|
||||
URL: https://refstack.openstack.org/#/results/88a1e6f4-707d-4627-b658-b14b7e6ba70d.
|
||||
|
||||
You can find your uploaded test results by clicking the `My Results`
|
||||
link on the RefStack website.
|
||||
@@ -1,36 +0,0 @@
|
||||
==============
|
||||
Product entity
|
||||
==============
|
||||
|
||||
Any user who has successfully authenticated to the RefStack server can create
|
||||
product entities. The minimum information needed to create a product entity is
|
||||
as follows:
|
||||
|
||||
- Name
|
||||
|
||||
This is the name of the product entity being created.
|
||||
|
||||
- Product type:
|
||||
|
||||
Product types are defined by OpenStack as shown on the OpenStack Marketplace
|
||||
( https://www.openstack.org/marketplace/ ). Currently, there are three types
|
||||
of products, namely: Distro & Appliances, Hosted Private Clouds and Public
|
||||
Clouds.
|
||||
|
||||
- Vendor
|
||||
|
||||
This is the vendor which owns the product. A default vendor will be created
|
||||
for the user if no vendor entity exists for this user.
|
||||
|
||||
Whenever a product is created, by default, it is a private product and is only
|
||||
visible to its vendor users. Vendor users can make a product publicly visible
|
||||
as needed later. However, only products that are owned by official vendors can
|
||||
be made publicly visible.
|
||||
|
||||
Product version
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
A default version is created whenever a product is created. The name of the
|
||||
default version is blank. The default version is used for products that have
|
||||
no version. Users can add new product versions to the product as needed.
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
=============
|
||||
Vendor entity
|
||||
=============
|
||||
|
||||
Any user who has successfully authenticated to the RefStack server can create
|
||||
vendor entities. The minimum required information to create a vendor is the
|
||||
vendor name. Users can update the rest of the vendor related information at a
|
||||
later time.
|
||||
|
||||
Vendor admin
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Whenever a user creates a vendor, this user will be added as the vendor's first
|
||||
vendor admin. Subsequently, any admin of the vendor can add additional users to
|
||||
the vendor. In RefStack, the "OpenStack User ID" of users are used as the
|
||||
identities for adding users to vendors. At the time this document is written,
|
||||
RefStack has not implemented user roles, and as such, all users of a vendor are
|
||||
admin users.
|
||||
|
||||
Vendor types
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
There are four types of vendor entities in RefStack:
|
||||
|
||||
- Foundation:
|
||||
|
||||
This is a special entity representing the OpenStack Foundation. Users belong
|
||||
to this entity are the Foundation admins. Foundation admins have visibility
|
||||
to all vendors and products.
|
||||
|
||||
- Private:
|
||||
|
||||
A vendor will always be created with type "private". Vendors of this type
|
||||
are only visible to their own users and Foundation admins. Vendor users can
|
||||
initiate a registration request to the Foundation to change its type from
|
||||
"private" to "official".
|
||||
|
||||
- Pending
|
||||
|
||||
Once a registration request is submitted, the vendor type will be changed
|
||||
automatically from type "private" to "pending". Vendors of this type are
|
||||
still only visible to their own users and Foundation admins.
|
||||
|
||||
- Official
|
||||
|
||||
Once a vendor registration request is approved by the Foundation. The vendor
|
||||
type will be changed from "pending" to "official". Official vendors are
|
||||
visible to all RefStack users.
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
Vendor and product management
|
||||
=============================
|
||||
|
||||
RefStack has implemented a vendor and product registration process so that test
|
||||
results can be associated to products of vendors. The creation and management
|
||||
of vendor and product entities can be done using the RefStack Server UI or
|
||||
RefStack APIs. The following is a quick guide outlining the information related
|
||||
to the creation and management of those entities.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:includehidden:
|
||||
|
||||
VendorEntity
|
||||
ProductEntity
|
||||
@@ -1,392 +0,0 @@
|
||||
[DEFAULT]
|
||||
|
||||
#
|
||||
# From oslo.log
|
||||
#
|
||||
|
||||
# If set to true, the logging level will be set to DEBUG instead of
|
||||
# the default INFO level. (boolean value)
|
||||
# Note: This option can be changed without restarting.
|
||||
#debug = false
|
||||
|
||||
# The name of a logging configuration file. This file is appended to
|
||||
# any existing logging configuration files. For details about logging
|
||||
# configuration files, see the Python logging module documentation.
|
||||
# Note that when logging configuration files are used then all logging
|
||||
# configuration is set in the configuration file and other logging
|
||||
# configuration options are ignored (for example, log-date-format).
|
||||
# (string value)
|
||||
# Note: This option can be changed without restarting.
|
||||
# Deprecated group/name - [DEFAULT]/log_config
|
||||
#log_config_append = <None>
|
||||
|
||||
# Defines the format string for %%(asctime)s in log records. Default:
|
||||
# %(default)s . This option is ignored if log_config_append is set.
|
||||
# (string value)
|
||||
#log_date_format = %Y-%m-%d %H:%M:%S
|
||||
|
||||
# (Optional) Name of log file to send logging output to. If no default
|
||||
# is set, logging will go to stderr as defined by use_stderr. This
|
||||
# option is ignored if log_config_append is set. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/logfile
|
||||
#log_file = <None>
|
||||
|
||||
# (Optional) The base directory used for relative log_file paths.
|
||||
# This option is ignored if log_config_append is set. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/logdir
|
||||
#log_dir = <None>
|
||||
|
||||
# Uses logging handler designed to watch file system. When log file is
|
||||
# moved or removed this handler will open a new log file with
|
||||
# specified path instantaneously. It makes sense only if log_file
|
||||
# option is specified and Linux platform is used. This option is
|
||||
# ignored if log_config_append is set. (boolean value)
|
||||
#watch_log_file = false
|
||||
|
||||
# Use syslog for logging. Existing syslog format is DEPRECATED and
|
||||
# will be changed later to honor RFC5424. This option is ignored if
|
||||
# log_config_append is set. (boolean value)
|
||||
#use_syslog = false
|
||||
|
||||
# Enable journald for logging. If running in a systemd environment you
|
||||
# may wish to enable journal support. Doing so will use the journal
|
||||
# native protocol which includes structured metadata in addition to
|
||||
# log messages.This option is ignored if log_config_append is set.
|
||||
# (boolean value)
|
||||
#use_journal = false
|
||||
|
||||
# Syslog facility to receive log lines. This option is ignored if
|
||||
# log_config_append is set. (string value)
|
||||
#syslog_log_facility = LOG_USER
|
||||
|
||||
# Use JSON formatting for logging. This option is ignored if
|
||||
# log_config_append is set. (boolean value)
|
||||
#use_json = false
|
||||
|
||||
# Log output to standard error. This option is ignored if
|
||||
# log_config_append is set. (boolean value)
|
||||
#use_stderr = false
|
||||
|
||||
# Log output to Windows Event Log. (boolean value)
|
||||
#use_eventlog = false
|
||||
|
||||
# The amount of time before the log files are rotated. This option is
|
||||
# ignored unless log_rotation_type is set to "interval". (integer
|
||||
# value)
|
||||
#log_rotate_interval = 1
|
||||
|
||||
# Rotation interval type. The time of the last file change (or the
|
||||
# time when the service was started) is used when scheduling the next
|
||||
# rotation. (string value)
|
||||
# Possible values:
|
||||
# Seconds - <No description provided>
|
||||
# Minutes - <No description provided>
|
||||
# Hours - <No description provided>
|
||||
# Days - <No description provided>
|
||||
# Weekday - <No description provided>
|
||||
# Midnight - <No description provided>
|
||||
#log_rotate_interval_type = days
|
||||
|
||||
# Maximum number of rotated log files. (integer value)
|
||||
#max_logfile_count = 30
|
||||
|
||||
# Log file maximum size in MB. This option is ignored if
|
||||
# "log_rotation_type" is not set to "size". (integer value)
|
||||
#max_logfile_size_mb = 200
|
||||
|
||||
# Log rotation type. (string value)
|
||||
# Possible values:
|
||||
# interval - Rotate logs at predefined time intervals.
|
||||
# size - Rotate logs once they reach a predefined size.
|
||||
# none - Do not rotate log files.
|
||||
#log_rotation_type = none
|
||||
|
||||
# Format string to use for log messages with context. Used by
|
||||
# oslo_log.formatters.ContextFormatter (string value)
|
||||
#logging_context_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s
|
||||
|
||||
# Format string to use for log messages when context is undefined.
|
||||
# Used by oslo_log.formatters.ContextFormatter (string value)
|
||||
#logging_default_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
|
||||
|
||||
# Additional data to append to log message when logging level for the
|
||||
# message is DEBUG. Used by oslo_log.formatters.ContextFormatter
|
||||
# (string value)
|
||||
#logging_debug_format_suffix = %(funcName)s %(pathname)s:%(lineno)d
|
||||
|
||||
# Prefix each line of exception output with this format. Used by
|
||||
# oslo_log.formatters.ContextFormatter (string value)
|
||||
#logging_exception_prefix = %(asctime)s.%(msecs)03d %(process)d ERROR %(name)s %(instance)s
|
||||
|
||||
# Defines the format string for %(user_identity)s that is used in
|
||||
# logging_context_format_string. Used by
|
||||
# oslo_log.formatters.ContextFormatter (string value)
|
||||
#logging_user_identity_format = %(user)s %(tenant)s %(domain)s %(user_domain)s %(project_domain)s
|
||||
|
||||
# List of package logging levels in logger=LEVEL pairs. This option is
|
||||
# ignored if log_config_append is set. (list value)
|
||||
#default_log_levels = amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,oslo_messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN,urllib3.connectionpool=WARN,websocket=WARN,requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN,keystonemiddleware=WARN,routes.middleware=WARN,stevedore=WARN,taskflow=WARN,keystoneauth=WARN,oslo.cache=INFO,oslo_policy=INFO,dogpile.core.dogpile=INFO
|
||||
|
||||
# Enables or disables publication of error events. (boolean value)
|
||||
#publish_errors = false
|
||||
|
||||
# The format for an instance that is passed with the log message.
|
||||
# (string value)
|
||||
#instance_format = "[instance: %(uuid)s] "
|
||||
|
||||
# The format for an instance UUID that is passed with the log message.
|
||||
# (string value)
|
||||
#instance_uuid_format = "[instance: %(uuid)s] "
|
||||
|
||||
# Interval, number of seconds, of log rate limiting. (integer value)
|
||||
#rate_limit_interval = 0
|
||||
|
||||
# Maximum number of logged messages per rate_limit_interval. (integer
|
||||
# value)
|
||||
#rate_limit_burst = 0
|
||||
|
||||
# Log level name used by rate limiting: CRITICAL, ERROR, INFO,
|
||||
# WARNING, DEBUG or empty string. Logs with level greater or equal to
|
||||
# rate_limit_except_level are not filtered. An empty string means that
|
||||
# all levels are filtered. (string value)
|
||||
#rate_limit_except_level = CRITICAL
|
||||
|
||||
# Enables or disables fatal status of deprecations. (boolean value)
|
||||
#fatal_deprecations = false
|
||||
|
||||
#
|
||||
# From refstack
|
||||
#
|
||||
|
||||
# Url of user interface for RefStack. Need for redirects after sign in
|
||||
# and sign out. (string value)
|
||||
#ui_url = https://refstack.openstack.org
|
||||
|
||||
# The backend to use for database. (string value)
|
||||
#db_backend = sqlalchemy
|
||||
|
||||
# The alembic version table name to use within the database. To allow
|
||||
# RefStack to upload and store the full set of subunit data, set this
|
||||
# option to refstack_alembic_version. (string value)
|
||||
#version_table = alembic_version
|
||||
|
||||
|
||||
[api]
|
||||
|
||||
#
|
||||
# From refstack
|
||||
#
|
||||
|
||||
# Url of public RefStack API. (string value)
|
||||
#api_url = https://refstack.openstack.org/api
|
||||
|
||||
# The directory where your static files can be found. Pecan comes
|
||||
# with middleware that can be used to serve static files (like CSS and
|
||||
# Javascript files) during development. Here, a special variable
|
||||
# %(project_root)s can be used to point to the root directory of the
|
||||
# Refstack project's module, so paths can be specified relative to
|
||||
# that. (string value)
|
||||
#static_root = refstack-ui/app
|
||||
|
||||
# Points to the directory where your template files live. Here, a
|
||||
# special variable %(project_root)s can be used to point to the root
|
||||
# directory of the Refstack project's main module, so paths can be
|
||||
# specified relative to that. (string value)
|
||||
#template_path = refstack-ui/app
|
||||
|
||||
# List of sites allowed cross-site resource access. If this is empty,
|
||||
# only same-origin requests are allowed. (list value)
|
||||
#allowed_cors_origins =
|
||||
|
||||
# Switch Refstack app into debug mode. Helpful for development. In
|
||||
# debug mode static file will be served by pecan application. Also,
|
||||
# server responses will contain some details with debug information.
|
||||
# (boolean value)
|
||||
#app_dev_mode = false
|
||||
|
||||
# Template for test result url. (string value)
|
||||
#test_results_url = /#/results/%s
|
||||
|
||||
# The GitHub API URL of the repository and location of the Interop
|
||||
# Working Group capability files. This URL is used to get a listing of
|
||||
# all capability files. (string value)
|
||||
#opendev_api_capabilities_url = https://opendev.org/api/v1/repos/openinfra/interop/contents/guidelines
|
||||
|
||||
# The GitHub API URL of the repository and location of any additional
|
||||
# guideline sources which will need to be parsed by the refstack API.
|
||||
# (string value)
|
||||
#additional_capability_urls = https://opendev.org/api/v1/repos/openinfra/interop/contents/add-ons/guidelines
|
||||
|
||||
# This is the base URL that is used for retrieving specific capability
|
||||
# files. Capability file names will be appended to this URL to get the
|
||||
# contents of that file. (string value)
|
||||
#opendev_raw_base_url = https://opendev.org/api/v1/repos/openinfra/interop/raw/
|
||||
|
||||
# Enable or disable anonymous uploads. If set to False, all clients
|
||||
# will need to authenticate and sign with a public/private keypair
|
||||
# previously uploaded to their user account. (boolean value)
|
||||
#enable_anonymous_upload = true
|
||||
|
||||
# Number of results for one page (integer value)
|
||||
#results_per_page = 20
|
||||
|
||||
# The format for start_date and end_date parameters (string value)
|
||||
#input_date_format = %Y-%m-%d %H:%M:%S
|
||||
|
||||
|
||||
[database]
|
||||
|
||||
#
|
||||
# From oslo.db
|
||||
#
|
||||
|
||||
# If True, SQLite uses synchronous mode. (boolean value)
|
||||
#sqlite_synchronous = true
|
||||
|
||||
# The back end to use for the database. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/db_backend
|
||||
#backend = sqlalchemy
|
||||
|
||||
# The SQLAlchemy connection string to use to connect to the database.
|
||||
# (string value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_connection
|
||||
# Deprecated group/name - [DATABASE]/sql_connection
|
||||
# Deprecated group/name - [sql]/connection
|
||||
#connection = <None>
|
||||
|
||||
# The SQLAlchemy connection string to use to connect to the slave
|
||||
# database. (string value)
|
||||
#slave_connection = <None>
|
||||
|
||||
# The SQL mode to be used for MySQL sessions. This option, including
|
||||
# the default, overrides any server-set SQL mode. To use whatever SQL
|
||||
# mode is set by the server configuration, set this to no value.
|
||||
# Example: mysql_sql_mode= (string value)
|
||||
#mysql_sql_mode = TRADITIONAL
|
||||
|
||||
# If True, transparently enables support for handling MySQL Cluster
|
||||
# (NDB). (boolean value)
|
||||
#mysql_enable_ndb = false
|
||||
|
||||
# Connections which have been present in the connection pool longer
|
||||
# than this number of seconds will be replaced with a new one the next
|
||||
# time they are checked out from the pool. (integer value)
|
||||
# Deprecated group/name - [DATABASE]/idle_timeout
|
||||
# Deprecated group/name - [database]/idle_timeout
|
||||
# Deprecated group/name - [DEFAULT]/sql_idle_timeout
|
||||
# Deprecated group/name - [DATABASE]/sql_idle_timeout
|
||||
# Deprecated group/name - [sql]/idle_timeout
|
||||
#connection_recycle_time = 3600
|
||||
|
||||
# Maximum number of SQL connections to keep open in a pool. Setting a
|
||||
# value of 0 indicates no limit. (integer value)
|
||||
#max_pool_size = 5
|
||||
|
||||
# Maximum number of database connection retries during startup. Set to
|
||||
# -1 to specify an infinite retry count. (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_max_retries
|
||||
# Deprecated group/name - [DATABASE]/sql_max_retries
|
||||
#max_retries = 10
|
||||
|
||||
# Interval between retries of opening a SQL connection. (integer
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_retry_interval
|
||||
# Deprecated group/name - [DATABASE]/reconnect_interval
|
||||
#retry_interval = 10
|
||||
|
||||
# If set, use this value for max_overflow with SQLAlchemy. (integer
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_max_overflow
|
||||
# Deprecated group/name - [DATABASE]/sqlalchemy_max_overflow
|
||||
#max_overflow = 50
|
||||
|
||||
# Verbosity of SQL debugging information: 0=None, 100=Everything.
|
||||
# (integer value)
|
||||
# Minimum value: 0
|
||||
# Maximum value: 100
|
||||
# Deprecated group/name - [DEFAULT]/sql_connection_debug
|
||||
#connection_debug = 0
|
||||
|
||||
# Add Python stack traces to SQL as comment strings. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_connection_trace
|
||||
#connection_trace = false
|
||||
|
||||
# If set, use this value for pool_timeout with SQLAlchemy. (integer
|
||||
# value)
|
||||
# Deprecated group/name - [DATABASE]/sqlalchemy_pool_timeout
|
||||
#pool_timeout = <None>
|
||||
|
||||
# Enable the experimental use of database reconnect on connection
|
||||
# lost. (boolean value)
|
||||
#use_db_reconnect = false
|
||||
|
||||
# Seconds between retries of a database transaction. (integer value)
|
||||
#db_retry_interval = 1
|
||||
|
||||
# If True, increases the interval between retries of a database
|
||||
# operation up to db_max_retry_interval. (boolean value)
|
||||
#db_inc_retry_interval = true
|
||||
|
||||
# If db_inc_retry_interval is set, the maximum seconds between retries
|
||||
# of a database operation. (integer value)
|
||||
#db_max_retry_interval = 10
|
||||
|
||||
# Maximum retries in case of connection error or deadlock error before
|
||||
# error is raised. Set to -1 to specify an infinite retry count.
|
||||
# (integer value)
|
||||
#db_max_retries = 20
|
||||
|
||||
# Optional URL parameters to append onto the connection URL at connect
|
||||
# time; specify as param1=value1¶m2=value2&... (string value)
|
||||
#connection_parameters =
|
||||
|
||||
|
||||
[osid]
|
||||
|
||||
#
|
||||
# From refstack
|
||||
#
|
||||
|
||||
# OpenStackID Auth Server URI. (string value)
|
||||
#openstack_openid_endpoint = https://openstackid.org/accounts/openid2
|
||||
|
||||
# OpenStackID logout URI. (string value)
|
||||
#openid_logout_endpoint = https://openstackid.org/accounts/user/logout
|
||||
|
||||
# Interaction mode. Specifies whether Openstack Id IdP may interact
|
||||
# with the user to determine the outcome of the request. (string
|
||||
# value)
|
||||
#openid_mode = checkid_setup
|
||||
|
||||
# Protocol version. Value identifying the OpenID protocol version
|
||||
# being used. This value should be "http://specs.openid.net/auth/2.0".
|
||||
# (string value)
|
||||
#openid_ns = http://specs.openid.net/auth/2.0
|
||||
|
||||
# Return endpoint in Refstack's API. Value indicating the endpoint
|
||||
# where the user should be returned to after signing in. Openstack Id
|
||||
# Idp only supports HTTPS address types. (string value)
|
||||
#openid_return_to = /v1/auth/signin_return
|
||||
|
||||
# Claimed identifier. This value must be set to
|
||||
# "http://specs.openid.net/auth/2.0/identifier_select". or to user
|
||||
# claimed identity (user local identifier or user owned identity [ex:
|
||||
# custom html hosted on a owned domain set to html discover]). (string
|
||||
# value)
|
||||
#openid_claimed_id = http://specs.openid.net/auth/2.0/identifier_select
|
||||
|
||||
# Alternate identifier. This value must be set to
|
||||
# http://specs.openid.net/auth/2.0/identifier_select. (string value)
|
||||
#openid_identity = http://specs.openid.net/auth/2.0/identifier_select
|
||||
|
||||
# Indicates request for user attribute information. This value must be
|
||||
# set to "http://openid.net/extensions/sreg/1.1". (string value)
|
||||
#openid_ns_sreg = http://openid.net/extensions/sreg/1.1
|
||||
|
||||
# Comma-separated list of field names which, if absent from the
|
||||
# response, will prevent the Consumer from completing the registration
|
||||
# without End User interation. The field names are those that are
|
||||
# specified in the Response Format, with the "openid.sreg." prefix
|
||||
# removed. Valid values include: "country", "email", "firstname",
|
||||
# "language", "lastname" (string value)
|
||||
#openid_sreg_required = email,fullname
|
||||
42
package.json
42
package.json
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"version": "0.5.0",
|
||||
"private": true,
|
||||
"name": "refstack-ui",
|
||||
"description": "A user interface for RefStack",
|
||||
"license": "Apache2",
|
||||
"devDependencies": {
|
||||
"eslint": "^3.0.0",
|
||||
"eslint-config-openstack": "4.0.1",
|
||||
"eslint-plugin-angular": "1.4.0",
|
||||
"jasmine-core": "2.8.0",
|
||||
"karma": "^1.7.1",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-cli": "1.0.1",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"angular-mocks": "^1.3.15"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "yarn",
|
||||
"pretest": "yarn",
|
||||
"test": "if [ -z $CHROME_BIN ];then export CHROME_BIN=/usr/bin/chromium-browser;fi && karma start ./refstack-ui/tests/karma.conf.js --single-run",
|
||||
"test-auto-watch": "if [ -z $CHROME_BIN ];then export CHROME_BIN=/usr/bin/chromium-browser;fi && karma start ./refstack-ui/tests/karma.conf.js --auto-watch",
|
||||
"lint": "eslint -c ./.eslintrc --no-color ./refstack-ui",
|
||||
"postinstall": "node -e \"try { require('fs').symlinkSync(require('path').resolve('node_modules/@bower_components'), 'refstack-ui/app/assets/lib', 'junction') } catch (e) { }\""
|
||||
},
|
||||
"dependencies": {
|
||||
"bower-away": "^1.1.2",
|
||||
"@bower_components/angular": "angular/bower-angular#1.3.15",
|
||||
"@bower_components/angular-animate": "angular/bower-angular-animate#~1.3",
|
||||
"@bower_components/angular-bootstrap": "angular-ui/bootstrap-bower#0.14.3",
|
||||
"@bower_components/angular-busy": "cgross/angular-busy#4.1.3",
|
||||
"@bower_components/angular-confirm-modal": "Schlogen/angular-confirm#1.2.3",
|
||||
"@bower_components/angular-mocks": "angular/bower-angular-mocks#1.3.15",
|
||||
"@bower_components/angular-resource": "angular/bower-angular-resource#1.3.15",
|
||||
"@bower_components/angular-ui-router": "angular-ui/angular-ui-router-bower#0.2.13",
|
||||
"@bower_components/bootstrap": "twbs/bootstrap#3.3.2",
|
||||
"@bower_components/jquery": "jquery/jquery-dist#>= 1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"yarn": ">= 1.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
Playbook for running refstack locally
|
||||
######################################
|
||||
|
||||
The playbook is meant for developers to help them with debugging and
|
||||
reviewing new changes in the refstack project.
|
||||
|
||||
The playbook semi-automates running the refstack server on the localhost.
|
||||
It downloads refstack role and templates from
|
||||
`system-config <https://opendev.org/opendev/system-config.git>`__ repository
|
||||
which is used for deploying and maintaining upstream servers, one of which is
|
||||
refstack. Then it builds the refstack image and spins a container using the
|
||||
refstack role.
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
- hosts: localhost
|
||||
become: true
|
||||
gather_facts: true
|
||||
vars:
|
||||
# dir where refstack files for buidling an image, running a container
|
||||
# will be stored
|
||||
refstack_dir: /tmp/refstack-docker
|
||||
refstack_openid_endpoint: ''
|
||||
# ip address of the machine you're running this on
|
||||
refstack_url: # 'http://<IP address of your server>'
|
||||
# the default credentials for the refstack database
|
||||
refstack_db_username: refstack
|
||||
refstack_db_password: Jz4ooq9TL7nc3hX3
|
||||
refstack_root_db_password: KbgY3r9HYnEYpgRP
|
||||
tasks:
|
||||
- name: Clone system-config repository
|
||||
git:
|
||||
repo: https://opendev.org/opendev/system-config.git
|
||||
dest: "{{ refstack_dir }}/system-config"
|
||||
|
||||
- name: Extract docker files
|
||||
copy:
|
||||
src: "{{ refstack_dir }}/system-config/docker/refstack/"
|
||||
dest: "{{ refstack_dir }}/refstack-docker-files"
|
||||
remote_src: yes
|
||||
|
||||
- name: Extract refstack role
|
||||
copy:
|
||||
src: "{{ refstack_dir }}/system-config/playbooks/roles/refstack/"
|
||||
dest: "{{ refstack_dir }}/refstack-role"
|
||||
remote_src: yes
|
||||
|
||||
- name: Delete the rest of system-config content
|
||||
file:
|
||||
state: absent
|
||||
path: "{{ refstack_dir }}/system-config"
|
||||
|
||||
- name: Install Docker
|
||||
apt:
|
||||
name:
|
||||
- docker
|
||||
- docker-compose
|
||||
state: present
|
||||
|
||||
- name: Build refstack image
|
||||
command: docker image build -f Dockerfile -t refstack:1 .
|
||||
args:
|
||||
chdir: "{{ refstack_dir }}/refstack-docker-files"
|
||||
|
||||
- include_role:
|
||||
name: "{{ refstack_dir }}/refstack-role"
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp', [
|
||||
'ui.router','ui.bootstrap', 'cgBusy',
|
||||
'ngResource', 'angular-confirm'
|
||||
]);
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.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: '/components/home/home.html'
|
||||
}).
|
||||
state('about', {
|
||||
url: '/about',
|
||||
templateUrl: '/components/about/about.html',
|
||||
controller: 'AboutController as ctrl'
|
||||
|
||||
}).
|
||||
state('guidelines', {
|
||||
url: '/guidelines',
|
||||
templateUrl: '/components/guidelines/guidelines.html',
|
||||
controller: 'GuidelinesController as ctrl'
|
||||
}).
|
||||
state('communityResults', {
|
||||
url: '/community_results',
|
||||
templateUrl: '/components/results/results.html',
|
||||
controller: 'ResultsController as ctrl'
|
||||
}).
|
||||
state('userResults', {
|
||||
url: '/user_results',
|
||||
templateUrl: '/components/results/results.html',
|
||||
controller: 'ResultsController as ctrl'
|
||||
}).
|
||||
state('resultsDetail', {
|
||||
url: '/results/:testID',
|
||||
templateUrl: '/components/results-report' +
|
||||
'/resultsReport.html',
|
||||
controller: 'ResultsReportController as ctrl'
|
||||
}).
|
||||
state('profile', {
|
||||
url: '/profile',
|
||||
templateUrl: '/components/profile/profile.html',
|
||||
controller: 'ProfileController as ctrl'
|
||||
}).
|
||||
state('authFailure', {
|
||||
url: '/auth_failure',
|
||||
templateUrl: '/components/home/home.html',
|
||||
controller: 'AuthFailureController as ctrl'
|
||||
}).
|
||||
state('logout', {
|
||||
url: '/logout',
|
||||
templateUrl: '/components/logout/logout.html',
|
||||
controller: 'LogoutController as ctrl'
|
||||
}).
|
||||
state('userVendors', {
|
||||
url: '/user_vendors',
|
||||
templateUrl: '/components/vendors/vendors.html',
|
||||
controller: 'VendorsController as ctrl'
|
||||
}).
|
||||
state('publicVendors', {
|
||||
url: '/public_vendors',
|
||||
templateUrl: '/components/vendors/vendors.html',
|
||||
controller: 'VendorsController as ctrl'
|
||||
}).
|
||||
state('vendor', {
|
||||
url: '/vendor/:vendorID',
|
||||
templateUrl: '/components/vendors/vendor.html',
|
||||
controller: 'VendorController as ctrl'
|
||||
}).
|
||||
state('userProducts', {
|
||||
url: '/user_products',
|
||||
templateUrl: '/components/products/products.html',
|
||||
controller: 'ProductsController as ctrl'
|
||||
}).
|
||||
state('publicProducts', {
|
||||
url: '/public_products',
|
||||
templateUrl: '/components/products/products.html',
|
||||
controller: 'ProductsController as ctrl'
|
||||
}).
|
||||
state('cloud', {
|
||||
url: '/cloud/:id',
|
||||
templateUrl: '/components/products/cloud.html',
|
||||
controller: 'ProductController as ctrl'
|
||||
}).
|
||||
state('distro', {
|
||||
url: '/distro/:id',
|
||||
templateUrl: '/components/products/distro.html',
|
||||
controller: 'ProductController as ctrl'
|
||||
});
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.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('refstackApp')
|
||||
.run(setup);
|
||||
|
||||
setup.$inject = [
|
||||
'$http', '$rootScope', '$window', '$state', 'refstackApiUrl'
|
||||
];
|
||||
|
||||
/**
|
||||
* Set up the app with injections into $rootscope. This is mainly for auth
|
||||
* functions.
|
||||
*/
|
||||
function setup($http, $rootScope, $window, $state, refstackApiUrl) {
|
||||
|
||||
$rootScope.auth = {};
|
||||
$rootScope.auth.doSignIn = doSignIn;
|
||||
$rootScope.auth.doSignOut = doSignOut;
|
||||
$rootScope.auth.doSignCheck = doSignCheck;
|
||||
|
||||
var sign_in_url = refstackApiUrl + '/auth/signin';
|
||||
var sign_out_url = refstackApiUrl + '/auth/signout';
|
||||
var profile_url = refstackApiUrl + '/profile';
|
||||
|
||||
/** This function initiates a sign in. */
|
||||
function doSignIn() {
|
||||
$window.location.href = sign_in_url;
|
||||
}
|
||||
|
||||
/** This function will initate a sign out. */
|
||||
function doSignOut() {
|
||||
$rootScope.auth.currentUser = null;
|
||||
$rootScope.auth.isAuthenticated = false;
|
||||
$window.location.href = sign_out_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}).
|
||||
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('refstackApp').constant(key, value);
|
||||
});
|
||||
|
||||
angular.bootstrap(document, ['refstackApp']);
|
||||
}
|
||||
|
||||
$http.get('config.json').success(function (data) {
|
||||
startApp(data);
|
||||
}).error(function () {
|
||||
startApp({});
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -1,251 +0,0 @@
|
||||
body {
|
||||
background: white;
|
||||
color: black;
|
||||
font-family: 'Helvetica Neue', 'Helvetica', 'Verdana', sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-size: 3em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.heading img {
|
||||
height: 50px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
input.error {
|
||||
background: #FAFF78;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Futura-CondensedExtraBold', 'Futura', 'Helvetica', sans-serif;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: none repeat scroll 0% 0% #333;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #1D6503;
|
||||
}
|
||||
|
||||
.advisory {
|
||||
color: #9F8501;
|
||||
}
|
||||
|
||||
.deprecated {
|
||||
color: #B03838;
|
||||
}
|
||||
|
||||
.removed {
|
||||
color: #801601;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
word-spacing: 20px;
|
||||
background: #F8F8F8;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.checkbox-test-list {
|
||||
word-spacing: normal;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.checkbox-test-list .info-hover {
|
||||
font-size: 12px;
|
||||
color: #878787;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.checkbox-verified {
|
||||
border: 1px solid #A9A9A9;
|
||||
text-align: center;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.capabilities {
|
||||
color: #4B4B4B;
|
||||
}
|
||||
|
||||
.capabilities .capability-list-item {
|
||||
border-bottom: 2px solid #AFAFAF;
|
||||
padding-bottom: .6em;
|
||||
}
|
||||
|
||||
.capabilities .capability-name {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#criteria {
|
||||
color: #4B4B4B;
|
||||
}
|
||||
|
||||
.criterion-name {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list-inline li:before {
|
||||
content: '\00BB';
|
||||
}
|
||||
|
||||
.program-about {
|
||||
font-size: .8em;
|
||||
padding-top: .3em;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.jumbotron .left {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.container .jumbotron {
|
||||
background: #F6F6F6;
|
||||
border-top: 2px solid #C9C9C9;
|
||||
border-bottom: 2px solid #C9C9C9;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.jumbotron .right {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.jumbotron img {
|
||||
width: 90%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
.jumbotron .openstack-intro__logo {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.result-filters {
|
||||
padding-bottom: 10px;
|
||||
border-top: 2px solid #C9C9C9;
|
||||
border-bottom: 2px solid #C9C9C9;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@media (min-width: 450px) {
|
||||
.jumbotron .openstack-intro__logo {
|
||||
width: 30%;
|
||||
}
|
||||
.openstack-intro__logo img {
|
||||
float: right;
|
||||
}
|
||||
.openstack-intro__content > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.openstack-intro__content > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.jumbotron.openstack-intro {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.yes {
|
||||
background: #1A911E;
|
||||
color: white;
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
}
|
||||
|
||||
.no {
|
||||
background: #BC0505;
|
||||
color: white;
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
}
|
||||
|
||||
.button-margin {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.tests-modal-content {
|
||||
overflow: auto;
|
||||
max-height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
.tests-modal-content textarea {
|
||||
font-size: .9em;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.test-detail {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.test-detail ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.test-detail-report {
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
a.glyphicon {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.test-list-dl {
|
||||
word-spacing: normal;
|
||||
}
|
||||
|
||||
.test-list-dl:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.modal-body .row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.about-sidebar {
|
||||
width: 20%;
|
||||
float: left;
|
||||
padding-right: 2px;
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
.about-content {
|
||||
width: 80%;
|
||||
float: left;
|
||||
padding-left: 5%;
|
||||
|
||||
}
|
||||
|
||||
.about-option {
|
||||
padding: 5px 5px 5px 10px;
|
||||
}
|
||||
|
||||
.about-active {
|
||||
background: #f2f2f2;
|
||||
border-left: 2px solid orange;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,13 +0,0 @@
|
||||
<hr>
|
||||
<div class="about-sidebar">
|
||||
<div ng-repeat="(key, data) in ctrl.options | arrayConverter | orderBy: 'order'">
|
||||
<a><div class="about-option"
|
||||
ng-click="ctrl.selectOption(data.id)"
|
||||
ng-class="{ 'about-active': ctrl.selected === data.id }">
|
||||
{{data.title}}
|
||||
</div></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="about-content">
|
||||
<div ng-include src="ctrl.template"></div>
|
||||
</div>
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('AboutController', AboutController);
|
||||
|
||||
AboutController.$inject = ['$location'];
|
||||
|
||||
/**
|
||||
* RefStack About Controller
|
||||
* This controller handles the about page and the multiple templates
|
||||
* associated to the page.
|
||||
*/
|
||||
function AboutController($location) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.selectOption = selectOption;
|
||||
ctrl.getHash = getHash;
|
||||
|
||||
ctrl.options = {
|
||||
'about' : {
|
||||
'title': 'About RefStack',
|
||||
'template': 'components/about/templates/overview.html',
|
||||
'order': 1
|
||||
},
|
||||
'uploading-your-results': {
|
||||
'title': 'Uploading Your Results',
|
||||
'template': 'components/about/templates/' +
|
||||
'uploading_private_results.html',
|
||||
'order': 2
|
||||
},
|
||||
'managing-results': {
|
||||
'title': 'Managing Results',
|
||||
'template': 'components/about/templates/' +
|
||||
'test_result_management.html',
|
||||
'order': 3
|
||||
},
|
||||
'vendors': {
|
||||
'title': 'Vendors',
|
||||
'template': 'components/about/templates/VendorEntity.html',
|
||||
'order': 4
|
||||
},
|
||||
'products': {
|
||||
'title': 'Products',
|
||||
'template': 'components/about/templates/ProductEntity.html',
|
||||
'order': 5
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an option key, mark it as selected and set the corresponding
|
||||
* template and URL hash.
|
||||
*/
|
||||
function selectOption(key) {
|
||||
ctrl.selected = key;
|
||||
ctrl.template = ctrl.options[key].template;
|
||||
$location.hash(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash in the URL and select it if possible.
|
||||
*/
|
||||
function getHash() {
|
||||
var hash = $location.hash();
|
||||
if (hash && hash in ctrl.options) {
|
||||
ctrl.selectOption(hash);
|
||||
} else {
|
||||
ctrl.selectOption('about');
|
||||
}
|
||||
}
|
||||
|
||||
ctrl.getHash();
|
||||
}
|
||||
})();
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('AuthFailureController', AuthFailureController);
|
||||
|
||||
AuthFailureController.$inject = ['$location', '$state', 'raiseAlert'];
|
||||
/**
|
||||
* Refstack Auth Failure Controller
|
||||
* This controller handles messages from Refstack 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');
|
||||
}
|
||||
})();
|
||||
@@ -1,85 +0,0 @@
|
||||
<h3>OpenStack Powered™ Guidelines</h3>
|
||||
|
||||
<!-- Guideline Filters -->
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<strong>Version:</strong>
|
||||
<!-- Slicing the version file name here gets rid of the '.json' file extension -->
|
||||
<select ng-model="ctrl.version"
|
||||
ng-change="ctrl.update()"
|
||||
class="form-control"
|
||||
ng-options="versionObj.name.slice(0,-5) for versionObj in ctrl.versionList">
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<strong>Target Program:</strong>
|
||||
<span class="program-about"><a target="_blank" href="http://www.openstack.org/brand/interop/">About</a></span>
|
||||
<select ng-model="ctrl.target" class="form-control" ng-change="ctrl.updateTargetCapabilities()">
|
||||
<option value="platform">OpenStack Powered Platform</option>
|
||||
<option value="compute">OpenStack Powered Compute</option>
|
||||
<option value="object">OpenStack Powered Object Storage</option>
|
||||
<option value="dns">OpenStack with DNS</option>
|
||||
<option value="orchestration">OpenStack with Orchestration</option>
|
||||
<option value="shared_file_system">OpenStack with Shared File System</option>
|
||||
<option value="load_balancer">OpenStack with Load Balancer</option>
|
||||
<option value="key_manager">OpenStack with Key Manager</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div ng-if="ctrl.guidelines">
|
||||
<strong>Guideline Status:</strong>
|
||||
{{ctrl.guidelineStatus | capitalize}}
|
||||
</div>
|
||||
|
||||
<div ng-show="ctrl.guidelines">
|
||||
<strong>Corresponding OpenStack Releases:</strong>
|
||||
<ul class="list-inline">
|
||||
<li ng-repeat="release in ctrl.releases">
|
||||
{{release | capitalize}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<strong>Capability Status:</strong>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="ctrl.status.required">
|
||||
<span class="required">Required</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" ng-model="ctrl.status.advisory">
|
||||
<span class="advisory">Advisory</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" ng-model="ctrl.status.deprecated">
|
||||
<span class="deprecated">Deprecated</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" ng-model="ctrl.status.removed">
|
||||
<span class="removed">Removed</span>
|
||||
</label>
|
||||
<a class="test-list-dl pull-right"
|
||||
title="Get a test list for capabilities matching selected statuses."
|
||||
ng-click="ctrl.openTestListModal()">
|
||||
|
||||
Test List <span class="glyphicon glyphicon-file"></span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- End Capability Filters -->
|
||||
|
||||
<p><small>Tests marked with <span class="glyphicon glyphicon-flag text-warning"></span> are tests flagged by Interop Working Group.</small></p>
|
||||
|
||||
<!-- Loading animation divs -->
|
||||
<div cg-busy="{promise:ctrl.versionsRequest,message:'Loading versions'}"></div>
|
||||
<div cg-busy="{promise:ctrl.capsRequest,message:'Loading capabilities'}"></div>
|
||||
|
||||
<!-- Get the version-specific template -->
|
||||
<div ng-include src="ctrl.detailsTemplate"></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>
|
||||
@@ -1,392 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('GuidelinesController', GuidelinesController);
|
||||
|
||||
GuidelinesController.$inject =
|
||||
['$filter', '$http', '$uibModal', 'refstackApiUrl'];
|
||||
|
||||
/**
|
||||
* RefStack Guidelines Controller
|
||||
* This controller is for the '/guidelines' page where a user can browse
|
||||
* through tests belonging to Interop WG defined capabilities.
|
||||
*/
|
||||
function GuidelinesController($filter ,$http, $uibModal, refstackApiUrl) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.getVersionList = getVersionList;
|
||||
ctrl.update = update;
|
||||
ctrl.updateTargetCapabilities = updateTargetCapabilities;
|
||||
ctrl.filterStatus = filterStatus;
|
||||
ctrl.getObjectLength = getObjectLength;
|
||||
ctrl.openTestListModal = openTestListModal;
|
||||
ctrl.updateVersionList = updateVersionList;
|
||||
ctrl.gl_type = 'powered';
|
||||
|
||||
/** The target OpenStack marketing program to show capabilities for. */
|
||||
ctrl.target = 'platform';
|
||||
|
||||
/** The various possible capability statuses. */
|
||||
ctrl.status = {
|
||||
required: true,
|
||||
advisory: false,
|
||||
deprecated: false,
|
||||
removed: false
|
||||
};
|
||||
|
||||
/**
|
||||
* The template to load for displaying capability details.
|
||||
*/
|
||||
ctrl.detailsTemplate = 'components/guidelines/partials/' +
|
||||
'guidelineDetails.html';
|
||||
|
||||
/**
|
||||
* Update the array of dictionary objects which stores data
|
||||
* pertaining to each guideline, sorting them in descending
|
||||
* order by guideline name. After these are sorted, the
|
||||
* function to update the capabilities is called.
|
||||
*/
|
||||
function updateVersionList() {
|
||||
let gl_files = ctrl.guidelineData[ctrl.gl_type];
|
||||
ctrl.versionList = $filter('orderBy')(gl_files, 'name', true);
|
||||
// Default to the first approved guideline which is expected
|
||||
// to be at index 1.
|
||||
ctrl.version = ctrl.versionList[1];
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a dictionary object comprised of available guideline types
|
||||
* and and an array of dictionary objects containing file info about
|
||||
* each guideline file pertaining to that particular guideline type.
|
||||
* After a successful API call, the function to sort and update the
|
||||
* version list is called.
|
||||
*/
|
||||
function getVersionList() {
|
||||
var content_url = refstackApiUrl + '/guidelines';
|
||||
ctrl.versionsRequest =
|
||||
$http.get(content_url).success(function (data) {
|
||||
ctrl.guidelineData = data;
|
||||
updateVersionList();
|
||||
}).error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error = 'Error retrieving version list: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API server to retrieve the JSON
|
||||
* content of the guideline file corresponding to the selected
|
||||
* version.
|
||||
*/
|
||||
function update() {
|
||||
ctrl.content_url = refstackApiUrl + '/guidelines/'
|
||||
+ ctrl.version.file;
|
||||
let get_params = {'gl_file': ctrl.version.file};
|
||||
ctrl.capsRequest =
|
||||
$http.get(ctrl.content_url, get_params).success(
|
||||
function (data) {
|
||||
ctrl.guidelines = data;
|
||||
if ('metadata' in data && data.metadata.schema >= '2.0') {
|
||||
ctrl.schema = data.metadata.schema;
|
||||
ctrl.criteria = data.metadata.scoring.criteria;
|
||||
ctrl.releases =
|
||||
data.metadata.os_trademark_approval.releases;
|
||||
ctrl.guidelineStatus =
|
||||
data.metadata.os_trademark_approval.status;
|
||||
} else {
|
||||
ctrl.schema = data.schema;
|
||||
ctrl.criteria = data.criteria;
|
||||
ctrl.releases = data.releases;
|
||||
ctrl.guidelineStatus = data.status;
|
||||
}
|
||||
ctrl.updateTargetCapabilities();
|
||||
|
||||
}).error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.guidelines = null;
|
||||
ctrl.error = 'Error retrieving guideline content: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will update the scope's 'targetCapabilities' object with
|
||||
* capabilities belonging to the selected OpenStack marketing program
|
||||
* (programs typically correspond to 'components' in the Interop WG
|
||||
* schema). Each capability will have its status mapped to it.
|
||||
*/
|
||||
function updateTargetCapabilities() {
|
||||
ctrl.targetCapabilities = {};
|
||||
var components = ctrl.guidelines.components;
|
||||
var targetCaps = ctrl.targetCapabilities;
|
||||
var targetComponents = null;
|
||||
|
||||
var old_type = ctrl.gl_type;
|
||||
if (ctrl.target === 'dns' ||
|
||||
ctrl.target === 'orchestration' ||
|
||||
ctrl.target === 'shared_file_system' ||
|
||||
ctrl.target === 'load_balancer' ||
|
||||
ctrl.target === 'key_manager'
|
||||
) {
|
||||
ctrl.gl_type = ctrl.target;
|
||||
} else {
|
||||
ctrl.gl_type = 'powered';
|
||||
}
|
||||
// If it has not been updated since the last program type change,
|
||||
// will need to update the list
|
||||
if (old_type !== ctrl.gl_type) {
|
||||
updateVersionList();
|
||||
return;
|
||||
}
|
||||
|
||||
// The 'platform' target is comprised of multiple components, so
|
||||
// we need to get the capabilities belonging to each of its
|
||||
// components.
|
||||
if (ctrl.target === 'platform' || ctrl.schema >= '2.0') {
|
||||
if ('add-ons' in ctrl.guidelines) {
|
||||
targetComponents = ['os_powered_' + ctrl.target];
|
||||
} else if (ctrl.schema >= '2.0') {
|
||||
var platformsMap = {
|
||||
'platform': 'OpenStack Powered Platform',
|
||||
'compute': 'OpenStack Powered Compute',
|
||||
'object': 'OpenStack Powered Storage'
|
||||
};
|
||||
|
||||
targetComponents = ctrl.guidelines.platforms[
|
||||
platformsMap[ctrl.target]].components.map(
|
||||
function(c) {
|
||||
return c.name;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
targetComponents = ctrl.guidelines.platform.required;
|
||||
}
|
||||
|
||||
// This will contain status priority values, where lower
|
||||
// values mean higher priorities.
|
||||
var statusMap = {
|
||||
required: 1,
|
||||
advisory: 2,
|
||||
deprecated: 3,
|
||||
removed: 4
|
||||
};
|
||||
|
||||
// For each component required for the platform program.
|
||||
angular.forEach(targetComponents, function (component) {
|
||||
// Get each capability list belonging to each status.
|
||||
var componentList = components[component];
|
||||
if (ctrl.schema >= '2.0') {
|
||||
componentList = componentList.capabilities;
|
||||
}
|
||||
angular.forEach(componentList,
|
||||
function (caps, status) {
|
||||
// For each capability.
|
||||
angular.forEach(caps, function(cap) {
|
||||
// If the capability has already been added.
|
||||
if (cap in targetCaps) {
|
||||
// If the status priority value is less
|
||||
// than the saved priority value, update
|
||||
// the value.
|
||||
if (statusMap[status] <
|
||||
statusMap[targetCaps[cap]]) {
|
||||
targetCaps[cap] = status;
|
||||
}
|
||||
} else {
|
||||
targetCaps[cap] = status;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
angular.forEach(components[ctrl.target],
|
||||
function (caps, status) {
|
||||
angular.forEach(caps, function(cap) {
|
||||
targetCaps[cap] = status;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This filter will check if a capability's status corresponds
|
||||
* to a status that is checked/selected in the UI. This filter
|
||||
* is meant to be used with the ng-repeat directive.
|
||||
* @param {Object} capability
|
||||
* @returns {Boolean} True if capability's status is selected
|
||||
*/
|
||||
function filterStatus(capability) {
|
||||
var caps = ctrl.targetCapabilities;
|
||||
return ctrl.status.required &&
|
||||
caps[capability.id] === 'required' ||
|
||||
ctrl.status.advisory &&
|
||||
caps[capability.id] === 'advisory' ||
|
||||
ctrl.status.deprecated &&
|
||||
caps[capability.id] === 'deprecated' ||
|
||||
ctrl.status.removed &&
|
||||
caps[capability.id] === 'removed';
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will get the length of an Object/dict based on
|
||||
* the number of keys it has.
|
||||
* @param {Object} object
|
||||
* @returns {Number} length of object
|
||||
*/
|
||||
function getObjectLength(object) {
|
||||
return Object.keys(object).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will open the modal that will show a list of all tests
|
||||
* belonging to capabilities with the selected status(es).
|
||||
*/
|
||||
function openTestListModal() {
|
||||
$uibModal.open({
|
||||
templateUrl: '/components/guidelines/partials' +
|
||||
'/testListModal.html',
|
||||
backdrop: true,
|
||||
windowClass: 'modal',
|
||||
animation: true,
|
||||
controller: 'TestListModalController as modal',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
version: function () {
|
||||
return ctrl.version.name.slice(0, -5);
|
||||
},
|
||||
version_file: function() {
|
||||
return ctrl.version.file;
|
||||
},
|
||||
target: function () {
|
||||
return ctrl.target;
|
||||
},
|
||||
status: function () {
|
||||
return ctrl.status;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
ctrl.getVersionList();
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('TestListModalController', TestListModalController);
|
||||
|
||||
TestListModalController.$inject = [
|
||||
'$uibModalInstance', '$http', 'version',
|
||||
'version_file', 'target', 'status',
|
||||
'refstackApiUrl'
|
||||
];
|
||||
|
||||
/**
|
||||
* Test List Modal Controller
|
||||
* This controller is for the modal that appears if a user wants to see the
|
||||
* test list corresponding to Interop WG capabilities with the selected
|
||||
* statuses.
|
||||
*/
|
||||
function TestListModalController($uibModalInstance, $http, version,
|
||||
version_file, target, status, refstackApiUrl) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.version = version;
|
||||
ctrl.version_file = version_file;
|
||||
ctrl.target = target;
|
||||
ctrl.status = status;
|
||||
ctrl.close = close;
|
||||
ctrl.updateTestListString = updateTestListString;
|
||||
|
||||
ctrl.aliases = true;
|
||||
ctrl.flagged = false;
|
||||
|
||||
// Check if the API URL is absolute or relative.
|
||||
if (refstackApiUrl.indexOf('http') > -1) {
|
||||
ctrl.url = refstackApiUrl;
|
||||
} else {
|
||||
ctrl.url = location.protocol + '//' + location.host +
|
||||
refstackApiUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will close/dismiss the modal.
|
||||
*/
|
||||
function close() {
|
||||
$uibModalInstance.dismiss('exit');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will return a list of statuses based on which ones
|
||||
* are selected.
|
||||
*/
|
||||
function getStatusList() {
|
||||
var statusList = [];
|
||||
angular.forEach(ctrl.status, function(value, key) {
|
||||
if (value) {
|
||||
statusList.push(key);
|
||||
}
|
||||
});
|
||||
return statusList;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will get the list of tests from the API and update the
|
||||
* controller's test list string variable.
|
||||
*/
|
||||
function updateTestListString() {
|
||||
var statuses = getStatusList();
|
||||
if (!statuses.length) {
|
||||
ctrl.error = 'No tests matching selected criteria.';
|
||||
return;
|
||||
}
|
||||
ctrl.testListUrl = [
|
||||
ctrl.url, '/guidelines/', ctrl.version_file, '/tests?',
|
||||
'target=', ctrl.target, '&',
|
||||
'type=', statuses.join(','), '&',
|
||||
'alias=', ctrl.aliases.toString(), '&',
|
||||
'flag=', ctrl.flagged.toString()
|
||||
].join('');
|
||||
ctrl.testListRequest =
|
||||
$http.get(ctrl.testListUrl).
|
||||
then(function successCallback(response) {
|
||||
ctrl.error = null;
|
||||
ctrl.testListString = response.data;
|
||||
if (!ctrl.testListString) {
|
||||
ctrl.testListCount = 0;
|
||||
} else {
|
||||
ctrl.testListCount =
|
||||
ctrl.testListString.split('\n').length;
|
||||
}
|
||||
}, function errorCallback(response) {
|
||||
ctrl.testListString = null;
|
||||
ctrl.testListCount = null;
|
||||
if (angular.isObject(response.data) &&
|
||||
response.data.message) {
|
||||
ctrl.error = 'Error retrieving test list: ' +
|
||||
response.data.message;
|
||||
} else {
|
||||
ctrl.error = 'Unknown error retrieving test list.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateTestListString();
|
||||
}
|
||||
})();
|
||||
@@ -1,50 +0,0 @@
|
||||
<!--
|
||||
HTML for guidelines page for all OpenStack Powered (TM) guideline schemas
|
||||
This expects the JSON data of the guidelines file to be stored in scope
|
||||
variable 'guidelines'.
|
||||
-->
|
||||
|
||||
<ol ng-show="ctrl.guidelines" class="capabilities">
|
||||
<li class="capability-list-item" ng-repeat="capability in ctrl.guidelines.capabilities | arrayConverter | filter:ctrl.filterStatus | orderBy:'id'">
|
||||
<span class="capability-name">{{capability.id}}</span><br />
|
||||
<em>{{capability.description}}</em><br />
|
||||
Status: <span class="{{ctrl.targetCapabilities[capability.id]}}">{{ctrl.targetCapabilities[capability.id]}}</span><br />
|
||||
<span ng-if="capability.project">Project: {{capability.project | capitalize}}<br /></span>
|
||||
<a ng-click="showAchievements = !showAchievements">Achievements ({{capability.achievements.length}})</a><br />
|
||||
<ol uib-collapse="!showAchievements" class="list-inline">
|
||||
<li ng-repeat="achievement in capability.achievements">
|
||||
{{achievement}}
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<a ng-click="showTests = !showTests">Tests ({{ctrl.getObjectLength(capability.tests)}})</a>
|
||||
<ul uib-collapse="!showTests">
|
||||
<li ng-if="ctrl.schema === '1.2'" ng-repeat="test in capability.tests">
|
||||
<span ng-class="{'glyphicon glyphicon-flag text-warning': capability.flagged.indexOf(test) > -1}"></span>
|
||||
{{test}}
|
||||
</li>
|
||||
<li ng-if="ctrl.schema > '1.2'" ng-repeat="(testName, testDetails) in capability.tests">
|
||||
<span ng-class="{'glyphicon glyphicon-flag text-warning': testDetails.flagged}" title="{{testDetails.flagged.reason}}"></span>
|
||||
{{testName}}
|
||||
<div class="test-detail" ng-if="testDetails.aliases">
|
||||
<strong>Aliases:</strong>
|
||||
<ul><li ng-repeat="alias in testDetails.aliases">{{alias}}</li></ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div ng-show="ctrl.criteria" class="criteria">
|
||||
<hr>
|
||||
<h4><a ng-click="showCriteria = !showCriteria">Criteria</a></h4>
|
||||
<div uib-collapse="showCriteria">
|
||||
<ul>
|
||||
<li ng-repeat="(key, criterion) in ctrl.criteria">
|
||||
<span class="criterion-name">{{criterion.name}}</span><br />
|
||||
<em>{{criterion.Description || criterion.description}}</em><br />
|
||||
Weight: {{criterion.weight}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,46 +0,0 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">×</button>
|
||||
<h4>Test List ({{modal.testListCount}})</h4>
|
||||
<p>Use this test list with <a title="refstack-client" target="_blank"href="https://opendev.org/openinfra/refstack-client">refstack-client</a>
|
||||
to run only tests in the {{modal.version}} OpenStack Powered™ guideline from capabilities with the following statuses:
|
||||
</p>
|
||||
<ul class="list-inline">
|
||||
<li class="required" ng-if="modal.status.required"> Required</li>
|
||||
<li class="advisory" ng-if="modal.status.advisory"> Advisory</li>
|
||||
<li class="deprecated" ng-if="modal.status.deprecated"> Deprecated</li>
|
||||
<li class="removed" ng-if="modal.status.removed"> Removed</li>
|
||||
</ul>
|
||||
<div class="checkbox checkbox-test-list">
|
||||
<label><input type="checkbox" ng-model="modal.aliases" ng-change="modal.updateTestListString()">Aliases</label>
|
||||
<span class="glyphicon glyphicon-info-sign info-hover" aria-hidden="true"
|
||||
title="Include test aliases as tests may have been renamed over time. It does not hurt to include these."></span>
|
||||
|
||||
<label><input type="checkbox" ng-model="modal.flagged" ng-change="modal.updateTestListString()">Flagged</label>
|
||||
<span class="glyphicon glyphicon-info-sign info-hover" aria-hidden="true"
|
||||
title="Include flagged tests.">
|
||||
</span>
|
||||
</div>
|
||||
<p ng-hide="modal.error"> Alternatively, get the test list directly from the API on your CLI:</p>
|
||||
<code ng-hide="modal.error">wget "{{modal.testListUrl}}" -O {{modal.target}}.{{modal.version}}-test-list.txt</code>
|
||||
</div>
|
||||
<div class="modal-body tests-modal-content">
|
||||
<div cg-busy="{promise:modal.testListRequest,message:'Loading'}"></div>
|
||||
<div ng-show="modal.error" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
{{modal.error}}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="16" id="tests" wrap="off">{{modal.testListString}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a target="_blank" href="{{modal.testListUrl}}" download="{{modal.target + '.' + modal.version + '-test-list.txt'}}">
|
||||
<button class="btn btn-primary" ng-if="modal.testListCount > 0" type="button">
|
||||
Download
|
||||
</button>
|
||||
</a>
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,38 +0,0 @@
|
||||
<div class="jumbotron openstack-intro">
|
||||
<div class="pull-right right openstack-intro__logo">
|
||||
<img src="assets/img/openstack-logo.png" alt="OpenStack">
|
||||
</div>
|
||||
<div class="pull-left left openstack-intro__content">
|
||||
<h1>OpenStack Interoperability</h1>
|
||||
<p>RefStack is a source of tools for
|
||||
<a href="http://www.openstack.org/brand/interop/">OpenStack interoperability</a>
|
||||
testing.</p>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<h4>What is RefStack?</h4>
|
||||
<ul>
|
||||
<li>Toolset for testing interoperability between OpenStack clouds.</li>
|
||||
<li>Database backed website supporting collection and publication of
|
||||
community test results for OpenStack.</li>
|
||||
<li>User interface to display individual test run results.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<h4>OpenStack Marketing Programs</h4>
|
||||
<ul>
|
||||
<li>OpenStack Powered Platform</li>
|
||||
<li>OpenStack Powered Compute</li>
|
||||
<li>OpenStack Powered Object Storage</li>
|
||||
<li>OpenStack with DNS</li>
|
||||
<li>OpenStack with Orchestration</li>
|
||||
<li>OpenStack with Shared File System</li>
|
||||
<li>OpenStack with Load Balancer</li>
|
||||
<li>OpenStack with Key Manager</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<div cg-busy="{promise:ctrl.redirectWait,message:'Logging you out...'}"></div>
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('LogoutController', LogoutController);
|
||||
|
||||
LogoutController.$inject = [
|
||||
'$location', '$window', '$timeout'
|
||||
];
|
||||
|
||||
/**
|
||||
* Refstack 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);
|
||||
}
|
||||
})();
|
||||
@@ -1,34 +0,0 @@
|
||||
<h3>Cloud Product</h3>
|
||||
<div cg-busy="{promise:ctrl.productRequest,message:'Loading'}"></div>
|
||||
<div ng-show="ctrl.product" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="pull-left">
|
||||
<div class="test-report">
|
||||
<strong>Name:</strong> {{ctrl.product.name}}<br />
|
||||
<strong>Product ID:</strong> {{ctrl.id}}<br />
|
||||
<strong>Description:</strong> {{ctrl.product.description}}<br />
|
||||
<span ng-if="ctrl.nullVersion.cpid"><strong>CPID:</strong> {{ctrl.nullVersion.cpid}}<br /></span>
|
||||
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
|
||||
<strong>Vendor Name:</strong> <a ui-sref="vendor({vendorID: ctrl.vendor.id})">{{ctrl.vendor.name}}</a><br />
|
||||
<div ng-if="ctrl.productProperties">
|
||||
<strong>Properties:</strong>
|
||||
<ul>
|
||||
<li ng-repeat="(key, value) in ctrl.productProperties">
|
||||
<em>{{key}}</em>: {{value}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-include src="'components/products/partials/management.html'"></div>
|
||||
<div class="clearfix"></div>
|
||||
<div ng-include src="'components/products/partials/versions.html'"></div>
|
||||
<hr>
|
||||
<div ng-include src="'components/products/partials/testsTable.html'"></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>
|
||||
@@ -1,34 +0,0 @@
|
||||
<h3>Distro Product</h3>
|
||||
<div cg-busy="{promise:ctrl.productRequest,message:'Loading'}"></div>
|
||||
<div ng-show="ctrl.product" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="pull-left">
|
||||
<div class="test-report">
|
||||
<strong>Name:</strong> {{ctrl.product.name}}<br />
|
||||
<strong>Product ID:</strong> {{ctrl.id}}<br />
|
||||
<strong>Description:</strong> {{ctrl.product.description}}<br />
|
||||
<span ng-if="ctrl.nullVersion.cpid"><strong>CPID:</strong> {{ctrl.nullVersion.cpid}}<br /></span>
|
||||
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
|
||||
<strong>Vendor Name:</strong> <a ui-sref="vendor({vendorID: ctrl.vendor.id})">{{ctrl.vendor.name}}</a><br />
|
||||
<div ng-if="ctrl.productProperties">
|
||||
<strong>Properties:</strong>
|
||||
<ul>
|
||||
<li ng-repeat="(key, value) in ctrl.productProperties">
|
||||
<em>{{key}}</em>: {{value}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-include src="'components/products/partials/management.html'"></div>
|
||||
<div class="clearfix"></div>
|
||||
<div ng-include src="'components/products/partials/versions.html'"></div>
|
||||
<hr>
|
||||
<div ng-include src="'components/products/partials/testsTable.html'"></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>
|
||||
@@ -1,18 +0,0 @@
|
||||
<div class="pull-right">
|
||||
<a ng-if="ctrl.product.can_manage"
|
||||
href="javascript:void(0)"
|
||||
ng-click="ctrl.openProductEditModal()">
|
||||
Edit
|
||||
</a><br />
|
||||
<a ng-if="ctrl.product.can_manage"
|
||||
href="javascript:void(0)"
|
||||
ng-click="ctrl.deleteProduct()"
|
||||
confirm="Are you sure you want to delete {{ctrl.product.name}}?">
|
||||
Delete
|
||||
</a><br />
|
||||
<a ng-if="ctrl.product.can_manage && (ctrl.vendor.type == 0 || ctrl.vendor.type == 3)"
|
||||
href="javascript:void(0)" ng-click="ctrl.switchProductPublicity()"
|
||||
confirm="Are you sure you want to switch publicity of this product?">
|
||||
Make Product <span ng-if="ctrl.product.public">Private</span><span ng-if="!ctrl.product.public">Public</span>
|
||||
</a><br />
|
||||
</div>
|
||||
@@ -1,75 +0,0 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">×</button>
|
||||
<h4>Edit Product</h4>
|
||||
<p>Make changes to your product.</p>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="name"
|
||||
ng-model="modal.product.name">
|
||||
<br />
|
||||
<label for="description">Description</label>
|
||||
<textarea type="text"
|
||||
class="form-control"
|
||||
id="description"
|
||||
ng-model="modal.product.description"
|
||||
rows="4"
|
||||
wrap="off">
|
||||
</textarea>
|
||||
<br />
|
||||
<label for="properties">Properties</label>
|
||||
<small><span class="text-muted glyphicon glyphicon-info-sign" title="Add arbitrary custom properties to your product."></span></small>
|
||||
<div class="row" ng-repeat="(index, prop) in modal.productProperties">
|
||||
<div class="col-md-2">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
ng-model="prop.key">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
ng-model="prop.value">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<a class="text-danger glyphicon glyphicon-remove"
|
||||
title="Delete this property?"
|
||||
ng-click="modal.removeProperty(index)"
|
||||
style='top:8px'></a>
|
||||
</div>
|
||||
</div>
|
||||
<div><small><a ng-click="modal.addField()"><span class="glyphicon glyphicon-plus"></span> Add new property</a></small></div>
|
||||
<br />
|
||||
<div ng-if="modal.productVersion.id">
|
||||
<label for="name">Product CPID</label>
|
||||
<small>
|
||||
<span class="text-muted glyphicon glyphicon-info-sign"
|
||||
title="You can optionally associate a cloud provider ID to this product. This is used to automatically associate uploaded test results to the product.">
|
||||
</span>
|
||||
</small>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="cpid"
|
||||
ng-model="modal.productVersion.cpid">
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="modal.showError" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
{{modal.error}}
|
||||
</div>
|
||||
<div ng-show="modal.showSuccess" class="alert alert-success" role="success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<span class="sr-only">Success:</span>
|
||||
Changes saved successfully.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.saveChanges()">Save Changes</button>
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,142 +0,0 @@
|
||||
<h4><strong>Test Runs on Product</strong></h4>
|
||||
<div cg-busy="{promise:ctrl.testsRequest,message:'Loading'}"></div>
|
||||
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Upload Date</th>
|
||||
<th>Test Run ID</th>
|
||||
<th>Product Version</th>
|
||||
<th>Shared</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat-start="(index, result) in ctrl.testsData">
|
||||
<td>
|
||||
<a ng-if="!result.expanded"
|
||||
class="glyphicon glyphicon-plus"
|
||||
ng-click="result.expanded = true">
|
||||
</a>
|
||||
<a ng-if="result.expanded"
|
||||
class="glyphicon glyphicon-minus"
|
||||
ng-click="result.expanded = false">
|
||||
</a>
|
||||
</td>
|
||||
<td>{{result.created_at}}</td>
|
||||
<td><a ui-sref="resultsDetail({testID: result.id})">{{result.id}}</a></td>
|
||||
<td>{{result.product_version.version}}</td>
|
||||
<td>
|
||||
<span ng-show="result.meta.shared" class="glyphicon glyphicon-share"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="result.expanded" ng-repeat-end>
|
||||
<td></td>
|
||||
<td colspan="4">
|
||||
<strong>Publicly Shared:</strong>
|
||||
<span ng-if="result.meta.shared == 'true' && !result.sharedEdit">Yes</span>
|
||||
<span ng-if="!result.meta.shared && !result.sharedEdit">
|
||||
<em>No</em>
|
||||
</span>
|
||||
<select ng-if="result.sharedEdit"
|
||||
ng-model="result.meta.shared"
|
||||
class="form-inline">
|
||||
<option value="true">Yes</option>
|
||||
<option value="">No</option>
|
||||
</select>
|
||||
<a ng-if="!result.sharedEdit"
|
||||
ng-click="result.sharedEdit = true"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil"></a>
|
||||
<a ng-if="result.sharedEdit"
|
||||
ng-click="ctrl.associateTestMeta(index,'shared',result.meta.shared)"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk"></a>
|
||||
<br />
|
||||
|
||||
<strong>Associated Guideline:</strong>
|
||||
<span ng-if="!result.meta.guideline && !result.guidelineEdit">
|
||||
<em>None</em>
|
||||
</span>
|
||||
<span ng-if="result.meta.guideline && !result.guidelineEdit">
|
||||
{{result.meta.guideline.slice(0, -5)}}
|
||||
</span>
|
||||
<select ng-if="result.guidelineEdit"
|
||||
ng-model="result.meta.guideline"
|
||||
ng-options="o as o.slice(0, -5) for o in ctrl.versionList"
|
||||
class="form-inline">
|
||||
<option value="">None</option>
|
||||
</select>
|
||||
<a ng-if="!result.guidelineEdit"
|
||||
ng-click="ctrl.getGuidelineVersionList();result.guidelineEdit = true"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil"></a>
|
||||
<a ng-if="result.guidelineEdit"
|
||||
ng-click="ctrl.associateTestMeta(index, 'guideline', result.meta.guideline)"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk">
|
||||
</a>
|
||||
<br />
|
||||
|
||||
<strong>Associated Target Program:</strong>
|
||||
<span ng-if="!result.meta.target && !result.targetEdit">
|
||||
<em>None</em>
|
||||
</span>
|
||||
<span ng-if="result.meta.target && !result.targetEdit">
|
||||
{{ctrl.targetMappings[result.meta.target]}}</span>
|
||||
<select ng-if="result.targetEdit"
|
||||
ng-model="result.meta.target"
|
||||
class="form-inline">
|
||||
<option value="">None</option>
|
||||
<option value="platform">OpenStack Powered Platform</option>
|
||||
<option value="compute">OpenStack Powered Compute</option>
|
||||
<option value="object">OpenStack Powered Object Storage</option>
|
||||
<option value="dns">OpenStack with DNS</option>
|
||||
<option value="orchestration">OpenStack with Orchestration</option>
|
||||
<option value="shared_file_system">OpenStack with Shared File System</option>
|
||||
<option value="load_balancer">OpenStack with Load Balancer</option>
|
||||
<option value="key_manager">OpenStack with Key Manager</option>
|
||||
</select>
|
||||
<a ng-if="!result.targetEdit"
|
||||
ng-click="result.targetEdit = true"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil">
|
||||
</a>
|
||||
<a ng-if="result.targetEdit"
|
||||
ng-click="ctrl.associateTestMeta(index, 'target', result.meta.target)"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk">
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
<small>
|
||||
<a ng-click="ctrl.unassociateTest(index)"
|
||||
confirm="Are you sure you want to unassociate this test result with product: {{ctrl.product.name}}? Test result ownership will be given back to the original owner only.">
|
||||
<span class="glyphicon glyphicon-remove-circle" ></span> Unassociate test result from product
|
||||
</a>
|
||||
</small>
|
||||
</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.getProductTests()">
|
||||
</uib-pagination>
|
||||
</div>
|
||||
|
||||
<div ng-show="ctrl.showTestsError" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
{{ctrl.testsError}}
|
||||
</div>
|
||||
@@ -1,29 +0,0 @@
|
||||
<strong>Version(s) Available:</strong>
|
||||
<span ng-repeat="item in ctrl.productVersions | orderBy:'version'">
|
||||
<a ng-show="item.version && ctrl.product.can_manage" class="label label-info" ng-click="ctrl.openVersionModal(item)">
|
||||
{{item.version}}
|
||||
</a>
|
||||
<span ng-hide="ctrl.product.can_manage" class="label label-info">{{item.version}}</span>
|
||||
</span>
|
||||
|
||||
<a ng-if="ctrl.product.can_manage"
|
||||
title="Add a new product version."
|
||||
ng-click="ctrl.showNewVersionInput = true">
|
||||
<small><span class="glyphicon glyphicon-plus"></span></small>
|
||||
</a>
|
||||
<div ng-if="ctrl.showNewVersionInput" class="row" style="margin-top: 5px;">
|
||||
<div class="col-md-2">
|
||||
<div class="input-group">
|
||||
<input ng-model="ctrl.newProductVersion"
|
||||
type="text" class="form-control" placeholder="New Version">
|
||||
<span class="input-group-btn">
|
||||
<button
|
||||
class="btn btn-default"
|
||||
type="button"
|
||||
ng-click="ctrl.addProductVersion()">
|
||||
Add
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,51 +0,0 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>Manage Version</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="pull-left">
|
||||
<strong>Version:</strong> {{modal.version.version}}<br />
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<a class="glyphicon glyphicon-trash"
|
||||
ng-click="modal.deleteProductVersion()"
|
||||
confirm="Are you sure you want to delete product version {{modal.version.version}}?">
|
||||
</a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<br />
|
||||
(Optional) Associate cloud provider ID (CPID) with product version for easier
|
||||
test run associating.
|
||||
<br />
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<strong>CPID:</strong><br />
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" ng-model="modal.version.cpid" />
|
||||
<span class="input-group-btn">
|
||||
<button
|
||||
class="btn btn-default"
|
||||
type="button"
|
||||
ng-click="modal.saveChanges()">
|
||||
Save
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="modal.showError" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
{{modal.error}}
|
||||
</div>
|
||||
<div ng-show="modal.showSuccess" class="alert alert-success" role="success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<span class="sr-only">Success:</span>
|
||||
Updated Successfully.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,522 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('ProductController', ProductController);
|
||||
|
||||
ProductController.$inject = [
|
||||
'$scope', '$http', '$state', '$stateParams', '$window', '$uibModal',
|
||||
'refstackApiUrl', 'raiseAlert'
|
||||
];
|
||||
|
||||
/**
|
||||
* RefStack Product Controller
|
||||
* This controller is for the '/product/' details page where owner can
|
||||
* view details of the product.
|
||||
*/
|
||||
function ProductController($scope, $http, $state, $stateParams,
|
||||
$window, $uibModal, refstackApiUrl, raiseAlert) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.getProduct = getProduct;
|
||||
ctrl.getProductVersions = getProductVersions;
|
||||
ctrl.deleteProduct = deleteProduct;
|
||||
ctrl.deleteProductVersion = deleteProductVersion;
|
||||
ctrl.getProductTests = getProductTests;
|
||||
ctrl.switchProductPublicity = switchProductPublicity;
|
||||
ctrl.associateTestMeta = associateTestMeta;
|
||||
ctrl.getGuidelineVersionList = getGuidelineVersionList;
|
||||
ctrl.addProductVersion = addProductVersion;
|
||||
ctrl.unassociateTest = unassociateTest;
|
||||
ctrl.openVersionModal = openVersionModal;
|
||||
ctrl.openProductEditModal = openProductEditModal;
|
||||
|
||||
/** The product id extracted from the URL route. */
|
||||
ctrl.id = $stateParams.id;
|
||||
ctrl.productVersions = [];
|
||||
|
||||
if (!$scope.auth.isAuthenticated) {
|
||||
$state.go('home');
|
||||
}
|
||||
|
||||
/** Mappings of Interop WG components to marketing program names. */
|
||||
ctrl.targetMappings = {
|
||||
'platform': 'Openstack Powered Platform',
|
||||
'compute': 'OpenStack Powered Compute',
|
||||
'object': 'OpenStack Powered Object Storage',
|
||||
'dns': 'OpenStack with DNS',
|
||||
'orchestration': 'OpenStack with Orchestration',
|
||||
'shared_file_system': 'OpenStack with Shared File System',
|
||||
'load_balancer': 'OpenStack with Load Balancer',
|
||||
'key_manager': 'OpenStack with Key Manager'
|
||||
};
|
||||
|
||||
// Pagination controls.
|
||||
ctrl.currentPage = 1;
|
||||
ctrl.itemsPerPage = 20;
|
||||
ctrl.maxSize = 5;
|
||||
|
||||
ctrl.getProduct();
|
||||
ctrl.getProductVersions();
|
||||
ctrl.getProductTests();
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API to get a product information.
|
||||
*/
|
||||
function getProduct() {
|
||||
ctrl.showError = false;
|
||||
ctrl.product = null;
|
||||
var content_url = refstackApiUrl + '/products/' + ctrl.id;
|
||||
ctrl.productRequest = $http.get(content_url).success(
|
||||
function(data) {
|
||||
ctrl.product = data;
|
||||
ctrl.productProperties =
|
||||
angular.fromJson(data.properties);
|
||||
}
|
||||
).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving from server: ' +
|
||||
angular.toJson(error);
|
||||
}).then(function() {
|
||||
var url = refstackApiUrl + '/vendors/' +
|
||||
ctrl.product.organization_id;
|
||||
$http.get(url).success(function(data) {
|
||||
ctrl.vendor = data;
|
||||
}).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API to get product versions.
|
||||
*/
|
||||
function getProductVersions() {
|
||||
ctrl.showError = false;
|
||||
var content_url = refstackApiUrl + '/products/' + ctrl.id +
|
||||
'/versions';
|
||||
ctrl.productVersionsRequest = $http.get(content_url).success(
|
||||
function(data) {
|
||||
ctrl.productVersions = data;
|
||||
|
||||
// Determine the null version.
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (data[i].version === null) {
|
||||
ctrl.nullVersion = data[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving versions from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will delete the product.
|
||||
*/
|
||||
function deleteProduct() {
|
||||
var url = [refstackApiUrl, '/products/', ctrl.id].join('');
|
||||
$http.delete(url).success(function () {
|
||||
$window.location.href = '/';
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', 'Error: ', error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will delete the given product versions.
|
||||
*/
|
||||
function deleteProductVersion(versionId) {
|
||||
var url = [
|
||||
refstackApiUrl, '/products/', ctrl.id,
|
||||
'/versions/', versionId ].join('');
|
||||
$http.delete(url).success(function () {
|
||||
ctrl.getProductVersions();
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', 'Error: ', error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a POST request to the API server to add a new version for
|
||||
* the product.
|
||||
*/
|
||||
function addProductVersion() {
|
||||
var url = [refstackApiUrl, '/products/', ctrl.id,
|
||||
'/versions'].join('');
|
||||
ctrl.addVersionRequest = $http.post(url,
|
||||
{'version': ctrl.newProductVersion})
|
||||
.success(function (data) {
|
||||
ctrl.productVersions.push(data);
|
||||
ctrl.newProductVersion = '';
|
||||
ctrl.showNewVersionInput = false;
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tests runs associated with the current product.
|
||||
*/
|
||||
function getProductTests() {
|
||||
ctrl.showTestsError = false;
|
||||
var content_url = refstackApiUrl + '/results' +
|
||||
'?page=' + ctrl.currentPage + '&product_id='
|
||||
+ ctrl.id;
|
||||
|
||||
ctrl.testsRequest = $http.get(content_url).success(
|
||||
function(data) {
|
||||
ctrl.testsData = data.results;
|
||||
ctrl.totalItems = data.pagination.total_pages *
|
||||
ctrl.itemsPerPage;
|
||||
ctrl.currentPage = data.pagination.current_page;
|
||||
}
|
||||
).error(function(error) {
|
||||
ctrl.showTestsError = true;
|
||||
ctrl.testsError =
|
||||
'Error retrieving tests from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will switch public/private property of the product.
|
||||
*/
|
||||
function switchProductPublicity() {
|
||||
var url = [refstackApiUrl, '/products/', ctrl.id].join('');
|
||||
$http.put(url, {public: !ctrl.product.public}).success(
|
||||
function (data) {
|
||||
ctrl.product = data;
|
||||
ctrl.productProperties = angular.fromJson(data.properties);
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', 'Error: ', error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 associateTestMeta(index, key, value) {
|
||||
var testId = ctrl.testsData[index].id;
|
||||
var metaUrl = [
|
||||
refstackApiUrl, '/results/', testId, '/meta/', key
|
||||
].join('');
|
||||
|
||||
var editFlag = key + 'Edit';
|
||||
if (value) {
|
||||
ctrl.associateRequest = $http.post(metaUrl, value)
|
||||
.success(function () {
|
||||
ctrl.testsData[index][editFlag] = false;
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
} else {
|
||||
ctrl.unassociateRequest = $http.delete(metaUrl)
|
||||
.success(function () {
|
||||
ctrl.testsData[index][editFlag] = false;
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an array of available capability files from the Refstack
|
||||
* API server, sort this array reverse-alphabetically, and store it in
|
||||
* a scoped variable.
|
||||
* Sample API return array: ["2015.03.json", "2015.04.json"]
|
||||
*/
|
||||
function getGuidelineVersionList() {
|
||||
if (ctrl.versionList) {
|
||||
return;
|
||||
}
|
||||
var content_url = refstackApiUrl + '/guidelines';
|
||||
ctrl.versionsRequest =
|
||||
$http.get(content_url).success(function (data) {
|
||||
ctrl.versionList = data.sort().reverse();
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title,
|
||||
'Unable to retrieve version list');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PUT request to the API server to unassociate a product with
|
||||
* a test result.
|
||||
*/
|
||||
function unassociateTest(index) {
|
||||
var testId = ctrl.testsData[index].id;
|
||||
var url = refstackApiUrl + '/results/' + testId;
|
||||
ctrl.associateRequest = $http.put(url, {'product_version_id': null})
|
||||
.success(function () {
|
||||
ctrl.testsData.splice(index, 1);
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will open the modal that will allow a product version
|
||||
* to be managed.
|
||||
*/
|
||||
function openVersionModal(version) {
|
||||
$uibModal.open({
|
||||
templateUrl: '/components/products/partials' +
|
||||
'/versionsModal.html',
|
||||
backdrop: true,
|
||||
windowClass: 'modal',
|
||||
animation: true,
|
||||
controller: 'ProductVersionModalController as modal',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
version: function () {
|
||||
return version;
|
||||
},
|
||||
parent: function () {
|
||||
return ctrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will open the modal that will allow product details
|
||||
* to be edited.
|
||||
*/
|
||||
function openProductEditModal() {
|
||||
$uibModal.open({
|
||||
templateUrl: '/components/products/partials' +
|
||||
'/productEditModal.html',
|
||||
backdrop: true,
|
||||
windowClass: 'modal',
|
||||
animation: true,
|
||||
controller: 'ProductEditModalController as modal',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
product: function () {
|
||||
return ctrl.product;
|
||||
},
|
||||
version: function () {
|
||||
return ctrl.nullVersion;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('ProductVersionModalController',
|
||||
ProductVersionModalController);
|
||||
|
||||
ProductVersionModalController.$inject = [
|
||||
'$uibModalInstance', '$http', 'refstackApiUrl', 'version', 'parent'
|
||||
];
|
||||
|
||||
/**
|
||||
* Product Version Modal Controller
|
||||
* This controller is for the modal that appears if a user wants to
|
||||
* manage a product version.
|
||||
*/
|
||||
function ProductVersionModalController($uibModalInstance, $http,
|
||||
refstackApiUrl, version, parent) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.version = angular.copy(version);
|
||||
ctrl.parent = parent;
|
||||
|
||||
ctrl.close = close;
|
||||
ctrl.deleteProductVersion = deleteProductVersion;
|
||||
ctrl.saveChanges = saveChanges;
|
||||
|
||||
/**
|
||||
* This function will close/dismiss the modal.
|
||||
*/
|
||||
function close() {
|
||||
$uibModalInstance.dismiss('exit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the parent function to delete a version, then close the modal.
|
||||
*/
|
||||
function deleteProductVersion() {
|
||||
ctrl.parent.deleteProductVersion(ctrl.version.id);
|
||||
ctrl.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will update the current version, saving changes.
|
||||
*/
|
||||
function saveChanges() {
|
||||
ctrl.showSuccess = false;
|
||||
ctrl.showError = false;
|
||||
var url = [
|
||||
refstackApiUrl, '/products/', ctrl.version.product_id,
|
||||
'/versions/', ctrl.version.id ].join('');
|
||||
var content = {'cpid': ctrl.version.cpid};
|
||||
$http.put(url, content).success(function() {
|
||||
// Update the original version object.
|
||||
version.cpid = ctrl.version.cpid;
|
||||
ctrl.showSuccess = true;
|
||||
}).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error = error.detail;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('ProductEditModalController', ProductEditModalController);
|
||||
|
||||
ProductEditModalController.$inject = [
|
||||
'$uibModalInstance', '$http', '$state', 'product',
|
||||
'version', 'refstackApiUrl'
|
||||
];
|
||||
|
||||
/**
|
||||
* Product Edit Modal Controller
|
||||
* This controls the modal that allows editing a product.
|
||||
*/
|
||||
function ProductEditModalController($uibModalInstance, $http,
|
||||
$state, product, version, refstackApiUrl) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.close = close;
|
||||
ctrl.addField = addField;
|
||||
ctrl.saveChanges = saveChanges;
|
||||
ctrl.removeProperty = removeProperty;
|
||||
|
||||
ctrl.product = angular.copy(product);
|
||||
ctrl.productName = product.name;
|
||||
ctrl.productProperties = [];
|
||||
ctrl.productVersion = angular.copy(version);
|
||||
ctrl.originalCpid = version ? version.cpid : null;
|
||||
|
||||
parseProductProperties();
|
||||
|
||||
/**
|
||||
* Close the product edit modal.
|
||||
*/
|
||||
function close() {
|
||||
$uibModalInstance.dismiss('exit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a blank property key-value pair into the productProperties
|
||||
* array. This will spawn new input boxes.
|
||||
*/
|
||||
function addField() {
|
||||
ctrl.productProperties.push({'key': '', 'value': ''});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PUT request to the server with the changes.
|
||||
*/
|
||||
function saveChanges() {
|
||||
ctrl.showError = false;
|
||||
ctrl.showSuccess = false;
|
||||
var url = [refstackApiUrl, '/products/', ctrl.product.id].join('');
|
||||
var properties = propertiesToJson();
|
||||
var content = {'description': ctrl.product.description,
|
||||
'properties': properties};
|
||||
if (ctrl.productName !== ctrl.product.name) {
|
||||
content.name = ctrl.product.name;
|
||||
}
|
||||
|
||||
// Request for product detail updating.
|
||||
$http.put(url, content).success(function() {
|
||||
|
||||
// Request for product version CPID update if it has changed.
|
||||
if (ctrl.productVersion &&
|
||||
ctrl.originalCpid !== ctrl.productVersion.cpid) {
|
||||
|
||||
url = url + '/versions/' + ctrl.productVersion.id;
|
||||
content = {'cpid': ctrl.productVersion.cpid};
|
||||
$http.put(url, content).success(function() {
|
||||
ctrl.showSuccess = true;
|
||||
ctrl.originalCpid = ctrl.productVersion.cpid;
|
||||
$state.reload();
|
||||
}).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error = error.detail;
|
||||
});
|
||||
} else {
|
||||
ctrl.showSuccess = true;
|
||||
$state.reload();
|
||||
}
|
||||
}).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error = error.detail;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a property from the productProperties array at the given
|
||||
* index.
|
||||
*/
|
||||
function removeProperty(index) {
|
||||
ctrl.productProperties.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the product properties and put them in a format more suitable
|
||||
* for forms.
|
||||
*/
|
||||
function parseProductProperties() {
|
||||
var props = angular.fromJson(ctrl.product.properties);
|
||||
angular.forEach(props, function(value, key) {
|
||||
ctrl.productProperties.push({'key': key, 'value': value});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the list of property objects to a dict containing the
|
||||
* each key-value pair.
|
||||
*/
|
||||
function propertiesToJson() {
|
||||
if (!ctrl.productProperties.length) {
|
||||
return null;
|
||||
}
|
||||
var properties = {};
|
||||
for (var i = 0, len = ctrl.productProperties.length; i < len; i++) {
|
||||
var prop = ctrl.productProperties[i];
|
||||
if (prop.key && prop.value) {
|
||||
properties[prop.key] = prop.value;
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,85 +0,0 @@
|
||||
<h3>{{ctrl.pageHeader}}</h3>
|
||||
<p>{{ctrl.pageParagraph}}</p>
|
||||
|
||||
<div ng-show="ctrl.data" class="products-table">
|
||||
<label ng-if="ctrl.isAdminView && ctrl.isUserProducts">
|
||||
<input type="checkbox" ng-model="ctrl.withPrivate" ng-change="ctrl.updateData();"> Show private
|
||||
</label>
|
||||
<br />
|
||||
<table ng-show="ctrl.data" class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Product Type</th>
|
||||
<th>Description</th>
|
||||
<th>Vendor</th>
|
||||
<th>Visibility</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="product in ctrl.data.products">
|
||||
<td ng-if="ctrl.isUserProducts && product.product_type == 0"><a ui-sref="distro({id: product.id})">{{product.name}}</a></td>
|
||||
<td ng-if="ctrl.isUserProducts && product.product_type != 0"><a ui-sref="cloud({id: product.id})">{{product.name}}</a></td>
|
||||
<td ng-if="!ctrl.isUserProducts">{{product.name}}</td>
|
||||
<td>{{ctrl.getProductTypeDescription(product.product_type)}}</td>
|
||||
<td>{{product.description}}</td>
|
||||
<td>{{ctrl.allVendors[product.organization_id].name}}</td>
|
||||
<td>{{product.public ? 'Public' : 'Private'}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.isUserProducts">
|
||||
<hr />
|
||||
<h4>Add New Product</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<label>Name</label>
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control" ng-model="ctrl.name" />
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label>Description</label>
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control" size="80"
|
||||
ng-model="ctrl.description" />
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label>Product type:</label>
|
||||
<select ng-model="ctrl.productType" class="form-control">
|
||||
<option value="{{0}}">{{ctrl.getProductTypeDescription(0)}}</option>
|
||||
<option value="{{1}}">{{ctrl.getProductTypeDescription(1)}}</option>
|
||||
<option value="{{2}}">{{ctrl.getProductTypeDescription(2)}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label>Vendor:</label>
|
||||
<select ng-model="ctrl.organizationId" class="form-control">
|
||||
<option ng-repeat="vendor in ctrl.vendors" value="{{vendor.id}}">{{vendor.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2" style="margin-top:24px;">
|
||||
<button type="submit" class="btn btn-primary" ng-click="ctrl.addProduct()">Add Product</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="ctrl.showSuccess" class="alert alert-success" role="success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<span class="sr-only">Success:</span>
|
||||
Product successfully created.
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div cg-busy="{promise:ctrl.authRequest,message:'Loading'}"></div>
|
||||
<div cg-busy="{promise:ctrl.productsRequest,message:'Loading'}"></div>
|
||||
<div cg-busy="{promise:ctrl.vendorsRequest,message:'Loading'}"></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>
|
||||
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('ProductsController', ProductsController);
|
||||
|
||||
ProductsController.$inject = [
|
||||
'$rootScope', '$scope', '$http', '$state', 'refstackApiUrl'
|
||||
];
|
||||
|
||||
/**
|
||||
* RefStack Products Controller
|
||||
*/
|
||||
function ProductsController($rootScope, $scope, $http, $state,
|
||||
refstackApiUrl) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.update = update;
|
||||
ctrl.updateData = updateData;
|
||||
ctrl._filterProduct = _filterProduct;
|
||||
ctrl.addProduct = addProduct;
|
||||
ctrl.updateVendors = updateVendors;
|
||||
ctrl.getProductTypeDescription = getProductTypeDescription;
|
||||
|
||||
/** Check to see if this page should display user-specific products. */
|
||||
ctrl.isUserProducts = $state.current.name === 'userProducts';
|
||||
/** Show private products in list for foundation admin */
|
||||
ctrl.withPrivate = false;
|
||||
|
||||
/** Properties for adding new products */
|
||||
ctrl.name = '';
|
||||
ctrl.description = '';
|
||||
ctrl.organizationId = '';
|
||||
|
||||
// Should only be on user-products-page if authenticated.
|
||||
if (ctrl.isUserProducts && !$scope.auth.isAuthenticated) {
|
||||
$state.go('home');
|
||||
}
|
||||
|
||||
ctrl.pageHeader = ctrl.isUserProducts ?
|
||||
'My Products' : 'Public Products';
|
||||
|
||||
ctrl.pageParagraph = ctrl.isUserProducts ?
|
||||
'Your added products are listed here.' :
|
||||
'Public products are listed here.';
|
||||
|
||||
if (ctrl.isUserProducts) {
|
||||
ctrl.authRequest = $scope.auth.doSignCheck()
|
||||
.then(ctrl.updateVendors)
|
||||
.then(ctrl.update);
|
||||
} else {
|
||||
ctrl.updateVendors();
|
||||
ctrl.update();
|
||||
}
|
||||
|
||||
ctrl.rawData = null;
|
||||
ctrl.allVendors = {};
|
||||
ctrl.isAdminView = $rootScope.auth
|
||||
&& $rootScope.auth.currentUser
|
||||
&& $rootScope.auth.currentUser.is_admin;
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API to get a listing of products.
|
||||
*/
|
||||
function update() {
|
||||
ctrl.showError = false;
|
||||
// Construct the API URL based on user-specified filters.
|
||||
var contentUrl = refstackApiUrl + '/products';
|
||||
if (typeof ctrl.rawData === 'undefined'
|
||||
|| ctrl.rawData === null) {
|
||||
ctrl.productsRequest =
|
||||
$http.get(contentUrl).success(function (data) {
|
||||
ctrl.rawData = data;
|
||||
ctrl.updateData();
|
||||
}).error(function (error) {
|
||||
ctrl.rawData = null;
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving Products listing from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
} else {
|
||||
ctrl.updateData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will update data for view with current settings on page.
|
||||
*/
|
||||
function updateData() {
|
||||
ctrl.data = {};
|
||||
ctrl.data.products = ctrl.rawData.products.filter(function(s) {
|
||||
return ctrl._filterProduct(s);
|
||||
});
|
||||
ctrl.data.products.sort(function(a, b) {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a specific product can be displayed on this page.
|
||||
*/
|
||||
function _filterProduct(product) {
|
||||
if (!ctrl.isUserProducts) {
|
||||
return product.public;
|
||||
}
|
||||
|
||||
if ($rootScope.auth.currentUser.is_admin) {
|
||||
// TO-DO: filter out non-admin's items
|
||||
// because public is not a correct flag for this
|
||||
return product.public || ctrl.withPrivate;
|
||||
}
|
||||
|
||||
return product.can_manage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product type description given the type integer.
|
||||
*/
|
||||
function getProductTypeDescription(product_type) {
|
||||
switch (product_type) {
|
||||
case 0:
|
||||
return 'Distro';
|
||||
case 1:
|
||||
return 'Public Cloud';
|
||||
case 2:
|
||||
return 'Hosted Private Cloud';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API to get a listing of
|
||||
* available vendors that can be used to associate with products.
|
||||
*/
|
||||
function updateVendors() {
|
||||
// Construct the API URL based on user-specified filters.
|
||||
var contentUrl = refstackApiUrl + '/vendors';
|
||||
ctrl.vendorsRequest =
|
||||
$http.get(contentUrl).success(function (data) {
|
||||
ctrl.vendors = Array();
|
||||
ctrl.allVendors = {};
|
||||
data.vendors.forEach(function(vendor) {
|
||||
ctrl.allVendors[vendor.id] = vendor;
|
||||
if (vendor.can_manage) {
|
||||
ctrl.vendors.push(vendor);
|
||||
}
|
||||
});
|
||||
ctrl.vendors.sort(function(a, b) {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
if (ctrl.vendors.length === 0) {
|
||||
ctrl.vendors.push({name: 'Create New...', id: ''});
|
||||
}
|
||||
ctrl.organizationId = ctrl.vendors[0].id;
|
||||
}).error(function (error) {
|
||||
ctrl.vendors = null;
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving vendor listing from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will add new Product record.
|
||||
*/
|
||||
function addProduct() {
|
||||
ctrl.showSuccess = false;
|
||||
ctrl.showError = false;
|
||||
var url = refstackApiUrl + '/products';
|
||||
var data = {
|
||||
name: ctrl.name,
|
||||
description: ctrl.description,
|
||||
organization_id: ctrl.organizationId,
|
||||
product_type: parseInt(ctrl.productType, 10)
|
||||
};
|
||||
$http.post(url, data).success(function () {
|
||||
ctrl.rawData = null;
|
||||
ctrl.showSuccess = true;
|
||||
ctrl.name = '';
|
||||
ctrl.description = '';
|
||||
ctrl.productType = null;
|
||||
ctrl.update();
|
||||
}).error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error adding new Product: ' + angular.toJson(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,27 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<h4>Import Public Key</h4>
|
||||
<p>Instructions for adding a public key and signature can be found
|
||||
<a href="https://opendev.org/openinfra/refstack/src/branch/master/doc/source/uploading_private_results.rst#user-content-generate-ssh-keys-locally"
|
||||
target="_blank"
|
||||
title="How to generate and upload SSH key and signature with refstack-client">here.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-body container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-2">Public Key</div>
|
||||
<div class="col-md-9 pull-right">
|
||||
<textarea type="text" rows="10" cols="42" ng-model="modal.raw_key" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-2">Signature</div>
|
||||
<div class="col-md-9 pull-right">
|
||||
<textarea type="text" rows="10" cols="42" ng-model="modal.self_signature" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning btn-sm" ng-click="modal.cancel()">Cancel</button>
|
||||
<button type="button" class="btn btn-default btn-sm" ng-click="modal.importPubKey()">Import Public Key</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,37 +0,0 @@
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div ng-show="ctrl.pubkeys">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h4>User Public Keys</h4>
|
||||
</div>
|
||||
<div class="col-md-2 pull-right">
|
||||
<button type="button" class="btn btn-default btn-sm" ng-click="ctrl.openImportPubKeyModal()">
|
||||
<span class="glyphicon glyphicon-plus"></span> Import Public Key
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<table class="table table-striped table-hover">
|
||||
<tbody>
|
||||
<tr ng-repeat="pubKey in ctrl.pubkeys" ng-click="ctrl.openShowPubKeyModal(pubKey)">
|
||||
<td>{{pubKey.format}}</td>
|
||||
<td>{{pubKey.shortKey}}</td>
|
||||
<td>{{pubKey.comment}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,219 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* 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('refstackApp')
|
||||
.factory('PubKeys', PubKeys);
|
||||
|
||||
PubKeys.$inject = ['$resource', 'refstackApiUrl'];
|
||||
|
||||
/**
|
||||
* This is a provider for the user's uploaded public keys.
|
||||
*/
|
||||
function PubKeys($resource, refstackApiUrl) {
|
||||
return $resource(refstackApiUrl + '/profile/pubkeys/:id', null, null);
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('ProfileController', ProfileController);
|
||||
|
||||
ProfileController.$inject = [
|
||||
'$scope', '$http', 'refstackApiUrl', 'PubKeys',
|
||||
'$uibModal', 'raiseAlert', '$state'
|
||||
];
|
||||
|
||||
/**
|
||||
* RefStack Profile Controller
|
||||
* This controller handles user's profile page, where a user can view
|
||||
* account-specific information.
|
||||
*/
|
||||
function ProfileController($scope, $http, refstackApiUrl,
|
||||
PubKeys, $uibModal, raiseAlert, $state) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.updatePubKeys = updatePubKeys;
|
||||
ctrl.openImportPubKeyModal = openImportPubKeyModal;
|
||||
ctrl.openShowPubKeyModal = openShowPubKeyModal;
|
||||
|
||||
// Must be authenticated to view this page.
|
||||
if (!$scope.auth.isAuthenticated) {
|
||||
$state.go('home');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will fetch all the user's public keys from the
|
||||
* server and store them in an array.
|
||||
*/
|
||||
function updatePubKeys() {
|
||||
var keys = PubKeys.query(function() {
|
||||
ctrl.pubkeys = [];
|
||||
angular.forEach(keys, function (key) {
|
||||
ctrl.pubkeys.push({
|
||||
'resource': key,
|
||||
'format': key.format,
|
||||
'shortKey': [
|
||||
key.pubkey.slice(0, 10),
|
||||
'.',
|
||||
key.pubkey.slice(-10)
|
||||
].join('.'),
|
||||
'pubkey': key.pubkey,
|
||||
'comment': key.comment
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will open the modal that will give the user a form
|
||||
* for importing a public key.
|
||||
*/
|
||||
function openImportPubKeyModal() {
|
||||
$uibModal.open({
|
||||
templateUrl: '/components/profile/importPubKeyModal.html',
|
||||
backdrop: true,
|
||||
windowClass: 'modal',
|
||||
controller: 'ImportPubKeyModalController as modal'
|
||||
}).result.finally(function() {
|
||||
ctrl.updatePubKeys();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will open the modal that will give the full
|
||||
* information regarding a specific public key.
|
||||
* @param {Object} pubKey resource
|
||||
*/
|
||||
function openShowPubKeyModal(pubKey) {
|
||||
$uibModal.open({
|
||||
templateUrl: '/components/profile/showPubKeyModal.html',
|
||||
backdrop: true,
|
||||
windowClass: 'modal',
|
||||
controller: 'ShowPubKeyModalController as modal',
|
||||
resolve: {
|
||||
pubKey: function() {
|
||||
return pubKey;
|
||||
}
|
||||
}
|
||||
}).result.finally(function() {
|
||||
ctrl.updatePubKeys();
|
||||
});
|
||||
}
|
||||
|
||||
ctrl.authRequest = $scope.auth.doSignCheck().then(ctrl.updatePubKeys);
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('ImportPubKeyModalController', ImportPubKeyModalController);
|
||||
|
||||
ImportPubKeyModalController.$inject = [
|
||||
'$uibModalInstance', 'PubKeys', 'raiseAlert'
|
||||
];
|
||||
|
||||
/**
|
||||
* Import Pub Key Modal Controller
|
||||
* This controller is for the modal that appears if a user wants to import
|
||||
* a public key.
|
||||
*/
|
||||
function ImportPubKeyModalController($uibModalInstance,
|
||||
PubKeys, raiseAlert) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.importPubKey = importPubKey;
|
||||
ctrl.cancel = cancel;
|
||||
|
||||
/**
|
||||
* This function will save a new public key resource to the API server.
|
||||
*/
|
||||
function importPubKey() {
|
||||
var newPubKey = new PubKeys(
|
||||
{raw_key: ctrl.raw_key, self_signature: ctrl.self_signature}
|
||||
);
|
||||
newPubKey.$save(
|
||||
function(newPubKey_) {
|
||||
raiseAlert('success', '', 'Public key saved successfully');
|
||||
$uibModalInstance.close(newPubKey_);
|
||||
},
|
||||
function(httpResp) {
|
||||
raiseAlert('danger',
|
||||
httpResp.statusText, httpResp.data.title);
|
||||
ctrl.cancel();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will dismiss the modal.
|
||||
*/
|
||||
function cancel() {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
}
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('ShowPubKeyModalController', ShowPubKeyModalController);
|
||||
|
||||
ShowPubKeyModalController.$inject = [
|
||||
'$uibModalInstance', 'raiseAlert', 'pubKey'
|
||||
];
|
||||
|
||||
/**
|
||||
* Show Pub Key Modal Controller
|
||||
* This controller is for the modal that appears if a user wants to see the
|
||||
* full details of one of their public keys.
|
||||
*/
|
||||
function ShowPubKeyModalController($uibModalInstance, raiseAlert, pubKey) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.deletePubKey = deletePubKey;
|
||||
ctrl.cancel = cancel;
|
||||
|
||||
ctrl.pubKey = pubKey.resource;
|
||||
ctrl.rawKey = [pubKey.format, pubKey.pubkey, pubKey.comment].join('\n');
|
||||
|
||||
/**
|
||||
* This function will delete a public key resource.
|
||||
*/
|
||||
function deletePubKey() {
|
||||
ctrl.pubKey.$remove(
|
||||
{id: ctrl.pubKey.id},
|
||||
function() {
|
||||
raiseAlert('success',
|
||||
'', 'Public key deleted successfully');
|
||||
$uibModalInstance.close(ctrl.pubKey.id);
|
||||
},
|
||||
function(httpResp) {
|
||||
raiseAlert('danger',
|
||||
httpResp.statusText, httpResp.data.title);
|
||||
ctrl.cancel();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will dismiss the modal.
|
||||
*/
|
||||
function cancel() {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,11 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<h4>Public Key</h4>
|
||||
</div>
|
||||
<div class="modal-body container-fluid">
|
||||
<textarea type="text" rows="10" cols="67" readonly="readonly">{{modal.rawKey}}</textarea>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="modal.cancel()">Cancel</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-click="modal.deletePubKey() "
|
||||
confirm="Are you sure you want to delete this public key? You will lose management access to any test results signed with this key.">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,70 +0,0 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">×</button>
|
||||
<h4>Edit Test Run Metadata</h4>
|
||||
<p>Make changes to your test metadata.</p>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<strong>Publicly Shared:</strong>
|
||||
<select ng-model="modal.metaCopy.shared"
|
||||
class="form-control">
|
||||
<option value="true">Yes</option>
|
||||
<option value="">No</option>
|
||||
</select>
|
||||
<br />
|
||||
<strong>Associated Guideline:</strong>
|
||||
<select ng-model="modal.metaCopy.guideline"
|
||||
ng-options="o as o.slice(0, -5) for o in modal.versionList"
|
||||
class="form-control">
|
||||
<option value="">None</option>
|
||||
</select>
|
||||
<br />
|
||||
<strong>Associated Target Program:</strong>
|
||||
<select ng-model="modal.metaCopy.target"
|
||||
class="form-control">
|
||||
<option value="">None</option>
|
||||
<option value="platform">OpenStack Powered Platform</option>
|
||||
<option value="compute">OpenStack Powered Compute</option>
|
||||
<option value="object">OpenStack Powered Object Storage</option>
|
||||
<option value="dns">OpenStack with DNS</option>
|
||||
<option value="orchestration">OpenStack with Orchestration</option>
|
||||
<option value="shared_file_system">OpenStack with Shared File System</option>
|
||||
<option value="load_balancer">OpenStack with Load Balancer</option>
|
||||
<option value="key_manager">OpenStack with Key Manager</option>
|
||||
</select>
|
||||
<hr>
|
||||
<strong>Associated Product:</strong>
|
||||
<select ng-options="product as product.name for product in modal.products | arrayConverter | orderBy: 'name' track by product.id"
|
||||
ng-model="modal.selectedProduct"
|
||||
ng-change="modal.getProductVersions()"
|
||||
class="form-control">
|
||||
<option value="">-- No Product --</option>
|
||||
</select>
|
||||
|
||||
<span ng-if="modal.productVersions.length">
|
||||
<strong>Product Version:</strong>
|
||||
<select ng-options="version as version.version for version in modal.productVersions | orderBy: 'version' track by version.id"
|
||||
ng-model="modal.selectedVersion"
|
||||
class="form-control">
|
||||
</select>
|
||||
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div ng-show="modal.showError" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
{{modal.error}}
|
||||
</div>
|
||||
<div ng-show="modal.showSuccess" class="alert alert-success" role="success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<span class="sr-only">Success:</span>
|
||||
Changes saved successfully.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.saveChanges()">Save Changes</button>
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,13 +0,0 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>All Passed Tests ({{modal.tests.length}})</h4>
|
||||
</div>
|
||||
<div class="modal-body tests-modal-content">
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="20" id="tests" wrap="off">{{modal.getTestListString()}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,87 +0,0 @@
|
||||
<!--
|
||||
HTML for each accordion group that separates the status types on the results
|
||||
report page.
|
||||
-->
|
||||
|
||||
<uib-accordion-group is-open="isOpen" is-disabled="ctrl.caps[status].caps.length == 0">
|
||||
<uib-accordion-heading>
|
||||
{{status | capitalize}}
|
||||
<small>
|
||||
(<strong>Total:</strong> {{ctrl.caps[status].caps.length}} capabilities, {{ctrl.caps[status].count}} tests)
|
||||
<span ng-if="ctrl.testStatus !== 'total'">
|
||||
(<strong>{{ctrl.testStatus | capitalize}}:</strong> {{ctrl.getStatusTestCount(status)}} tests)
|
||||
</span>
|
||||
</small>
|
||||
<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="capability in ctrl.caps[status].caps | orderBy:'id'"
|
||||
ng-if="ctrl.isCapabilityShown(capability)">
|
||||
|
||||
<a ng-click="showTests = !showTests"
|
||||
title="{{ctrl.guidelineData.capabilities[capability.id].description}}">
|
||||
{{capability.id}}
|
||||
</a>
|
||||
<span ng-class="{'text-success': ctrl.testStatus === 'passed',
|
||||
'text-danger': ctrl.testStatus === 'not passed',
|
||||
'text-warning': ctrl.testStatus === 'flagged'}"
|
||||
ng-if="ctrl.testStatus !== 'total'">
|
||||
[{{ctrl.getCapabilityTestCount(capability)}}]
|
||||
</span>
|
||||
<span ng-class="{'text-success': (capability.passedTests.length > 0 &&
|
||||
capability.notPassedTests.length == 0),
|
||||
'text-danger': (capability.passedTests.length == 0 &&
|
||||
capability.notPassedTests.length > 0),
|
||||
'text-warning': (capability.passedTests.length > 0 &&
|
||||
capability.notPassedTests.length > 0)}"
|
||||
ng-if="ctrl.testStatus === 'total'">
|
||||
[{{capability.passedTests.length}}/{{capability.passedTests.length +
|
||||
capability.notPassedTests.length}}]
|
||||
</span>
|
||||
|
||||
<ul class="list-unstyled" uib-collapse="!showTests">
|
||||
<!-- Start passed test list -->
|
||||
<li ng-repeat="test in capability.passedTests | orderBy:'toString()'"
|
||||
ng-if="ctrl.isTestShown(test, capability)">
|
||||
|
||||
<span class="glyphicon glyphicon-ok text-success"
|
||||
aria-hidden="true">
|
||||
</span>
|
||||
<span ng-class="{'glyphicon glyphicon-flag text-warning':
|
||||
ctrl.isTestFlagged(test, ctrl.guidelineData.capabilities[capability.id])}"
|
||||
title="{{ctrl.getFlaggedReason(test, ctrl.guidelineData.capabilities[capability.id])}}">
|
||||
</span>
|
||||
{{test}}
|
||||
<span ng-if="ctrl.guidelineData.capabilities[capability.id].tests[test].aliases"> —
|
||||
<a ng-click="showAliases = !showAliases">[Aliases]</a>
|
||||
<div class="test-detail-report" ng-if="ctrl.guidelineData.capabilities[capability.id].tests[test].aliases && showAliases">
|
||||
<ul><li ng-repeat="alias in ctrl.guidelineData.capabilities[capability.id].tests[test].aliases">{{alias}}</li></ul>
|
||||
</div>
|
||||
</span>
|
||||
</li>
|
||||
<!-- End passed test list -->
|
||||
|
||||
<!-- Start not passed test list -->
|
||||
<li ng-repeat="test in capability.notPassedTests | orderBy:'toString()'"
|
||||
ng-if="ctrl.isTestShown(test, capability)">
|
||||
|
||||
<span class="glyphicon glyphicon-remove text-danger" aria-hidden="true"></span>
|
||||
<span ng-class="{'glyphicon glyphicon-flag text-warning':
|
||||
ctrl.isTestFlagged(test, ctrl.guidelineData.capabilities[capability.id])}"
|
||||
title="{{ctrl.getFlaggedReason(test, ctrl.guidelineData.capabilities[capability.id])}}">
|
||||
</span>
|
||||
{{test}}
|
||||
<span ng-if="ctrl.guidelineData.capabilities[capability.id].tests[test].aliases"> —
|
||||
<a ng-click="showAliases = !showAliases">[Aliases]</a>
|
||||
<div class="test-detail-report" ng-if="ctrl.guidelineData.capabilities[capability.id].tests[test].aliases && showAliases">
|
||||
<ul><li ng-repeat="alias in ctrl.guidelineData.capabilities[capability.id].tests[test].aliases">{{alias}}</li></ul>
|
||||
</div>
|
||||
</span>
|
||||
</li>
|
||||
<!-- End not passed test list -->
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</uib-accordion-group>
|
||||
@@ -1,190 +0,0 @@
|
||||
<h3>Test Run Results</h3>
|
||||
|
||||
<div ng-show="ctrl.resultsData" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="pull-left">
|
||||
<div class="test-report">
|
||||
<strong>Test ID:</strong> {{ctrl.testId}}<br />
|
||||
<div ng-if="ctrl.isResultAdmin()"><strong>Cloud ID:</strong> {{ctrl.resultsData.cpid}}<br /></div>
|
||||
<strong>Upload Date:</strong> {{ctrl.resultsData.created_at}} UTC<br />
|
||||
<strong>Duration:</strong> {{ctrl.resultsData.duration_seconds}} seconds<br />
|
||||
<strong>Total Number of Passed Tests:</strong>
|
||||
<a title="See all passed tests" ng-click="ctrl.openFullTestListModal()">
|
||||
{{ctrl.resultsData.results.length}}
|
||||
</a>
|
||||
</div>
|
||||
<hr>
|
||||
<div ng-show="ctrl.isResultAdmin()">
|
||||
<strong>Publicly Shared:</strong>
|
||||
<span ng-if="ctrl.resultsData.meta.shared">Yes</span>
|
||||
<span ng-if="!ctrl.resultsData.meta.shared">No</span>
|
||||
<br />
|
||||
</div>
|
||||
<div ng-show="ctrl.resultsData.product_version">
|
||||
<strong>Product:</strong>
|
||||
{{ctrl.resultsData.product_version.product_info.name}}
|
||||
<span ng-if="ctrl.resultsData.product_version.version">
|
||||
({{ctrl.resultsData.product_version.version}})
|
||||
</span><br />
|
||||
</div>
|
||||
<div ng-show="ctrl.resultsData.meta.guideline">
|
||||
<strong>Associated Guideline:</strong>
|
||||
{{ctrl.resultsData.meta.guideline.slice(0, -5)}}
|
||||
</div>
|
||||
<div ng-show="ctrl.resultsData.meta.target">
|
||||
<strong>Associated Target Program:</strong>
|
||||
{{ctrl.targetMappings[ctrl.resultsData.meta.target]}}
|
||||
</div>
|
||||
<div ng-show="ctrl.resultsData.verification_status">
|
||||
<strong>Verified:</strong>
|
||||
<span class="yes">YES</span>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="pull-right">
|
||||
<div ng-show="ctrl.isResultAdmin() && !ctrl.resultsData.verification_status">
|
||||
<button class="btn btn-info" ng-click="ctrl.openEditTestModal()">Edit</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="ctrl.deleteTestRun()" confirm="Are you sure you want to delete these test run results?">Delete</button>
|
||||
</div>
|
||||
<div ng-show="ctrl.resultsData.user_role === 'foundation'">
|
||||
<hr>
|
||||
<div class="checkbox checkbox-verified">
|
||||
<label><input type="checkbox"
|
||||
ng-model="ctrl.isVerified"
|
||||
ng-change="ctrl.updateVerificationStatus()"
|
||||
ng-true-value="1"
|
||||
ng-false-value="0">
|
||||
<strong>Verified</strong>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="ctrl.resultsData">
|
||||
<p>See how these results stack up against Interop Working Group capabilities and OpenStack
|
||||
<a target="_blank" href="http://www.openstack.org/brand/interop/">target marketing programs.</a>
|
||||
</p>
|
||||
|
||||
<!-- User Options -->
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<strong>Guideline Version:</strong>
|
||||
<!-- Slicing the version file name here gets rid of the '.json' file extension -->
|
||||
<select ng-model="ctrl.version"
|
||||
ng-change="ctrl.updateGuidelines()"
|
||||
class="form-control"
|
||||
ng-options="versionFile.slice(0,-5) for versionFile in ctrl.versionList">
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<strong>Target Program:</strong>
|
||||
<select ng-model="ctrl.target" class="form-control" ng-change="ctrl.buildCapabilitiesObject()">
|
||||
<option value="platform">OpenStack Powered Platform</option>
|
||||
<option value="compute">OpenStack Powered Compute</option>
|
||||
<option value="object">OpenStack Powered Object Storage</option>
|
||||
<option value="dns">OpenStack with DNS</option>
|
||||
<option value="orchestration">OpenStack with Orchestration</option>
|
||||
<option value="shared_file_system">OpenStack with Shared File System</option>
|
||||
<option value="load_balancer">OpenStack with Load Balancer</option>
|
||||
<option value="key_manager">OpenStack with Key Manager</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End User Options -->
|
||||
|
||||
<br />
|
||||
<div ng-if="ctrl.guidelineData">
|
||||
<strong>Guideline Status:</strong>
|
||||
{{ctrl.guidelineStatus | capitalize}}
|
||||
</div>
|
||||
|
||||
<strong>Corresponding OpenStack Releases:</strong>
|
||||
<ul class="list-inline">
|
||||
<li ng-repeat="release in ctrl.releases">
|
||||
{{release | capitalize}}
|
||||
</li>
|
||||
</ul>
|
||||
<hr >
|
||||
|
||||
<div ng-show="ctrl.guidelineData">
|
||||
<strong>Status:</strong>
|
||||
<p>This cloud passes <strong>{{ctrl.requiredPassPercent | number:1}}% </strong>
|
||||
({{ctrl.caps.required.passedCount}}/{{ctrl.caps.required.count}})
|
||||
of the tests in the <strong>{{ctrl.version.slice(0, -5)}}</strong> <em>required</em> capabilities for the
|
||||
<strong>{{ctrl.targetMappings[target]}}</strong> program. <br />
|
||||
Excluding flagged tests, this cloud passes
|
||||
<strong>{{ctrl.nonFlagRequiredPassPercent | number:1}}%</strong>
|
||||
({{ctrl.nonFlagPassCount}}/{{ctrl.totalNonFlagCount}})
|
||||
of the <em>required</em> tests.
|
||||
</p>
|
||||
|
||||
<p>Compliance with <strong>{{ctrl.version.slice(0, -5)}}</strong>:
|
||||
<strong>
|
||||
<span ng-if="ctrl.nonFlagPassCount === ctrl.totalNonFlagCount" class="yes">YES</span>
|
||||
<span ng-if="ctrl.nonFlagPassCount !== ctrl.totalNonFlagCount" class="no">NO</span>
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
<h4>Capability Overview</h4>
|
||||
|
||||
Test Filters:<br />
|
||||
<div class="btn-group button-margin" data-toggle="buttons">
|
||||
<label class="btn btn-default" ng-class="{'active': ctrl.testStatus === 'total'}">
|
||||
<input type="radio" ng-model="ctrl.testStatus" value="total">
|
||||
<span class="text-primary">All</span>
|
||||
</label>
|
||||
<label class="btn btn-default" ng-class="{'active': ctrl.testStatus === 'passed'}">
|
||||
<input type="radio" ng-model="ctrl.testStatus" value="passed">
|
||||
<span class="text-success">Passed</span>
|
||||
</label>
|
||||
<label class="btn btn-default" ng-class="{'active': ctrl.testStatus === 'not passed'}">
|
||||
<input type="radio" ng-model="ctrl.testStatus" value="not passed">
|
||||
<span class="text-danger">Not Passed</span>
|
||||
</label>
|
||||
<label class="btn btn-default" ng-class="{'active': ctrl.testStatus === 'flagged'}">
|
||||
<input type="radio" ng-model="ctrl.testStatus" value="flagged">
|
||||
<span class="text-warning">Flagged</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<uib-accordion close-others=false>
|
||||
<!-- The ng-repeat is used to pass in a local variable to the template. -->
|
||||
<ng-include
|
||||
ng-repeat="status in ['required']"
|
||||
src="ctrl.detailsTemplate"
|
||||
onload="isOpen = true">
|
||||
</ng-include>
|
||||
<br />
|
||||
<ng-include
|
||||
ng-repeat="status in ['advisory']"
|
||||
src="ctrl.detailsTemplate">
|
||||
</ng-include>
|
||||
<br />
|
||||
<ng-include
|
||||
ng-repeat="status in ['deprecated']"
|
||||
src="ctrl.detailsTemplate">
|
||||
</ng-include>
|
||||
<br />
|
||||
<ng-include
|
||||
ng-repeat="status in ['removed']"
|
||||
src="ctrl.detailsTemplate">
|
||||
</ng-include>
|
||||
</uib-accordion>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="loading">
|
||||
<div cg-busy="{promise:versionsRequest,message:'Loading versions'}"></div>
|
||||
<div cg-busy="{promise:capsRequest,message:'Loading capabilities'}"></div>
|
||||
<div cg-busy="{promise:resultsRequest,message:'Loading results'}"></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>
|
||||
@@ -1,942 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('ResultsReportController', ResultsReportController);
|
||||
|
||||
ResultsReportController.$inject = [
|
||||
'$http', '$stateParams', '$window',
|
||||
'$uibModal', 'refstackApiUrl', 'raiseAlert'
|
||||
];
|
||||
|
||||
/**
|
||||
* RefStack 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($http, $stateParams, $window,
|
||||
$uibModal, refstackApiUrl, raiseAlert) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.getVersionList = getVersionList;
|
||||
ctrl.getResults = getResults;
|
||||
ctrl.isResultAdmin = isResultAdmin;
|
||||
ctrl.isShared = isShared;
|
||||
ctrl.shareTestRun = shareTestRun;
|
||||
ctrl.deleteTestRun = deleteTestRun;
|
||||
ctrl.updateVerificationStatus = updateVerificationStatus;
|
||||
ctrl.updateGuidelines = updateGuidelines;
|
||||
ctrl.getTargetCapabilities = getTargetCapabilities;
|
||||
ctrl.buildCapabilityV1_2 = buildCapabilityV1_2;
|
||||
ctrl.buildCapabilityV1_3 = buildCapabilityV1_3;
|
||||
ctrl.buildCapabilitiesObject = buildCapabilitiesObject;
|
||||
ctrl.isTestFlagged = isTestFlagged;
|
||||
ctrl.getFlaggedReason = getFlaggedReason;
|
||||
ctrl.isCapabilityShown = isCapabilityShown;
|
||||
ctrl.isTestShown = isTestShown;
|
||||
ctrl.getCapabilityTestCount = getCapabilityTestCount;
|
||||
ctrl.getStatusTestCount = getStatusTestCount;
|
||||
ctrl.openFullTestListModal = openFullTestListModal;
|
||||
ctrl.openEditTestModal = openEditTestModal;
|
||||
getVersionList();
|
||||
|
||||
/** The testID extracted from the URL route. */
|
||||
ctrl.testId = $stateParams.testID;
|
||||
|
||||
/** The target OpenStack marketing program to compare against. */
|
||||
ctrl.target = 'platform';
|
||||
|
||||
/** Mappings of Interop WG components to marketing program names. */
|
||||
ctrl.targetMappings = {
|
||||
'platform': 'Openstack Powered Platform',
|
||||
'compute': 'OpenStack Powered Compute',
|
||||
'object': 'OpenStack Powered Object Storage',
|
||||
'dns': 'OpenStack with DNS',
|
||||
'orchestration': 'OpenStack with orchestration',
|
||||
'shared_file_system': 'OpenStack with Shared File System',
|
||||
'load_balancer': 'OpenStack with Load Balancer',
|
||||
'key_manager': 'OpenStack with Key Manager'
|
||||
};
|
||||
|
||||
/** The schema version of the currently selected guideline data. */
|
||||
ctrl.schemaVersion = null;
|
||||
|
||||
/** The selected test status used for test filtering. */
|
||||
ctrl.testStatus = 'total';
|
||||
|
||||
/** The HTML template that all accordian groups will use. */
|
||||
ctrl.detailsTemplate = 'components/results-report/partials/' +
|
||||
'reportDetails.html';
|
||||
|
||||
/**
|
||||
* Retrieve an array of available guideline files from the Refstack
|
||||
* API server, sort this array reverse-alphabetically, and store it in
|
||||
* a scoped variable. The scope's selected version is initialized to
|
||||
* the latest (i.e. first) version here as well. After a successful API
|
||||
* call, the function to update the capabilities is called.
|
||||
* Sample API return array: ["2015.03.json", "2015.04.json"]
|
||||
*/
|
||||
function getVersionList() {
|
||||
if (ctrl.target === 'dns' ||
|
||||
ctrl.target === 'orchestration' ||
|
||||
ctrl.target === 'shared_file_system' ||
|
||||
ctrl.target === 'load_balancer' ||
|
||||
ctrl.target === 'key_manager'
|
||||
) {
|
||||
ctrl.gl_type = ctrl.target;
|
||||
|
||||
} else {
|
||||
ctrl.gl_type = 'powered';
|
||||
}
|
||||
var content_url = refstackApiUrl + '/guidelines';
|
||||
ctrl.versionsRequest =
|
||||
$http.get(content_url).success(function (data) {
|
||||
let gl_files = data[ctrl.gl_type];
|
||||
let gl_names = gl_files.map((gl_obj) => gl_obj.name);
|
||||
ctrl.versionList = gl_names.sort().reverse();
|
||||
let file_names = gl_files.map((gl_obj) => gl_obj.file);
|
||||
ctrl.fileList = file_names.sort().reverse();
|
||||
|
||||
if (!ctrl.version) {
|
||||
// Default to the first approved guideline which is
|
||||
// expected to be at index 1.
|
||||
ctrl.version = ctrl.versionList[1];
|
||||
ctrl.versionFile = ctrl.fileList[1];
|
||||
} else {
|
||||
let versionIndex =
|
||||
ctrl.versionList.indexOf(ctrl.version);
|
||||
ctrl.versionFile = ctrl.fileList[versionIndex];
|
||||
}
|
||||
ctrl.updateGuidelines();
|
||||
}).error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error = 'Error retrieving version list: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve results from the Refstack API server based on the test
|
||||
* run id in the URL. This function is the first function that will
|
||||
* be called from the controller. Upon successful retrieval of results,
|
||||
* the function that gets the version list will be called.
|
||||
*/
|
||||
function getResults() {
|
||||
var content_url = refstackApiUrl + '/results/' + ctrl.testId;
|
||||
ctrl.resultsRequest =
|
||||
$http.get(content_url).success(function (data) {
|
||||
ctrl.resultsData = data;
|
||||
ctrl.version = ctrl.resultsData.meta.guideline;
|
||||
ctrl.isVerified = ctrl.resultsData.verification_status;
|
||||
if (ctrl.resultsData.meta.target) {
|
||||
ctrl.target = ctrl.resultsData.meta.target;
|
||||
}
|
||||
getVersionList();
|
||||
}).error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.resultsData = null;
|
||||
ctrl.error = 'Error retrieving results from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This tells you whether the current user has administrative
|
||||
* privileges for the test result.
|
||||
* @returns {Boolean} true if the user has admin privileges.
|
||||
*/
|
||||
function isResultAdmin() {
|
||||
return Boolean(ctrl.resultsData &&
|
||||
(ctrl.resultsData.user_role === 'owner' ||
|
||||
ctrl.resultsData.user_role === 'foundation'));
|
||||
}
|
||||
/**
|
||||
* This tells you whether the current results are shared with the
|
||||
* community or not.
|
||||
* @returns {Boolean} true if the results are shared
|
||||
*/
|
||||
function isShared() {
|
||||
return Boolean(ctrl.resultsData &&
|
||||
'shared' in ctrl.resultsData.meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will send an API request in order to share or unshare the
|
||||
* current results based on the passed in shareState.
|
||||
* @param {Boolean} shareState - Whether to share or unshare results.
|
||||
*/
|
||||
function shareTestRun(shareState) {
|
||||
var content_url = [
|
||||
refstackApiUrl, '/results/', ctrl.testId, '/meta/shared'
|
||||
].join('');
|
||||
if (shareState) {
|
||||
ctrl.shareRequest =
|
||||
$http.post(content_url, 'true').success(function () {
|
||||
ctrl.resultsData.meta.shared = 'true';
|
||||
raiseAlert('success', '', 'Test run shared!');
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
} else {
|
||||
ctrl.shareRequest =
|
||||
$http.delete(content_url).success(function () {
|
||||
delete ctrl.resultsData.meta.shared;
|
||||
raiseAlert('success', '', 'Test run unshared!');
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will send a request to the API to delete the current
|
||||
* test results set.
|
||||
*/
|
||||
function deleteTestRun() {
|
||||
var content_url = [
|
||||
refstackApiUrl, '/results/', ctrl.testId
|
||||
].join('');
|
||||
ctrl.deleteRequest =
|
||||
$http.delete(content_url).success(function () {
|
||||
$window.history.back();
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will send a request to the API to delete the current
|
||||
* test results set.
|
||||
*/
|
||||
function updateVerificationStatus() {
|
||||
var content_url = [
|
||||
refstackApiUrl, '/results/', ctrl.testId
|
||||
].join('');
|
||||
var data = {'verification_status': ctrl.isVerified};
|
||||
ctrl.updateRequest =
|
||||
$http.put(content_url, data).success(
|
||||
function () {
|
||||
ctrl.resultsData.verification_status = ctrl.isVerified;
|
||||
raiseAlert('success', '',
|
||||
'Verification status changed!');
|
||||
}).error(function (error) {
|
||||
ctrl.isVerified = ctrl.resultsData.verification_status;
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API server to retrieve the JSON
|
||||
* content of the guideline file corresponding to the selected
|
||||
* version. A function to construct an object from the capability
|
||||
* data will be called upon successful retrieval.
|
||||
*/
|
||||
function updateGuidelines() {
|
||||
ctrl.guidelineData = null;
|
||||
ctrl.showError = false;
|
||||
|
||||
ctrl.content_url = refstackApiUrl + '/guidelines/' +
|
||||
ctrl.versionFile;
|
||||
let getparams = {'gl_file': ctrl.versionFile};
|
||||
ctrl.capsRequest =
|
||||
$http.get(ctrl.content_url, getparams).success(function (data) {
|
||||
ctrl.guidelineData = data;
|
||||
if ('metadata' in data && data.metadata.schema >= '2.0') {
|
||||
ctrl.schemaVersion = data.metadata.schema;
|
||||
ctrl.guidelineStatus =
|
||||
data.metadata.os_trademark_approval.status;
|
||||
ctrl.releases =
|
||||
data.metadata.os_trademark_approval.releases;
|
||||
} else {
|
||||
ctrl.schemaVersion = data.schema;
|
||||
ctrl.guidelineStatus = data.status;
|
||||
ctrl.releases = data.releases;
|
||||
}
|
||||
ctrl.buildCapabilitiesObject();
|
||||
}).error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.guidelineData = null;
|
||||
ctrl.error = 'Error retrieving guideline date: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will get all the capabilities relevant to the target and
|
||||
* their corresponding statuses.
|
||||
* @returns {Object} Object containing each capability and their status
|
||||
*/
|
||||
function getTargetCapabilities() {
|
||||
var components = ctrl.guidelineData.components;
|
||||
var targetCaps = {};
|
||||
var targetComponents = null;
|
||||
var old_type = ctrl.gl_type;
|
||||
if (ctrl.target === 'dns' ||
|
||||
ctrl.target === 'orchestration' ||
|
||||
ctrl.target === 'shared_file_system' ||
|
||||
ctrl.target === 'load_balancer' ||
|
||||
ctrl.target === 'key_manager'
|
||||
) {
|
||||
ctrl.gl_type = ctrl.target;
|
||||
} else {
|
||||
ctrl.gl_type = 'powered';
|
||||
}
|
||||
// If it has not been updated since the last program type change,
|
||||
// will need to update the list
|
||||
if (old_type !== ctrl.gl_type) {
|
||||
ctrl.getVersionList();
|
||||
return false;
|
||||
}
|
||||
|
||||
// The 'platform' target is comprised of multiple components, so
|
||||
// we need to get the capabilities belonging to each of its
|
||||
// components.
|
||||
if (ctrl.target === 'platform' || ctrl.schemaVersion >= '2.0') {
|
||||
if ('add-ons' in ctrl.guidelineData) {
|
||||
targetComponents = ['os_powered_' + ctrl.target];
|
||||
} else if (ctrl.schemaVersion >= '2.0') {
|
||||
var platformsMap = {
|
||||
'platform': 'OpenStack Powered Platform',
|
||||
'compute': 'OpenStack Powered Compute',
|
||||
'object': 'OpenStack Powered Storage',
|
||||
};
|
||||
targetComponents = ctrl.guidelineData.platforms[
|
||||
platformsMap[ctrl.target]].components.map(
|
||||
function(c) {
|
||||
return c.name;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
targetComponents = ctrl.guidelineData.platform.required;
|
||||
}
|
||||
|
||||
// This will contain status priority values, where lower
|
||||
// values mean higher priorities.
|
||||
var statusMap = {
|
||||
required: 1,
|
||||
advisory: 2,
|
||||
deprecated: 3,
|
||||
removed: 4
|
||||
};
|
||||
|
||||
// For each component required for the platform program.
|
||||
angular.forEach(targetComponents, function (component) {
|
||||
var componentList = components[component];
|
||||
if (ctrl.schemaVersion >= '2.0') {
|
||||
componentList = componentList.capabilities;
|
||||
}
|
||||
// Get each capability list belonging to each status.
|
||||
angular.forEach(componentList,
|
||||
function (caps, status) {
|
||||
// For each capability.
|
||||
angular.forEach(caps, function(cap) {
|
||||
// If the capability has already been added.
|
||||
if (cap in targetCaps) {
|
||||
// If the status priority value is less
|
||||
// than the saved priority value, update
|
||||
// the value.
|
||||
if (statusMap[status] <
|
||||
statusMap[targetCaps[cap]]) {
|
||||
targetCaps[cap] = status;
|
||||
}
|
||||
} else {
|
||||
targetCaps[cap] = status;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
angular.forEach(components[ctrl.target],
|
||||
function (caps, status) {
|
||||
angular.forEach(caps, function(cap) {
|
||||
targetCaps[cap] = status;
|
||||
});
|
||||
});
|
||||
}
|
||||
return targetCaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will build the a capability object for schema version 1.2.
|
||||
* This object will contain the information needed to form a report in
|
||||
* the HTML template.
|
||||
* @param {String} capId capability ID
|
||||
*/
|
||||
function buildCapabilityV1_2(capId) {
|
||||
var cap = {
|
||||
'id': capId,
|
||||
'passedTests': [],
|
||||
'notPassedTests': [],
|
||||
'passedFlagged': [],
|
||||
'notPassedFlagged': []
|
||||
};
|
||||
var capDetails = ctrl.guidelineData.capabilities[capId];
|
||||
// Loop through each test belonging to the capability.
|
||||
angular.forEach(capDetails.tests,
|
||||
function (testId) {
|
||||
// If the test ID is in the results' test list, add
|
||||
// it to the passedTests array.
|
||||
if (ctrl.resultsData.results.indexOf(testId) > -1) {
|
||||
cap.passedTests.push(testId);
|
||||
if (capDetails.flagged.indexOf(testId) > -1) {
|
||||
cap.passedFlagged.push(testId);
|
||||
}
|
||||
} else {
|
||||
cap.notPassedTests.push(testId);
|
||||
if (capDetails.flagged.indexOf(testId) > -1) {
|
||||
cap.notPassedFlagged.push(testId);
|
||||
}
|
||||
}
|
||||
});
|
||||
return cap;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will build the a capability object for schema version 1.3 and
|
||||
* above. This object will contain the information needed to form a
|
||||
* report in the HTML template.
|
||||
* @param {String} capId capability ID
|
||||
*/
|
||||
function buildCapabilityV1_3(capId) {
|
||||
var cap = {
|
||||
'id': capId,
|
||||
'passedTests': [],
|
||||
'notPassedTests': [],
|
||||
'passedFlagged': [],
|
||||
'notPassedFlagged': []
|
||||
};
|
||||
|
||||
// For cases where a capability listed in components is not
|
||||
// in the capabilities object.
|
||||
if (!(capId in ctrl.guidelineData.capabilities)) {
|
||||
return cap;
|
||||
}
|
||||
|
||||
// Loop through each test belonging to the capability.
|
||||
angular.forEach(ctrl.guidelineData.capabilities[capId].tests,
|
||||
function (details, testId) {
|
||||
var passed = false;
|
||||
|
||||
// If the test ID is in the results' test list.
|
||||
if (ctrl.resultsData.results.indexOf(testId) > -1) {
|
||||
passed = true;
|
||||
} else if ('aliases' in details) {
|
||||
var len = details.aliases.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
var alias = details.aliases[i];
|
||||
if (ctrl.resultsData.results.indexOf(alias) > -1) {
|
||||
passed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to correct array based on whether the test was
|
||||
// passed or not.
|
||||
if (passed) {
|
||||
cap.passedTests.push(testId);
|
||||
if ('flagged' in details) {
|
||||
cap.passedFlagged.push(testId);
|
||||
}
|
||||
} else {
|
||||
cap.notPassedTests.push(testId);
|
||||
if ('flagged' in details) {
|
||||
cap.notPassedFlagged.push(testId);
|
||||
}
|
||||
}
|
||||
});
|
||||
return cap;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will check the schema version of the current capabilities file,
|
||||
* and will call the correct method to build an object based on the
|
||||
* capability data retrieved from the Refstack API server.
|
||||
*/
|
||||
function buildCapabilitiesObject() {
|
||||
// This is the object template where 'count' is the number of
|
||||
// total tests that fall under the given status, and 'passedCount'
|
||||
// is the number of tests passed. The 'caps' array will contain
|
||||
// objects with details regarding each capability.
|
||||
ctrl.caps = {
|
||||
'required': {'caps': [], 'count': 0, 'passedCount': 0,
|
||||
'flagFailCount': 0, 'flagPassCount': 0},
|
||||
'advisory': {'caps': [], 'count': 0, 'passedCount': 0,
|
||||
'flagFailCount': 0, 'flagPassCount': 0},
|
||||
'deprecated': {'caps': [], 'count': 0, 'passedCount': 0,
|
||||
'flagFailCount': 0, 'flagPassCount': 0},
|
||||
'removed': {'caps': [], 'count': 0, 'passedCount': 0,
|
||||
'flagFailCount': 0, 'flagPassCount': 0}
|
||||
};
|
||||
var capMethod = null;
|
||||
|
||||
switch (ctrl.schemaVersion) {
|
||||
case '1.2':
|
||||
capMethod = 'buildCapabilityV1_2';
|
||||
break;
|
||||
case '1.3':
|
||||
case '1.4':
|
||||
case '1.5':
|
||||
case '1.6':
|
||||
case '2.0':
|
||||
capMethod = 'buildCapabilityV1_3';
|
||||
break;
|
||||
default:
|
||||
ctrl.showError = true;
|
||||
ctrl.guidelineData = null;
|
||||
ctrl.error = 'The schema version for the guideline ' +
|
||||
'file selected (' + ctrl.schemaVersion +
|
||||
') is currently not supported.';
|
||||
return;
|
||||
}
|
||||
|
||||
// Get test details for each relevant capability and store
|
||||
// them in the scope's 'caps' object.
|
||||
var targetCaps = ctrl.getTargetCapabilities();
|
||||
angular.forEach(targetCaps, function(status, capId) {
|
||||
var cap = ctrl[capMethod](capId);
|
||||
ctrl.caps[status].count +=
|
||||
cap.passedTests.length + cap.notPassedTests.length;
|
||||
ctrl.caps[status].passedCount += cap.passedTests.length;
|
||||
ctrl.caps[status].flagPassCount += cap.passedFlagged.length;
|
||||
ctrl.caps[status].flagFailCount +=
|
||||
cap.notPassedFlagged.length;
|
||||
ctrl.caps[status].caps.push(cap);
|
||||
});
|
||||
|
||||
ctrl.requiredPassPercent = ctrl.caps.required.passedCount *
|
||||
100 / ctrl.caps.required.count;
|
||||
|
||||
ctrl.totalRequiredFailCount = ctrl.caps.required.count -
|
||||
ctrl.caps.required.passedCount;
|
||||
ctrl.totalRequiredFlagCount =
|
||||
ctrl.caps.required.flagFailCount +
|
||||
ctrl.caps.required.flagPassCount;
|
||||
ctrl.totalNonFlagCount = ctrl.caps.required.count -
|
||||
ctrl.totalRequiredFlagCount;
|
||||
ctrl.nonFlagPassCount = ctrl.totalNonFlagCount -
|
||||
(ctrl.totalRequiredFailCount -
|
||||
ctrl.caps.required.flagFailCount);
|
||||
|
||||
ctrl.nonFlagRequiredPassPercent = ctrl.nonFlagPassCount *
|
||||
100 / ctrl.totalNonFlagCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will check if a given test is flagged.
|
||||
* @param {String} test ID of the test to check
|
||||
* @param {Object} capObj capability that test is under
|
||||
* @returns {Boolean} truthy value if test is flagged
|
||||
*/
|
||||
function isTestFlagged(test, capObj) {
|
||||
if (!capObj) {
|
||||
return false;
|
||||
}
|
||||
return ctrl.schemaVersion === '1.2' &&
|
||||
capObj.flagged.indexOf(test) > -1 ||
|
||||
ctrl.schemaVersion >= '1.3' &&
|
||||
capObj.tests[test].flagged;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return the reason a test is flagged. An empty string
|
||||
* will be returned if the passed in test is not flagged.
|
||||
* @param {String} test ID of the test to check
|
||||
* @param {String} capObj capability that test is under
|
||||
* @returns {String} reason
|
||||
*/
|
||||
function getFlaggedReason(test, capObj) {
|
||||
if (ctrl.schemaVersion === '1.2' &&
|
||||
ctrl.isTestFlagged(test, capObj)) {
|
||||
|
||||
// Return a generic message since schema 1.2 does not
|
||||
// provide flag reasons.
|
||||
return 'Interop Working Group has flagged this test.';
|
||||
} else if (ctrl.schemaVersion >= '1.3' &&
|
||||
ctrl.isTestFlagged(test, capObj)) {
|
||||
|
||||
return capObj.tests[test].flagged.reason;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will check the if a capability should be shown based on the
|
||||
* test filter selected. If a capability does not have any tests
|
||||
* belonging under the given filter, it should not be shown.
|
||||
* @param {Object} capability Built object for capability
|
||||
* @returns {Boolean} true if capability should be shown
|
||||
*/
|
||||
function isCapabilityShown(capability) {
|
||||
return ctrl.testStatus === 'total' ||
|
||||
ctrl.testStatus === 'passed' &&
|
||||
capability.passedTests.length > 0 ||
|
||||
ctrl.testStatus === 'not passed' &&
|
||||
capability.notPassedTests.length > 0 ||
|
||||
ctrl.testStatus === 'flagged' &&
|
||||
capability.passedFlagged.length +
|
||||
capability.notPassedFlagged.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will check the if a test should be shown based on the test
|
||||
* filter selected.
|
||||
* @param {String} test ID of the test
|
||||
* @param {Object} capability Built object for capability
|
||||
* @return {Boolean} true if test should be shown
|
||||
*/
|
||||
function isTestShown(test, capability) {
|
||||
return ctrl.testStatus === 'total' ||
|
||||
ctrl.testStatus === 'passed' &&
|
||||
capability.passedTests.indexOf(test) > -1 ||
|
||||
ctrl.testStatus === 'not passed' &&
|
||||
capability.notPassedTests.indexOf(test) > -1 ||
|
||||
ctrl.testStatus === 'flagged' &&
|
||||
(capability.passedFlagged.indexOf(test) > -1 ||
|
||||
capability.notPassedFlagged.indexOf(test) > -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will give the number of tests belonging under the selected
|
||||
* test filter for a given capability.
|
||||
* @param {Object} capability Built object for capability
|
||||
* @returns {Number} number of tests under filter
|
||||
*/
|
||||
function getCapabilityTestCount(capability) {
|
||||
if (ctrl.testStatus === 'total') {
|
||||
return capability.passedTests.length +
|
||||
capability.notPassedTests.length;
|
||||
} else if (ctrl.testStatus === 'passed') {
|
||||
return capability.passedTests.length;
|
||||
} else if (ctrl.testStatus === 'not passed') {
|
||||
return capability.notPassedTests.length;
|
||||
} else if (ctrl.testStatus === 'flagged') {
|
||||
return capability.passedFlagged.length +
|
||||
capability.notPassedFlagged.length;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will give the number of tests belonging under the selected
|
||||
* test filter for a given status.
|
||||
* @param {String} capability status
|
||||
* @returns {Number} number of tests for status under filter
|
||||
*/
|
||||
function getStatusTestCount(status) {
|
||||
if (!ctrl.caps) {
|
||||
return -1;
|
||||
} else if (ctrl.testStatus === 'total') {
|
||||
return ctrl.caps[status].count;
|
||||
} else if (ctrl.testStatus === 'passed') {
|
||||
return ctrl.caps[status].passedCount;
|
||||
} else if (ctrl.testStatus === 'not passed') {
|
||||
return ctrl.caps[status].count -
|
||||
ctrl.caps[status].passedCount;
|
||||
} else if (ctrl.testStatus === 'flagged') {
|
||||
return ctrl.caps[status].flagFailCount +
|
||||
ctrl.caps[status].flagPassCount;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will open the modal that will show the full list of passed
|
||||
* tests for the current results.
|
||||
*/
|
||||
function openFullTestListModal() {
|
||||
$uibModal.open({
|
||||
templateUrl: '/components/results-report/partials' +
|
||||
'/fullTestListModal.html',
|
||||
backdrop: true,
|
||||
windowClass: 'modal',
|
||||
animation: true,
|
||||
controller: 'FullTestListModalController as modal',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
tests: function () {
|
||||
return ctrl.resultsData.results;
|
||||
},
|
||||
gl_type: function () {
|
||||
return ctrl.gl_type;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will open the modal that will all a user to edit test run
|
||||
* metadata.
|
||||
*/
|
||||
function openEditTestModal() {
|
||||
$uibModal.open({
|
||||
templateUrl: '/components/results-report/partials' +
|
||||
'/editTestModal.html',
|
||||
backdrop: true,
|
||||
windowClass: 'modal',
|
||||
animation: true,
|
||||
controller: 'EditTestModalController as modal',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
resultsData: function () {
|
||||
return ctrl.resultsData;
|
||||
},
|
||||
gl_type: function () {
|
||||
return ctrl.gl_type;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getResults();
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('FullTestListModalController', FullTestListModalController);
|
||||
|
||||
FullTestListModalController.$inject =
|
||||
['$uibModalInstance', 'tests', 'gl_type'];
|
||||
|
||||
/**
|
||||
* Full Test List Modal Controller
|
||||
* This controller is for the modal that appears if a user wants to see the
|
||||
* full list of passed tests on a report page.
|
||||
*/
|
||||
function FullTestListModalController($uibModalInstance, tests, gl_type) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.tests = tests;
|
||||
ctrl.gl_type = gl_type;
|
||||
|
||||
/**
|
||||
* This function will close/dismiss the modal.
|
||||
*/
|
||||
ctrl.close = function () {
|
||||
$uibModalInstance.dismiss('exit');
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will return a string representing the sorted
|
||||
* tests list separated by newlines.
|
||||
*/
|
||||
ctrl.getTestListString = function () {
|
||||
return ctrl.tests.sort().join('\n');
|
||||
};
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('EditTestModalController', EditTestModalController);
|
||||
|
||||
EditTestModalController.$inject = [
|
||||
'$uibModalInstance', '$http', '$state', 'raiseAlert',
|
||||
'refstackApiUrl', 'resultsData', 'gl_type'
|
||||
];
|
||||
|
||||
/**
|
||||
* Edit Test Modal Controller
|
||||
* This controller is for the modal that appears if a user wants to edit
|
||||
* test run metadata.
|
||||
*/
|
||||
function EditTestModalController($uibModalInstance, $http, $state,
|
||||
raiseAlert, refstackApiUrl, resultsData, gl_type) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.getVersionList = getVersionList;
|
||||
ctrl.getUserProducts = getUserProducts;
|
||||
ctrl.associateProductVersion = associateProductVersion;
|
||||
ctrl.getProductVersions = getProductVersions;
|
||||
ctrl.saveChanges = saveChanges;
|
||||
|
||||
ctrl.resultsData = resultsData;
|
||||
ctrl.metaCopy = angular.copy(resultsData.meta);
|
||||
ctrl.prodVersionCopy = angular.copy(resultsData.product_version);
|
||||
ctrl.gl_type = gl_type;
|
||||
|
||||
ctrl.getVersionList();
|
||||
ctrl.getUserProducts();
|
||||
|
||||
/**
|
||||
* Retrieve an array of available capability files from the Refstack
|
||||
* API server, sort this array reverse-alphabetically, and store it in
|
||||
* a scoped variable.
|
||||
* Sample API return array: ["2015.03.json", "2015.04.json"]
|
||||
*/
|
||||
function getVersionList() {
|
||||
if (ctrl.versionList) {
|
||||
return;
|
||||
}
|
||||
var content_url = refstackApiUrl + '/guidelines';
|
||||
ctrl.versionsRequest =
|
||||
$http.get(content_url).success(function (data) {
|
||||
let gl_files = data[ctrl.gl_type];
|
||||
let gl_names = gl_files.map((gl_obj) => gl_obj.name);
|
||||
ctrl.versionList = gl_names.sort().reverse();
|
||||
ctrl.version = ctrl.versionList[1];
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title,
|
||||
'Unable to retrieve version list');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products user has management rights to or all products depending
|
||||
* on the passed in parameter value.
|
||||
*/
|
||||
function getUserProducts() {
|
||||
var contentUrl = refstackApiUrl + '/products';
|
||||
ctrl.productsRequest =
|
||||
$http.get(contentUrl).success(function (data) {
|
||||
ctrl.products = {};
|
||||
angular.forEach(data.products, function(prod) {
|
||||
if (prod.can_manage) {
|
||||
ctrl.products[prod.id] = prod;
|
||||
}
|
||||
});
|
||||
if (ctrl.prodVersionCopy) {
|
||||
ctrl.selectedProduct = ctrl.products[
|
||||
ctrl.prodVersionCopy.product_info.id
|
||||
];
|
||||
}
|
||||
ctrl.getProductVersions();
|
||||
}).error(function (error) {
|
||||
ctrl.products = null;
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving Products listing from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PUT request to the API server to associate a product with
|
||||
* a test result.
|
||||
*/
|
||||
function associateProductVersion() {
|
||||
var verId = ctrl.selectedVersion ?
|
||||
ctrl.selectedVersion.id : null;
|
||||
var testId = resultsData.id;
|
||||
var url = refstackApiUrl + '/results/' + testId;
|
||||
ctrl.associateRequest = $http.put(url, {'product_version_id':
|
||||
verId})
|
||||
.error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.showSuccess = false;
|
||||
ctrl.error =
|
||||
'Error associating product version with test run: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all versions for a product.
|
||||
*/
|
||||
function getProductVersions() {
|
||||
if (!ctrl.selectedProduct) {
|
||||
ctrl.productVersions = [];
|
||||
ctrl.selectedVersion = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var url = refstackApiUrl + '/products/' +
|
||||
ctrl.selectedProduct.id + '/versions';
|
||||
ctrl.getVersionsRequest = $http.get(url)
|
||||
.success(function (data) {
|
||||
ctrl.productVersions = data;
|
||||
if (ctrl.prodVersionCopy &&
|
||||
ctrl.prodVersionCopy.product_info.id ===
|
||||
ctrl.selectedProduct.id) {
|
||||
ctrl.selectedVersion = ctrl.prodVersionCopy;
|
||||
} else {
|
||||
angular.forEach(data, function(ver) {
|
||||
if (!ver.version) {
|
||||
ctrl.selectedVersion = ver;
|
||||
}
|
||||
});
|
||||
}
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PUT request to the server with the changes.
|
||||
*/
|
||||
function saveChanges() {
|
||||
ctrl.showError = false;
|
||||
ctrl.showSuccess = false;
|
||||
var metaBaseUrl = [
|
||||
refstackApiUrl, '/results/', resultsData.id, '/meta/'
|
||||
].join('');
|
||||
var metaFields = ['target', 'guideline', 'shared'];
|
||||
var meta = ctrl.metaCopy;
|
||||
angular.forEach(metaFields, function(field) {
|
||||
var oldMetaValue = field in ctrl.resultsData.meta ?
|
||||
ctrl.resultsData.meta[field] : '';
|
||||
if (field in meta && oldMetaValue !== meta[field]) {
|
||||
var metaUrl = metaBaseUrl + field;
|
||||
if (meta[field]) {
|
||||
ctrl.assocRequest = $http.post(metaUrl, meta[field])
|
||||
.success(function() {
|
||||
ctrl.resultsData.meta[field] = meta[field];
|
||||
})
|
||||
.error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.showSuccess = false;
|
||||
ctrl.error =
|
||||
'Error associating metadata with ' +
|
||||
'test run: ' + angular.toJson(error);
|
||||
});
|
||||
} else {
|
||||
ctrl.unassocRequest = $http.delete(metaUrl)
|
||||
.success(function () {
|
||||
delete ctrl.resultsData.meta[field];
|
||||
delete meta[field];
|
||||
})
|
||||
.error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.showSuccess = false;
|
||||
ctrl.error =
|
||||
'Error associating metadata with ' +
|
||||
'test run: ' + angular.toJson(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
ctrl.associateProductVersion();
|
||||
if (!ctrl.showError) {
|
||||
ctrl.showSuccess = true;
|
||||
$state.reload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will close/dismiss the modal.
|
||||
*/
|
||||
ctrl.close = function () {
|
||||
$uibModalInstance.dismiss('exit');
|
||||
};
|
||||
}
|
||||
})();
|
||||
@@ -1,252 +0,0 @@
|
||||
<h3>{{ctrl.pageHeader}}</h3>
|
||||
<p>{{ctrl.pageParagraph}}</p>
|
||||
|
||||
<div class="result-filters">
|
||||
<h4>Filters</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label for="cpid">Start Date</label>
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control"
|
||||
uib-datepicker-popup="{{ctrl.format}}"
|
||||
ng-model="ctrl.startDate" is-open="ctrl.startOpen"
|
||||
close-text="Close" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="ctrl.open($event, 'startOpen')">
|
||||
<i class="glyphicon glyphicon-calendar"></i>
|
||||
</button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="cpid">End Date</label>
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control"
|
||||
uib-datepicker-popup="{{ctrl.format}}"
|
||||
ng-model="ctrl.endDate" is-open="ctrl.endOpen"
|
||||
close-text="Close" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="ctrl.open($event, 'endOpen')">
|
||||
<i class="glyphicon glyphicon-calendar"></i>
|
||||
</button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-3" style="margin-top:24px;">
|
||||
<button type="submit" class="btn btn-primary" ng-click="ctrl.update()">Filter</button>
|
||||
<button type="submit" class="btn btn-primary btn-danger" ng-click="ctrl.clearFilters()">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<table ng-show="ctrl.data" class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-if="ctrl.isUserResults"></th>
|
||||
<th>Upload Date</th>
|
||||
<th>Test Run ID</th>
|
||||
<th ng-if="ctrl.isUserResults">Vendor</th>
|
||||
<th ng-if="ctrl.isUserResults">Product (version)</th>
|
||||
<th ng-if="ctrl.isUserResults">Target Program</th>
|
||||
<th ng-if="ctrl.isUserResults">Guideline</th>
|
||||
<th ng-if="ctrl.isUserResults">Verified</th>
|
||||
<th ng-if="ctrl.isUserResults">Shared</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat-start="(index, result) in ctrl.data.results">
|
||||
<td ng-if="ctrl.isUserResults">
|
||||
<a ng-if="!result.expanded"
|
||||
class="glyphicon glyphicon-plus"
|
||||
ng-click="result.expanded = true">
|
||||
</a>
|
||||
<a ng-if="result.expanded"
|
||||
class="glyphicon glyphicon-minus"
|
||||
ng-click="result.expanded = false">
|
||||
</a>
|
||||
</td>
|
||||
<td>{{result.created_at}}</td>
|
||||
<td><a ui-sref="resultsDetail({testID: result.id})">
|
||||
{{result.id.slice(0, 8)}}...{{result.id.slice(-8)}}
|
||||
</a>
|
||||
</td>
|
||||
<td ng-if="ctrl.isUserResults">
|
||||
{{ctrl.vendors[result.product_version.product_info.organization_id].name || '-'}}
|
||||
</td>
|
||||
<td ng-if="ctrl.isUserResults">{{result.product_version.product_info.name || '-'}}
|
||||
<span ng-if="result.product_version.version">
|
||||
({{result.product_version.version}})
|
||||
</span>
|
||||
</td>
|
||||
<td ng-if="ctrl.isUserResults">{{ctrl.targetMappings[result.meta.target] || '-'}}</td>
|
||||
<td ng-if="ctrl.isUserResults">{{result.meta.guideline.slice(0, -5) || '-'}}</td>
|
||||
<td ng-if="ctrl.isUserResults">
|
||||
<span ng-if="result.verification_status" class="glyphicon glyphicon-ok"></span>
|
||||
<span ng-if="!result.verification_status">-</span>
|
||||
|
||||
</td>
|
||||
<td ng-if="ctrl.isUserResults">
|
||||
<span ng-show="result.meta.shared" class="glyphicon glyphicon-share"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="result.expanded" ng-repeat-end>
|
||||
<td></td>
|
||||
<td colspan="3">
|
||||
<strong>Publicly Shared:</strong>
|
||||
<span ng-if="result.meta.shared == 'true' && !result.sharedEdit">Yes</span>
|
||||
<span ng-if="!result.meta.shared && !result.sharedEdit">
|
||||
<em>No</em>
|
||||
</span>
|
||||
<select ng-if="result.sharedEdit"
|
||||
ng-model="result.meta.shared"
|
||||
class="form-inline">
|
||||
<option value="true">Yes</option>
|
||||
<option value="">No</option>
|
||||
</select>
|
||||
<a ng-if="!result.sharedEdit"
|
||||
ng-click="result.sharedEdit = true"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil"></a>
|
||||
<a ng-if="result.sharedEdit"
|
||||
ng-click="ctrl.associateMeta(index,'shared',result.meta.shared)"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk"></a>
|
||||
<br />
|
||||
|
||||
<strong>Associated Guideline:</strong>
|
||||
<span ng-if="!result.meta.guideline && !result.guidelineEdit">
|
||||
<em>None</em>
|
||||
</span>
|
||||
<span ng-if="result.meta.guideline && !result.guidelineEdit">
|
||||
{{result.meta.guideline.slice(0, -5)}}
|
||||
</span>
|
||||
<select ng-if="result.guidelineEdit"
|
||||
ng-model="result.meta.guideline"
|
||||
ng-options="o as o.slice(0, -5) for o in ctrl.versionList"
|
||||
class="form-inline">
|
||||
<option value="">None</option>
|
||||
</select>
|
||||
<a ng-if="!result.guidelineEdit"
|
||||
ng-click="ctrl.getVersionList();result.guidelineEdit = true"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil"></a>
|
||||
<a ng-if="result.guidelineEdit"
|
||||
ng-click="ctrl.associateMeta(index, 'guideline', result.meta.guideline)"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk">
|
||||
</a>
|
||||
<br />
|
||||
|
||||
<strong>Associated Target Program:</strong>
|
||||
<span ng-if="!result.meta.target && !result.targetEdit">
|
||||
<em>None</em>
|
||||
</span>
|
||||
<span ng-if="result.meta.target && !result.targetEdit">
|
||||
{{ctrl.targetMappings[result.meta.target]}}</span>
|
||||
<select ng-if="result.targetEdit"
|
||||
ng-model="result.meta.target"
|
||||
class="form-inline">
|
||||
<option value="">None</option>
|
||||
<option value="platform">OpenStack Powered Platform</option>
|
||||
<option value="compute">OpenStack Powered Compute</option>
|
||||
<option value="object">OpenStack Powered Object Storage</option>
|
||||
<option value="dns">OpenStack with DNS</option>
|
||||
<option value="orchestration">OpenStack with Orchestration</option>
|
||||
<option value="shared_file_system">OpenStack with Shared File System</option>
|
||||
<option value="load_balancer">OpenStack with Load Balancer</option>
|
||||
<option value="key_manager">OpenStack with Key Manager</option>
|
||||
</select>
|
||||
<a ng-if="!result.targetEdit"
|
||||
ng-click="result.targetEdit = true;"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil">
|
||||
</a>
|
||||
<a ng-if="result.targetEdit"
|
||||
ng-click="ctrl.associateMeta(index, 'target', result.meta.target)"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk">
|
||||
</a>
|
||||
<br />
|
||||
|
||||
<strong>Associated Product:</strong>
|
||||
<span ng-if="!result.product_version && !result.productEdit">
|
||||
<em>None</em>
|
||||
</span>
|
||||
<span ng-if="result.product_version && !result.productEdit">
|
||||
<span ng-if="ctrl.products[result.product_version.product_info.id].product_type == 0">
|
||||
<a ui-sref="distro({id: result.product_version.product_info.id})">
|
||||
{{ctrl.products[result.product_version.product_info.id].name}}
|
||||
<small ng-if="result.product_version.version">
|
||||
({{result.product_version.version}})
|
||||
</small>
|
||||
</a>
|
||||
</span>
|
||||
<span ng-if="ctrl.products[result.product_version.product_info.id].product_type != 0">
|
||||
<a ui-sref="cloud({id: result.product_version.product_info.id})">
|
||||
{{ctrl.products[result.product_version.product_info.id].name}}
|
||||
<small ng-if="result.product_version.version">
|
||||
({{result.product_version.version}})
|
||||
</small>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<select ng-if="result.productEdit"
|
||||
ng-options="product as product.name for product in ctrl.products | arrayConverter | orderBy: 'name' track by product.id"
|
||||
ng-model="result.selectedProduct"
|
||||
ng-change="ctrl.getProductVersions(result)">
|
||||
<option value="">-- No Product --</option>
|
||||
</select>
|
||||
|
||||
<span ng-if="result.productVersions.length && result.productEdit">
|
||||
<span class="glyphicon glyphicon-arrow-right" style="padding-right:3px;color:#303030;"></span>
|
||||
Version:
|
||||
<select ng-options="version as version.version for version in result.productVersions | orderBy: 'version' track by version.id"
|
||||
ng-model="result.selectedVersion">
|
||||
</select>
|
||||
|
||||
</span>
|
||||
<a ng-if="!result.productEdit"
|
||||
ng-click="ctrl.prepVersionEdit(result)"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil">
|
||||
</a>
|
||||
<a ng-if="result.productEdit"
|
||||
ng-click="ctrl.associateProductVersion(result)"
|
||||
confirm="Once you associate this test to this product, ownership
|
||||
will be transferred to the product's vendor admins.
|
||||
Continue?"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk">
|
||||
</a>
|
||||
<br />
|
||||
</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 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>
|
||||
@@ -1,355 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('ResultsController', ResultsController);
|
||||
|
||||
ResultsController.$inject = [
|
||||
'$scope', '$http', '$filter', '$state', 'refstackApiUrl','raiseAlert'
|
||||
];
|
||||
|
||||
/**
|
||||
* RefStack 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, refstackApiUrl,
|
||||
raiseAlert) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.update = update;
|
||||
ctrl.open = open;
|
||||
ctrl.clearFilters = clearFilters;
|
||||
ctrl.associateMeta = associateMeta;
|
||||
ctrl.getVersionList = getVersionList;
|
||||
ctrl.getUserProducts = getUserProducts;
|
||||
ctrl.getVendors = getVendors;
|
||||
ctrl.associateProductVersion = associateProductVersion;
|
||||
ctrl.getProductVersions = getProductVersions;
|
||||
ctrl.prepVersionEdit = prepVersionEdit;
|
||||
if (ctrl.target === 'dns' ||
|
||||
ctrl.target === 'orchestration' ||
|
||||
ctrl.target === 'shared_file_system' ||
|
||||
ctrl.target === 'load_balancer' ||
|
||||
ctrl.target === 'key_manager'
|
||||
) {
|
||||
ctrl.gl_type = ctrl.target;
|
||||
} else {
|
||||
ctrl.gl_type = 'powered';
|
||||
}
|
||||
|
||||
/** Mappings of Interop WG components to marketing program names. */
|
||||
ctrl.targetMappings = {
|
||||
'platform': 'Openstack Powered Platform',
|
||||
'compute': 'OpenStack Powered Compute',
|
||||
'object': 'OpenStack Powered Object Storage',
|
||||
'dns': 'OpenStack with DNS',
|
||||
'orchestration': 'OpenStack with Orchestration',
|
||||
'shared_file_system': 'OpenStack with Shared File System',
|
||||
'load_balancer': 'OpenStack with Load Balancer',
|
||||
'key_manager': 'OpenStack with Key Manager'
|
||||
};
|
||||
|
||||
/** 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 Refstack 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';
|
||||
|
||||
/** Check to see if this page should display user-specific results. */
|
||||
ctrl.isUserResults = $state.current.name === 'userResults';
|
||||
|
||||
// Should only be on user-results-page if authenticated.
|
||||
if (ctrl.isUserResults && !$scope.auth.isAuthenticated) {
|
||||
$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.';
|
||||
|
||||
if (ctrl.isUserResults) {
|
||||
ctrl.authRequest = $scope.auth.doSignCheck()
|
||||
.then(ctrl.update);
|
||||
ctrl.getUserProducts();
|
||||
} else {
|
||||
ctrl.update();
|
||||
}
|
||||
|
||||
ctrl.getVendors();
|
||||
|
||||
/**
|
||||
* This will contact the Refstack 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 = refstackApiUrl + '/results' +
|
||||
'?page=' + ctrl.currentPage;
|
||||
var start = $filter('date')(ctrl.startDate, 'yyyy-MM-dd');
|
||||
if (start) {
|
||||
content_url =
|
||||
content_url + '&start_date=' + start + ' 00:00:00';
|
||||
}
|
||||
var end = $filter('date')(ctrl.endDate, 'yyyy-MM-dd');
|
||||
if (end) {
|
||||
content_url = content_url + '&end_date=' + end + ' 23:59:59';
|
||||
}
|
||||
if (ctrl.isUserResults) {
|
||||
content_url = content_url + '&signed';
|
||||
}
|
||||
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;
|
||||
}).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 = [
|
||||
refstackApiUrl, '/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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an array of available capability files from the Refstack
|
||||
* API server, sort this array reverse-alphabetically, and store it in
|
||||
* a scoped variable.
|
||||
* Sample API return array: ["2015.03.json", "2015.04.json"]
|
||||
*/
|
||||
function getVersionList() {
|
||||
if (ctrl.versionList) {
|
||||
return;
|
||||
}
|
||||
var content_url = refstackApiUrl + '/guidelines';
|
||||
ctrl.versionsRequest =
|
||||
$http.get(content_url).success(function (data) {
|
||||
// NEED TO sort after grabbing the GL_TYPE DATA
|
||||
let gl_files = data[ctrl.gl_type];
|
||||
ctrl.versionList = gl_files.map((gl_obj) => gl_obj.name);
|
||||
ctrl.version = ctrl.versionList[1];
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title,
|
||||
'Unable to retrieve version list');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products user has management rights to or all products depending
|
||||
* on the passed in parameter value.
|
||||
*/
|
||||
function getUserProducts() {
|
||||
if (ctrl.products) {
|
||||
return;
|
||||
}
|
||||
var contentUrl = refstackApiUrl + '/products';
|
||||
ctrl.productsRequest =
|
||||
$http.get(contentUrl).success(function (data) {
|
||||
ctrl.products = {};
|
||||
angular.forEach(data.products, function(prod) {
|
||||
if (prod.can_manage) {
|
||||
ctrl.products[prod.id] = prod;
|
||||
}
|
||||
});
|
||||
}).error(function (error) {
|
||||
ctrl.products = null;
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving Products listing from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API to get a listing of
|
||||
* vendors.
|
||||
*/
|
||||
function getVendors() {
|
||||
var contentUrl = refstackApiUrl + '/vendors';
|
||||
ctrl.vendorsRequest =
|
||||
$http.get(contentUrl).success(function (data) {
|
||||
ctrl.vendors = {};
|
||||
data.vendors.forEach(function(vendor) {
|
||||
ctrl.vendors[vendor.id] = vendor;
|
||||
});
|
||||
}).error(function (error) {
|
||||
ctrl.vendors = null;
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving vendor listing from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PUT request to the API server to associate a product with
|
||||
* a test result.
|
||||
*/
|
||||
function associateProductVersion(result) {
|
||||
var verId = result.selectedVersion ?
|
||||
result.selectedVersion.id : null;
|
||||
var testId = result.id;
|
||||
var url = refstackApiUrl + '/results/' + testId;
|
||||
ctrl.associateRequest = $http.put(url, {'product_version_id':
|
||||
verId})
|
||||
.success(function () {
|
||||
result.product_version = result.selectedVersion;
|
||||
if (result.selectedVersion) {
|
||||
result.product_version.product_info =
|
||||
result.selectedProduct;
|
||||
}
|
||||
result.productEdit = false;
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all versions for a product.
|
||||
*/
|
||||
function getProductVersions(result) {
|
||||
if (!result.selectedProduct) {
|
||||
result.productVersions = [];
|
||||
result.selectedVersion = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var url = refstackApiUrl + '/products/' +
|
||||
result.selectedProduct.id + '/versions';
|
||||
ctrl.getVersionsRequest = $http.get(url)
|
||||
.success(function (data) {
|
||||
result.productVersions = data;
|
||||
|
||||
// If the test result isn't already associated to a
|
||||
// version, default it to the null version.
|
||||
if (!result.product_version) {
|
||||
angular.forEach(data, function(ver) {
|
||||
if (!ver.version) {
|
||||
result.selectedVersion = ver;
|
||||
}
|
||||
});
|
||||
}
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate variables needed for editing product/version
|
||||
* associations.
|
||||
*/
|
||||
function prepVersionEdit(result) {
|
||||
result.productEdit = true;
|
||||
if (result.product_version) {
|
||||
result.selectedProduct =
|
||||
ctrl.products[result.product_version.product_info.id];
|
||||
}
|
||||
result.selectedVersion = result.product_version;
|
||||
ctrl.getProductVersions(result);
|
||||
}
|
||||
|
||||
}
|
||||
})();
|
||||
@@ -1,62 +0,0 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">×</button>
|
||||
<h4>Edit Vendor</h4>
|
||||
<p>Make changes to your vendor.</p>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input ng-disabled="modal.vendor.type==3 && !modal.isAdmin"
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="name"
|
||||
ng-model="modal.vendor.name">
|
||||
<br />
|
||||
<label for="description">Description</label>
|
||||
<textarea type="text"
|
||||
class="form-control"
|
||||
id="description"
|
||||
ng-model="modal.vendor.description"
|
||||
rows="4"
|
||||
wrap="off">
|
||||
</textarea>
|
||||
<br />
|
||||
<label for="properties">Properties</label>
|
||||
<small><span class="text-muted glyphicon glyphicon-info-sign" title="Add arbitrary custom properties to your vendor."></span></small>
|
||||
<div class="row" ng-repeat="(index, prop) in modal.vendorProperties">
|
||||
<div class="col-md-2">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
ng-model="prop.key">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
ng-model="prop.value">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<a class="text-danger glyphicon glyphicon-remove"
|
||||
title="Delete this property?"
|
||||
ng-click="modal.removeProperty(index)"
|
||||
style='top:8px'></a>
|
||||
</div>
|
||||
</div>
|
||||
<div><small><a ng-click="modal.addField()"><span class="glyphicon glyphicon-plus"></span> Add new property</a></small></div>
|
||||
</div>
|
||||
<div ng-show="modal.showError" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
{{modal.error}}
|
||||
</div>
|
||||
<div ng-show="modal.showSuccess" class="alert alert-success" role="success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<span class="sr-only">Success:</span>
|
||||
Changes saved successfully.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.saveChanges()">Save Changes</button>
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
115
refstack-ui/app/components/vendors/vendor.html
vendored
115
refstack-ui/app/components/vendors/vendor.html
vendored
@@ -1,115 +0,0 @@
|
||||
<div ng-show="ctrl.vendor" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="pull-left">
|
||||
<div class="test-report">
|
||||
<strong>Vendor ID:</strong> {{ctrl.vendorId}}<br />
|
||||
<strong>Type:</strong>
|
||||
<span ng-show="ctrl.vendor.type == 0">OpenStack</span>
|
||||
<span ng-show="ctrl.vendor.type == 1" class="text-info">Private</span>
|
||||
<span ng-show="ctrl.vendor.type == 2" class="text-warning">Pending Approval</span>
|
||||
<span ng-show="ctrl.vendor.type == 3" class="text-success">Official</span>
|
||||
<br />
|
||||
<strong>Name:</strong> {{ctrl.vendor.name}}<br />
|
||||
<strong>Description:</strong> {{ctrl.vendor.description || '-'}}<br />
|
||||
<div ng-if="ctrl.vendorProperties">
|
||||
<strong>Properties:</strong>
|
||||
<ul>
|
||||
<li ng-repeat="(key, value) in ctrl.vendorProperties">
|
||||
<em>{{key}}</em>: {{value}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pull-right">
|
||||
<a ng-if="ctrl.vendor.canDelete" href="javascript:void(0)" ng-click="ctrl.deleteVendor()" confirm="Are you sure you want to delete this vendor?">Delete</a><br />
|
||||
<a ng-if="ctrl.vendor.canEdit" href="javascript:void(0)" ng-click="ctrl.openVendorEditModal()">Edit</a><br />
|
||||
<a ng-if="ctrl.vendor.canRegister" href="javascript:void(0)" ng-click="ctrl.registerVendor()">Register with Foundation</a><br />
|
||||
<a ng-if="ctrl.vendor.canApprove && ctrl.vendor.type == 2" href="javascript:void(0)" ng-click="ctrl.approveVendor()"
|
||||
confirm="Are you sure you want to approve this vendor?">Approve registration</a><br />
|
||||
<a ng-if="ctrl.vendor.canApprove && ctrl.vendor.type == 2" href="javascript:void(0)" ng-click="ctrl.declineVendor()">Decline registration</a><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div ng-if="ctrl.vendorProperties.registration_decline_reason">
|
||||
<hr />
|
||||
<strong>Registration decline reason:</strong> {{ctrl.vendorProperties.registration_decline_reason}}<br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="ctrl.vendorUsers" class="row">
|
||||
<hr />
|
||||
<h3>Vendor Users</h3>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Open ID</th>
|
||||
<th>Full Name</th>
|
||||
<th>E-Mail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="user in ctrl.vendorUsers">
|
||||
<td>{{user.openid}}</td>
|
||||
<td>{{user.fullname}}</td>
|
||||
<td>{{user.email}}</td>
|
||||
<td>
|
||||
<a ng-if="user.openid != ctrl.currentUser"
|
||||
href="javascript:void(0)"
|
||||
ng-click="ctrl.removeUserFromVendor(user.openid)"
|
||||
confirm="Are you sure you want to remove this user from vendor?">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="row form-group">
|
||||
<div class="col-md-6">
|
||||
<label for="new_user">Add user to vendor:</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="new_user"
|
||||
placeholder="Input Open ID"
|
||||
ng-model="ctrl.userToAdd">
|
||||
</div>
|
||||
<div class="col-md-1" style="margin-top:25px;">
|
||||
<button type="submit" class="btn btn-primary" ng-click="ctrl.addUserToVendor(ctrl.userToAdd)">Add User</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<h3>Vendor Products</h3>
|
||||
<table ng-show="ctrl.vendorProducts" class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Product Type</th>
|
||||
<th>Description</th>
|
||||
<th>Visibility</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="product in ctrl.vendorProducts">
|
||||
<td ng-if="product.product_type == 0"><a ui-sref="distro({id: product.id})">{{product.name}}</a></td>
|
||||
<td ng-if="product.product_type != 0"><a ui-sref="cloud({id: product.id})">{{product.name}}</a></td>
|
||||
<td>{{ctrl.getProductTypeDescription(product.product_type)}}</td>
|
||||
<td>{{product.description}}</td>
|
||||
<td>{{product.public ? 'Public' : 'Private'}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading animation divs -->
|
||||
<div cg-busy="{promise:ctrl.vendorRequest,message:'Loading'}"></div>
|
||||
<div cg-busy="{promise:ctrl.usersRequest,message:'Loading'}"></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>
|
||||
@@ -1,347 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('VendorController', VendorController);
|
||||
|
||||
VendorController.$inject = [
|
||||
'$rootScope', '$scope', '$http', '$state', '$stateParams', '$window',
|
||||
'$uibModal', 'refstackApiUrl', 'raiseAlert', 'confirmModal'
|
||||
];
|
||||
|
||||
/**
|
||||
* RefStack Vendor Controller
|
||||
* This controller is for the '/vendor/' details page where owner can
|
||||
* view details of the Vendor and manage users.
|
||||
*/
|
||||
function VendorController($rootScope, $scope, $http, $state, $stateParams,
|
||||
$window, $uibModal, refstackApiUrl, raiseAlert, confirmModal) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.getVendor = getVendor;
|
||||
ctrl.getVendorUsers = getVendorUsers;
|
||||
ctrl.getVendorProducts = getVendorProducts;
|
||||
ctrl.getProductTypeDescription = getProductTypeDescription;
|
||||
ctrl.registerVendor = registerVendor;
|
||||
ctrl.approveVendor = approveVendor;
|
||||
ctrl.declineVendor = declineVendor;
|
||||
ctrl.deleteVendor = deleteVendor;
|
||||
ctrl.removeUserFromVendor = removeUserFromVendor;
|
||||
ctrl.addUserToVendor = addUserToVendor;
|
||||
ctrl.openVendorEditModal = openVendorEditModal;
|
||||
|
||||
/** The vendor id extracted from the URL route. */
|
||||
ctrl.vendorId = $stateParams.vendorID;
|
||||
|
||||
// Should only be on user-vendors-page if authenticated.
|
||||
if (!$scope.auth.isAuthenticated) {
|
||||
$state.go('home');
|
||||
}
|
||||
|
||||
ctrl.getVendor();
|
||||
ctrl.getVendorUsers();
|
||||
ctrl.getVendorProducts();
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API to get a vendor information.
|
||||
*/
|
||||
function getVendor() {
|
||||
ctrl.showError = false;
|
||||
ctrl.vendor = null;
|
||||
// Construct the API URL based on user-specified filters.
|
||||
var contentUrl = refstackApiUrl + '/vendors/' + ctrl.vendorId;
|
||||
ctrl.vendorRequest =
|
||||
$http.get(contentUrl).success(function(data) {
|
||||
ctrl.vendor = data;
|
||||
var isAdmin = $rootScope.auth.currentUser.is_admin;
|
||||
ctrl.vendor.canDelete = ctrl.vendor.canEdit =
|
||||
ctrl.vendor.type !== 0
|
||||
&& (ctrl.vendor.can_manage || isAdmin);
|
||||
ctrl.vendor.canRegister =
|
||||
ctrl.vendor.type === 1;
|
||||
ctrl.vendor.canApprove = isAdmin;
|
||||
ctrl.vendorProperties = angular.fromJson(data.properties);
|
||||
}).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will 'send' application for registration.
|
||||
*/
|
||||
function registerVendor() {
|
||||
var url = [refstackApiUrl, '/vendors/', ctrl.vendorId,
|
||||
'/action'].join('');
|
||||
$http.post(url, {register: null}).success(function() {
|
||||
ctrl.getVendor();
|
||||
}).error(function(error) {
|
||||
raiseAlert('danger', 'Error: ', error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will approve application for registration.
|
||||
*/
|
||||
function approveVendor() {
|
||||
var url = [refstackApiUrl, '/vendors/', ctrl.vendorId,
|
||||
'/action'].join('');
|
||||
$http.post(url, {approve: null}).success(function() {
|
||||
ctrl.getVendor();
|
||||
}).error(function(error) {
|
||||
raiseAlert('danger', 'Error: ', error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will decline a vendor's application for registration.
|
||||
*/
|
||||
function declineVendor() {
|
||||
confirmModal('Please input decline reason', function(reason) {
|
||||
var url = [refstackApiUrl, '/vendors/', ctrl.vendorId,
|
||||
'/action'].join('');
|
||||
var content = {deny: null, registration_decline_reason: reason};
|
||||
$http.post(url, content).success(
|
||||
function() {
|
||||
ctrl.getVendor();
|
||||
}).error(function(error) {
|
||||
raiseAlert('danger', 'Error: ', error.detail);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the current vendor.
|
||||
*/
|
||||
function deleteVendor() {
|
||||
var url = [refstackApiUrl, '/vendors/', ctrl.vendorId].join('');
|
||||
$http.delete(url).success(function () {
|
||||
$window.location.href = '/';
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', 'Error: ', error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates list of users in the vendor's group
|
||||
*/
|
||||
function getVendorUsers() {
|
||||
ctrl.showError = false;
|
||||
var contentUrl = refstackApiUrl + '/vendors/' + ctrl.vendorId
|
||||
+ '/users';
|
||||
ctrl.usersRequest =
|
||||
$http.get(contentUrl).success(function(data) {
|
||||
ctrl.vendorUsers = data;
|
||||
ctrl.currentUser = $rootScope.auth.currentUser.openid;
|
||||
}).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates list of users in the vendor's group
|
||||
*/
|
||||
function getVendorProducts() {
|
||||
ctrl.showError = false;
|
||||
var contentUrl = refstackApiUrl + '/products?organization_id='
|
||||
+ ctrl.vendorId;
|
||||
ctrl.productsRequest =
|
||||
$http.get(contentUrl).success(function(data) {
|
||||
ctrl.vendorProducts = data.products;
|
||||
}).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product type description given the type integer.
|
||||
*/
|
||||
function getProductTypeDescription(product_type) {
|
||||
switch (product_type) {
|
||||
case 0:
|
||||
return 'Distro';
|
||||
case 1:
|
||||
return 'Public Cloud';
|
||||
case 2:
|
||||
return 'Hosted Private Cloud';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes user with specific openid from vendor's group
|
||||
* @param {Object} openid
|
||||
*/
|
||||
function removeUserFromVendor(openid) {
|
||||
var url = [refstackApiUrl, '/vendors/', ctrl.vendorId,
|
||||
'/users/', btoa(openid)].join('');
|
||||
$http.delete(url).success(function () {
|
||||
ctrl.getVendorUsers();
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', 'Error: ', error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a user to a vendor group given an Open ID.
|
||||
* @param {Object} openid
|
||||
*/
|
||||
function addUserToVendor(openid) {
|
||||
var url = [refstackApiUrl, '/vendors/', ctrl.vendorId,
|
||||
'/users/', btoa(openid)].join('');
|
||||
$http.put(url).success(function() {
|
||||
ctrl.userToAdd = '';
|
||||
ctrl.getVendorUsers();
|
||||
}).error(function(error) {
|
||||
raiseAlert('danger', 'Problem adding user. ' +
|
||||
'Is the Open ID correct? Error: ',
|
||||
error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will open the modal that will allow a user to edit
|
||||
*/
|
||||
function openVendorEditModal() {
|
||||
$uibModal.open({
|
||||
templateUrl: '/components/vendors/partials' +
|
||||
'/vendorEditModal.html',
|
||||
backdrop: true,
|
||||
windowClass: 'modal',
|
||||
animation: true,
|
||||
controller: 'VendorEditModalController as modal',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
vendor: function () {
|
||||
return ctrl.vendor;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('VendorEditModalController', VendorEditModalController);
|
||||
|
||||
VendorEditModalController.$inject = [
|
||||
'$rootScope',
|
||||
'$uibModalInstance', '$http', '$state', 'vendor', 'refstackApiUrl'
|
||||
];
|
||||
|
||||
/**
|
||||
* Vendor Edit Modal Controller
|
||||
* This controls the modal that allows editing a vendor.
|
||||
*/
|
||||
function VendorEditModalController($rootScope, $uibModalInstance, $http,
|
||||
$state, vendor, refstackApiUrl) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.close = close;
|
||||
ctrl.addField = addField;
|
||||
ctrl.saveChanges = saveChanges;
|
||||
ctrl.removeProperty = removeProperty;
|
||||
|
||||
ctrl.vendor = angular.copy(vendor);
|
||||
ctrl.vendorName = vendor.name;
|
||||
ctrl.vendorProperties = [];
|
||||
ctrl.isAdmin = $rootScope.auth.currentUser.is_admin;
|
||||
|
||||
parseVendorProperties();
|
||||
|
||||
/**
|
||||
* Close the vendor edit modal.
|
||||
*/
|
||||
function close() {
|
||||
$uibModalInstance.dismiss('exit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a blank property key-value pair into the vendorProperties
|
||||
* array. This will spawn new input boxes.
|
||||
*/
|
||||
function addField() {
|
||||
ctrl.vendorProperties.push({'key': '', 'value': ''});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PUT request to the server with the changes.
|
||||
*/
|
||||
function saveChanges() {
|
||||
ctrl.showError = false;
|
||||
ctrl.showSuccess = false;
|
||||
var url = [refstackApiUrl, '/vendors/', ctrl.vendor.id].join('');
|
||||
var properties = propertiesToJson();
|
||||
var content = {'description': ctrl.vendor.description,
|
||||
'properties': properties};
|
||||
if (ctrl.vendorName !== ctrl.vendor.name) {
|
||||
content.name = ctrl.vendor.name;
|
||||
}
|
||||
$http.put(url, content).success(function() {
|
||||
ctrl.showSuccess = true;
|
||||
$state.reload();
|
||||
}).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error = error.detail;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a property from the vendorProperties array at the given index.
|
||||
*/
|
||||
function removeProperty(index) {
|
||||
ctrl.vendorProperties.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the vendor properties and put them in a format more suitable
|
||||
* for forms.
|
||||
*/
|
||||
function parseVendorProperties() {
|
||||
var props = angular.fromJson(ctrl.vendor.properties);
|
||||
angular.forEach(props, function(value, key) {
|
||||
ctrl.vendorProperties.push({'key': key, 'value': value});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the list of property objects to a dict containing the
|
||||
* each key-value pair..
|
||||
*/
|
||||
function propertiesToJson() {
|
||||
var properties = {};
|
||||
for (var i = 0, len = ctrl.vendorProperties.length; i < len; i++) {
|
||||
var prop = ctrl.vendorProperties[i];
|
||||
if (prop.key && prop.value) {
|
||||
properties[prop.key] = prop.value;
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
})();
|
||||
75
refstack-ui/app/components/vendors/vendors.html
vendored
75
refstack-ui/app/components/vendors/vendors.html
vendored
@@ -1,75 +0,0 @@
|
||||
<h3>{{ctrl.pageHeader}}</h3>
|
||||
<p>{{ctrl.pageParagraph}}</p>
|
||||
|
||||
<div cg-busy="{promise:ctrl.authRequest,message:'Loading'}"></div>
|
||||
<div cg-busy="{promise:ctrl.vendorsRequest,message:'Loading'}"></div>
|
||||
|
||||
<div ng-show="ctrl.data" class="vendors-table">
|
||||
<label ng-if="ctrl.isAdminView && ctrl.isUserVendors">
|
||||
<input type="checkbox" ng-model="ctrl.withPrivate" ng-change="ctrl.updateData();"> Show private vendors
|
||||
</label>
|
||||
<br />
|
||||
<table ng-show="ctrl.data" class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="vendor in ctrl.data.vendors">
|
||||
<td ng-if="ctrl.isUserVendors"><a ui-sref="vendor({vendorID: vendor.id})">{{vendor.name}}</a></td>
|
||||
<td ng-if="!ctrl.isUserVendors">{{vendor.name}}</td>
|
||||
<td>{{vendor.description || '-'}}</td>
|
||||
<td>
|
||||
<span ng-show="vendor.type == 0" class="glyphicon glyphicon-exclamation-sign"> OpenStack</span>
|
||||
<span ng-show="vendor.type == 1" class="glyphicon glyphicon-eye-close"> Private</span>
|
||||
<span ng-show="vendor.type == 2" class="glyphicon glyphicon-transfer"> Pending Approval</span>
|
||||
<span ng-show="vendor.type == 3" class="glyphicon glyphicon-check"> Official</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.isUserVendors">
|
||||
<hr />
|
||||
<h4>Add New Vendor</h4>
|
||||
<p>Creating a vendor allows you to associate test results to specific vendors/products.
|
||||
Created vendors are private, but vendors can be registered with the Foundation to become public and official.
|
||||
This will require approval by a Foundation administrator.</p>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label>Name</label>
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control"
|
||||
ng-model="ctrl.name" close-text="Close" />
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label>Description</label>
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control" size="80"
|
||||
ng-model="ctrl.description" close-text="Close" />
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-3" style="margin-top:24px;">
|
||||
<button type="submit" class="btn btn-primary" ng-click="ctrl.addVendor()">Add Vendor</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="ctrl.showSuccess" class="alert alert-success" role="success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<span class="sr-only">Success:</span>
|
||||
Vendor successfully created.
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div cg-busy="{promise:ctrl.vendorsRequest,message:'Loading'}"></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>
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('VendorsController', VendorsController);
|
||||
|
||||
VendorsController.$inject = [
|
||||
'$rootScope', '$scope', '$http', '$state', 'refstackApiUrl'];
|
||||
|
||||
/**
|
||||
* RefStack Vendors Controller
|
||||
* This controller is for the '/user_vendors' or '/public_vendors' page
|
||||
* where a user can browse a listing of his/her vendors or public vendors.
|
||||
*/
|
||||
function VendorsController($rootScope, $scope, $http, $state,
|
||||
refstackApiUrl) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.update = update;
|
||||
ctrl.updateData = updateData;
|
||||
ctrl._filterVendor = _filterVendor;
|
||||
ctrl.addVendor = addVendor;
|
||||
|
||||
/** Check to see if this page should display user-specific vendors. */
|
||||
ctrl.isUserVendors = $state.current.name === 'userVendors';
|
||||
|
||||
/** Show private vendors in list for foundation admin */
|
||||
ctrl.withPrivate = false;
|
||||
|
||||
/** Properties for adding new vendor */
|
||||
ctrl.name = '';
|
||||
ctrl.description = '';
|
||||
|
||||
// Should only be on user-vendors-page if authenticated.
|
||||
if (ctrl.isUserVendors && !$scope.auth.isAuthenticated) {
|
||||
$state.go('home');
|
||||
}
|
||||
|
||||
ctrl.pageHeader = ctrl.isUserVendors ?
|
||||
'My Vendors' : 'Public Vendors';
|
||||
|
||||
ctrl.pageParagraph = ctrl.isUserVendors ?
|
||||
'Your added vendors are listed here.' :
|
||||
'Public Vendors approved by the OpenStack Foundation are ' +
|
||||
'listed here.';
|
||||
|
||||
if (ctrl.isUserVendors) {
|
||||
ctrl.authRequest = $scope.auth.doSignCheck()
|
||||
.then(ctrl.update);
|
||||
} else {
|
||||
ctrl.update();
|
||||
}
|
||||
|
||||
ctrl.rawData = null;
|
||||
ctrl.isAdminView = $rootScope.auth
|
||||
&& $rootScope.auth.currentUser
|
||||
&& $rootScope.auth.currentUser.is_admin;
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API to get a listing of vendors
|
||||
*/
|
||||
function update() {
|
||||
ctrl.showError = false;
|
||||
ctrl.data = null;
|
||||
// Construct the API URL based on user-specified filters.
|
||||
var contentUrl = refstackApiUrl + '/vendors';
|
||||
if (typeof ctrl.rawData === 'undefined'
|
||||
|| ctrl.rawData === null) {
|
||||
ctrl.vendorsRequest =
|
||||
$http.get(contentUrl).success(function (data) {
|
||||
ctrl.rawData = data;
|
||||
ctrl.updateData();
|
||||
}).error(function (error) {
|
||||
ctrl.rawData = null;
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error retrieving vendors listing from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
} else {
|
||||
ctrl.updateData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will update data for view with current settings on page.
|
||||
*/
|
||||
function updateData() {
|
||||
ctrl.data = {};
|
||||
ctrl.data.vendors = ctrl.rawData.vendors.filter(function(vendor) {
|
||||
return ctrl._filterVendor(vendor);
|
||||
});
|
||||
ctrl.data.vendors.sort(function(a, b) {
|
||||
if (a.type > b.type) {
|
||||
return 1;
|
||||
}
|
||||
if (a.type < b.type) {
|
||||
return -1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if vendor can be displayed on this page.
|
||||
*/
|
||||
function _filterVendor(vendor) {
|
||||
if (!ctrl.isUserVendors) {
|
||||
return vendor.type === 0 || vendor.type === 3;
|
||||
}
|
||||
|
||||
if (!$rootScope.auth || !$rootScope.auth.currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($rootScope.auth.currentUser.is_admin) {
|
||||
return vendor.type !== 1 || ctrl.withPrivate;
|
||||
}
|
||||
|
||||
return vendor.can_manage;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will add a new vendor record.
|
||||
*/
|
||||
function addVendor() {
|
||||
ctrl.showSuccess = false;
|
||||
ctrl.showError = false;
|
||||
var url = refstackApiUrl + '/vendors';
|
||||
var data = {
|
||||
name: ctrl.name,
|
||||
description: ctrl.description
|
||||
};
|
||||
$http.post(url, data).success(function () {
|
||||
ctrl.showSuccess = true;
|
||||
ctrl.name = '';
|
||||
ctrl.description = '';
|
||||
ctrl.rawData = null;
|
||||
ctrl.update();
|
||||
}).error(function (error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error =
|
||||
'Error adding new vendor: ' + angular.toJson(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1 +0,0 @@
|
||||
{"refstackApiUrl": "https://refstack.openstack.org/api/v1"}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 638 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 318 B |
@@ -1,65 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (c) 2015 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<html id="ng-app">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="Refstack">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Refstack</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
|
||||
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
|
||||
|
||||
<link rel="stylesheet" href="assets/lib/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="assets/lib/angular-busy/dist/angular-busy.min.css">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script src="assets/lib/angular/angular.min.js"></script>
|
||||
<script src="assets/lib/angular-ui-router/release/angular-ui-router.min.js"></script>
|
||||
<script src="assets/lib/angular-resource/angular-resource.min.js"></script>
|
||||
<script src="assets/lib/angular-bootstrap/ui-bootstrap-tpls.min.js"></script>
|
||||
<script src="assets/lib/angular-busy/dist/angular-busy.min.js"></script>
|
||||
<script src="assets/lib/angular-confirm-modal/angular-confirm.js"></script>
|
||||
<script src="app.js"></script>
|
||||
|
||||
<!-- Controllers -->
|
||||
<script src="shared/header/headerController.js"></script>
|
||||
<script src="shared/alerts/alertModalFactory.js"></script>
|
||||
<script src="shared/alerts/confirmModalFactory.js"></script>
|
||||
<script src="components/about/aboutController.js"></script>
|
||||
<script src="components/guidelines/guidelinesController.js"></script>
|
||||
<script src="components/results/resultsController.js"></script>
|
||||
<script src="components/results-report/resultsReportController.js"></script>
|
||||
<script src="components/profile/profileController.js"></script>
|
||||
<script src="components/auth-failure/authFailureController.js"></script>
|
||||
<script src="components/logout/logoutController.js"></script>
|
||||
<script src="components/vendors/vendorController.js"></script>
|
||||
<script src="components/vendors/vendorsController.js"></script>
|
||||
<script src="components/products/productController.js"></script>
|
||||
<script src="components/products/productsController.js"></script>
|
||||
|
||||
<!-- Filters -->
|
||||
<script src="shared/filters.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="container">
|
||||
<header ng-include src="'shared/header/header.html'"></header>
|
||||
|
||||
<div ui-view></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +0,0 @@
|
||||
# robotstxt.org
|
||||
|
||||
User-agent: *
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<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>
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.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: '/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('refstackApp')
|
||||
.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();
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,13 +0,0 @@
|
||||
<div class="modal-header"><h3 class="modal-title">Confirm</h3></div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="confirmText">{{confirmModal.data.text}}:</label>
|
||||
<textarea type="text" class="form-control"
|
||||
rows="5" ng-model="confirmModal.inputText" id="confirmText">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" ng-click="confirmModal.confirm()">Ok</button>
|
||||
<button class="btn btn-default" ng-click="confirmModal.cancel()">Cancel</button>
|
||||
</div>
|
||||
@@ -1,67 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.factory('confirmModal', confirmModal);
|
||||
|
||||
confirmModal.$inject = ['$uibModal'];
|
||||
|
||||
/**
|
||||
* Opens confirm modal dialog with input textbox
|
||||
*/
|
||||
function confirmModal($uibModal) {
|
||||
return function(text, successHandler) {
|
||||
$uibModal.open({
|
||||
templateUrl: '/shared/alerts/confirmModal.html',
|
||||
controller: 'CustomConfirmModalController as confirmModal',
|
||||
size: 'md',
|
||||
resolve: {
|
||||
data: function () {
|
||||
return {
|
||||
text: text,
|
||||
successHandler: successHandler
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('CustomConfirmModalController',
|
||||
CustomConfirmModalController);
|
||||
|
||||
CustomConfirmModalController.$inject = ['$uibModalInstance', 'data'];
|
||||
|
||||
/**
|
||||
* This is the controller for the alert pop-up.
|
||||
*/
|
||||
function CustomConfirmModalController($uibModalInstance, data) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.confirm = confirm;
|
||||
ctrl.cancel = cancel;
|
||||
|
||||
ctrl.data = angular.copy(data);
|
||||
|
||||
/**
|
||||
* Initiate confirmation and call the success handler with the
|
||||
* input text.
|
||||
*/
|
||||
function confirm() {
|
||||
$uibModalInstance.close();
|
||||
if (angular.isDefined(ctrl.data.successHandler)) {
|
||||
ctrl.data.successHandler(ctrl.inputText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the confirm modal without initiating changes.
|
||||
*/
|
||||
function cancel() {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.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('refstackApp')
|
||||
.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);
|
||||
};
|
||||
}
|
||||
})();
|
||||
@@ -1,51 +0,0 @@
|
||||
<div class="heading"><a ui-sref="home"><img src="assets/img/OpenStack_Project_Refstack_mascot_90x90.png" alt="RefStack"></a>
|
||||
RefStack
|
||||
</div>
|
||||
<nav class="navbar navbar-default" role="navigation" ng-controller="HeaderController as header">
|
||||
<div class="container-fluid">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" ng-click="header.navbarCollapsed = !header.navbarCollapsed">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar" uib-collapse="header.navbarCollapsed">
|
||||
<ul class="nav navbar-nav">
|
||||
<li ng-class="{ active: header.isActive('/')}"><a ui-sref="home">Home</a></li>
|
||||
<li ng-class="{ active: header.isActive('/about')}"><a ui-sref="about">About</a></li>
|
||||
<li ng-class="{ active: header.isActive('/guidelines')}"><a ui-sref="guidelines">OpenStack Powered™ Guidelines</a></li>
|
||||
<li ng-class="{ active: header.isActive('/community_results')}"><a ui-sref="communityResults">Community Results</a></li>
|
||||
<!--
|
||||
<li ng-class="{ active: header.isCatalogActive('public')}" class="dropdown" uib-dropdown>
|
||||
<a role="button" class="dropdown-toggle" uib-dropdown-toggle>
|
||||
Catalog <strong class="caret"></strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ui-sref="publicVendors">Vendors</a></li>
|
||||
<li><a ui-sref="publicProducts">Products</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li ng-class="{ active: header.isActive('/user_results')}" ng-if="auth.isAuthenticated"><a ui-sref="userResults">My Results</a></li>
|
||||
<li ng-if="auth.isAuthenticated" ng-class="{ active: header.isCatalogActive('user')}" class="dropdown" uib-dropdown>
|
||||
<a role="button" class="dropdown-toggle" uib-dropdown-toggle>
|
||||
My Catalog <strong class="caret"></strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ui-sref="userVendors">My Vendors</a></li>
|
||||
<li><a ui-sref="userProducts">My Products</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li ng-class="{ active: header.isActive('/profile')}" ng-if="auth.isAuthenticated"><a ui-sref="profile">Profile</a></li>
|
||||
<li ng-if="auth.isAuthenticated"><a href="" ng-click="auth.doSignOut()">Sign Out</a></li>
|
||||
<li ng-if="!auth.isAuthenticated"><a href="" ng-click="auth.doSignIn()">Sign In / Sign Up</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* 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('refstackApp')
|
||||
.controller('HeaderController', HeaderController);
|
||||
|
||||
HeaderController.$inject = ['$location'];
|
||||
|
||||
/**
|
||||
* Refstack 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');
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,44 +0,0 @@
|
||||
module.exports = function (config) {
|
||||
'use strict';
|
||||
|
||||
config.set({
|
||||
|
||||
basePath: '../',
|
||||
|
||||
files: [
|
||||
// Angular libraries.
|
||||
'app/assets/lib/angular/angular.js',
|
||||
'app/assets/lib/angular-ui-router/release/angular-ui-router.js',
|
||||
'app/assets/lib/angular-mocks/angular-mocks.js',
|
||||
'app/assets/lib/angular-bootstrap/ui-bootstrap-tpls.min.js',
|
||||
'app/assets/lib/angular-busy/dist/angular-busy.min.js',
|
||||
'app/assets/lib/angular-resource/angular-resource.min.js',
|
||||
'app/assets/lib/angular-confirm-modal/angular-confirm.js',
|
||||
// JS files.
|
||||
'app/app.js',
|
||||
'app/components/**/*.js',
|
||||
'app/shared/*.js',
|
||||
'app/shared/**/*.js',
|
||||
|
||||
// Test Specs.
|
||||
'tests/unit/*.js'
|
||||
],
|
||||
|
||||
autoWatch: true,
|
||||
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
browsers: ['Chrome'],
|
||||
|
||||
plugins: [
|
||||
'karma-chrome-launcher',
|
||||
'karma-jasmine'
|
||||
],
|
||||
|
||||
junitReporter: {
|
||||
outputFile: 'test_out/unit.xml',
|
||||
suite: 'unit'
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
describe('Auth', function () {
|
||||
'use strict';
|
||||
|
||||
var fakeApiUrl = 'http://foo.bar/v1';
|
||||
var $window, $rootScope, $httpBackend;
|
||||
beforeEach(function () {
|
||||
$window = {location: { href: jasmine.createSpy()} };
|
||||
module(function ($provide) {
|
||||
$provide.constant('refstackApiUrl', fakeApiUrl);
|
||||
$provide.value('$window', $window);
|
||||
});
|
||||
module('refstackApp');
|
||||
inject(function (_$httpBackend_, _$rootScope_) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$rootScope = _$rootScope_;
|
||||
});
|
||||
$httpBackend.whenGET('/components/home/home.html')
|
||||
.respond('<div>mock template</div>');
|
||||
});
|
||||
it('should show signin url for signed user', function () {
|
||||
$httpBackend.expectGET(fakeApiUrl +
|
||||
'/profile').respond({'openid': 'foo@bar.com',
|
||||
'email': 'foo@bar.com',
|
||||
'fullname': 'foo' });
|
||||
$httpBackend.flush();
|
||||
$rootScope.auth.doSignIn();
|
||||
expect($window.location.href).toBe(fakeApiUrl + '/auth/signin');
|
||||
expect($rootScope.auth.isAuthenticated).toBe(true);
|
||||
});
|
||||
|
||||
it('should show signout url for not signed user', function () {
|
||||
$httpBackend.expectGET(fakeApiUrl +
|
||||
'/profile').respond(401);
|
||||
$httpBackend.flush();
|
||||
$rootScope.auth.doSignOut();
|
||||
expect($window.location.href).toBe(fakeApiUrl + '/auth/signout');
|
||||
expect($rootScope.auth.isAuthenticated).toBe(false);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
||||
/** Jasmine specs for Refstack filters */
|
||||
describe('Refstack filters', function () {
|
||||
'use strict';
|
||||
|
||||
var fakeApiUrl = 'http://foo.bar/v1';
|
||||
beforeEach(function () {
|
||||
module(function ($provide) {
|
||||
$provide.constant('refstackApiUrl', fakeApiUrl);
|
||||
});
|
||||
module('refstackApp');
|
||||
});
|
||||
|
||||
describe('Filter: arrayConverter', function () {
|
||||
var $filter;
|
||||
beforeEach(inject(function (_$filter_) {
|
||||
$filter = _$filter_('arrayConverter');
|
||||
}));
|
||||
|
||||
it('should convert dict to array of dict values', function () {
|
||||
var object = {'id1': {'key1': 'value1'}, 'id2': {'key2': 'value2'}};
|
||||
var expected = [{'key1': 'value1', 'id': 'id1'},
|
||||
{'key2': 'value2', 'id': 'id2'}];
|
||||
expect($filter(object)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Filter: capitalize', function() {
|
||||
var $filter;
|
||||
beforeEach(inject(function(_$filter_) {
|
||||
$filter = _$filter_('capitalize');
|
||||
}));
|
||||
|
||||
it('should capitalize the first letter', function () {
|
||||
var string1 = 'somestring';
|
||||
var string2 = 'someString';
|
||||
var string3 = 'SOMESTRING';
|
||||
expect($filter(string1)).toEqual('Somestring');
|
||||
expect($filter(string2)).toEqual('SomeString');
|
||||
expect($filter(string3)).toEqual(string3);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
"""Refstack package."""
|
||||
@@ -1,15 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
"""Refstack API package."""
|
||||
@@ -1,268 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""App factory."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from beaker.middleware import SessionMiddleware
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import pecan
|
||||
import webob
|
||||
|
||||
from refstack.api import constants as const
|
||||
from refstack.api import exceptions as api_exc
|
||||
from refstack.api import utils as api_utils
|
||||
from refstack import db
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
PROJECT_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
os.pardir)
|
||||
UI_OPTS = [
|
||||
cfg.StrOpt('ui_url',
|
||||
default='https://refstack.openstack.org',
|
||||
help='Url of user interface for RefStack. Need for redirects '
|
||||
'after sign in and sign out.'
|
||||
),
|
||||
]
|
||||
|
||||
API_OPTS = [
|
||||
cfg.StrOpt('api_url',
|
||||
default='https://refstack.openstack.org/api',
|
||||
help='Url of public RefStack API.'
|
||||
),
|
||||
cfg.StrOpt('static_root',
|
||||
default='refstack-ui/app',
|
||||
help='The directory where your static files can be found. '
|
||||
'Pecan comes with middleware that can be used to serve '
|
||||
'static files (like CSS and Javascript files) during '
|
||||
'development. Here, a special variable %(project_root)s '
|
||||
'can be used to point to the root directory of the '
|
||||
'Refstack project\'s module, so paths can be specified '
|
||||
'relative to that.'
|
||||
),
|
||||
cfg.StrOpt('template_path',
|
||||
default='refstack-ui/app',
|
||||
help='Points to the directory where your template files live. '
|
||||
'Here, a special variable %(project_root)s can be used to '
|
||||
'point to the root directory of the Refstack project\'s '
|
||||
'main module, so paths can be specified relative to that.'
|
||||
),
|
||||
cfg.ListOpt('allowed_cors_origins',
|
||||
default=[],
|
||||
help='List of sites allowed cross-site resource access. If '
|
||||
'this is empty, only same-origin requests are allowed.'
|
||||
),
|
||||
cfg.BoolOpt('app_dev_mode',
|
||||
default=False,
|
||||
help='Switch Refstack app into debug mode. Helpful for '
|
||||
'development. In debug mode static file will be served '
|
||||
'by pecan application. Also, server responses will '
|
||||
'contain some details with debug information.'
|
||||
),
|
||||
cfg.StrOpt('test_results_url',
|
||||
default='/#/results/%s',
|
||||
help='Template for test result url.'
|
||||
),
|
||||
cfg.StrOpt('opendev_api_capabilities_url',
|
||||
default='https://opendev.org/api/v1/repos/openinfra/interop/'
|
||||
'contents/guidelines',
|
||||
help='The GitHub API URL of the repository and location of the '
|
||||
'Interop Working Group capability files. This URL is used '
|
||||
'to get a listing of all capability files.'
|
||||
),
|
||||
cfg.StrOpt('additional_capability_urls',
|
||||
default='https://opendev.org/api/v1/repos/openinfra/interop/'
|
||||
'contents/add-ons/guidelines',
|
||||
help=('The GitHub API URL of the repository and location of '
|
||||
'any additional guideline sources which will need to '
|
||||
'be parsed by the refstack API.')),
|
||||
cfg.StrOpt('opendev_raw_base_url',
|
||||
default='https://opendev.org/api/v1/repos/openinfra/interop/'
|
||||
'raw/',
|
||||
help='This is the base URL that is used for retrieving '
|
||||
'specific capability files. Capability file names will '
|
||||
'be appended to this URL to get the contents of that file.'
|
||||
),
|
||||
cfg.BoolOpt('enable_anonymous_upload',
|
||||
default=True,
|
||||
help='Enable or disable anonymous uploads. If set to False, '
|
||||
'all clients will need to authenticate and sign with a '
|
||||
'public/private keypair previously uploaded to their '
|
||||
'user account.'
|
||||
)
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
opt_group = cfg.OptGroup(name='api',
|
||||
title='Options for the Refstack API')
|
||||
|
||||
CONF.register_opts(UI_OPTS)
|
||||
|
||||
CONF.register_group(opt_group)
|
||||
CONF.register_opts(API_OPTS, opt_group)
|
||||
|
||||
log.register_options(CONF)
|
||||
|
||||
|
||||
class JSONErrorHook(pecan.hooks.PecanHook):
|
||||
"""A pecan hook that translates webob HTTP errors into a JSON format."""
|
||||
|
||||
def __init__(self):
|
||||
"""Hook init."""
|
||||
self.debug = CONF.api.app_dev_mode
|
||||
|
||||
def on_error(self, state, exc):
|
||||
"""Request error handler."""
|
||||
if isinstance(exc, webob.exc.HTTPRedirection):
|
||||
return
|
||||
elif isinstance(exc, webob.exc.HTTPError):
|
||||
return webob.Response(
|
||||
body=json.dumps({'code': exc.status_int,
|
||||
'title': exc.title,
|
||||
'detail': exc.detail}),
|
||||
status=exc.status_int,
|
||||
charset='UTF-8',
|
||||
content_type='application/json'
|
||||
)
|
||||
title = None
|
||||
if isinstance(exc, api_exc.ValidationError):
|
||||
status_code = 400
|
||||
elif isinstance(exc, api_exc.ParseInputsError):
|
||||
status_code = 400
|
||||
elif isinstance(exc, db.NotFound):
|
||||
status_code = 404
|
||||
elif isinstance(exc, db.Duplication):
|
||||
status_code = 409
|
||||
else:
|
||||
LOG.exception(exc)
|
||||
status_code = 500
|
||||
title = 'Internal Server Error'
|
||||
|
||||
body = {'title': title or exc.args[0], 'code': status_code}
|
||||
if self.debug:
|
||||
body['detail'] = str(exc)
|
||||
return webob.Response(
|
||||
body=json.dumps(body),
|
||||
status=status_code,
|
||||
charset='UTF-8',
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
||||
class WritableLogger(object):
|
||||
"""A thin wrapper that responds to `write` and logs."""
|
||||
|
||||
def __init__(self, logger, level):
|
||||
"""Init the WritableLogger by getting logger and log level."""
|
||||
self.logger = logger
|
||||
self.level = level
|
||||
|
||||
def write(self, msg):
|
||||
"""Invoke logger with log level and message."""
|
||||
self.logger.log(self.level, msg.rstrip())
|
||||
|
||||
|
||||
class CORSHook(pecan.hooks.PecanHook):
|
||||
"""A pecan hook that handles Cross-Origin Resource Sharing."""
|
||||
|
||||
def __init__(self):
|
||||
"""Init the hook by getting the allowed origins."""
|
||||
self.allowed_origins = getattr(CONF.api, 'allowed_cors_origins', [])
|
||||
|
||||
def after(self, state):
|
||||
"""Add CORS headers to the response.
|
||||
|
||||
If the request's origin is in the list of allowed origins, add the
|
||||
CORS headers to the response.
|
||||
"""
|
||||
origin = state.request.headers.get('Origin', None)
|
||||
if origin in self.allowed_origins:
|
||||
state.response.headers['Access-Control-Allow-Origin'] = origin
|
||||
state.response.headers['Access-Control-Allow-Methods'] = \
|
||||
'GET, OPTIONS, PUT, POST'
|
||||
state.response.headers['Access-Control-Allow-Headers'] = \
|
||||
'origin, authorization, accept, content-type'
|
||||
state.response.headers['Access-Control-Allow-Credentials'] = 'true'
|
||||
|
||||
|
||||
class JWTAuthHook(pecan.hooks.PecanHook):
|
||||
"""A pecan hook that handles authentication with JSON Web Tokens."""
|
||||
|
||||
def on_route(self, state):
|
||||
"""Check signature in request headers."""
|
||||
token = api_utils.decode_token(state.request)
|
||||
if token:
|
||||
state.request.environ[const.JWT_TOKEN_ENV] = token
|
||||
|
||||
|
||||
def setup_app(config):
|
||||
"""App factory."""
|
||||
# By default we expect path to oslo config file in environment variable
|
||||
# REFSTACK_OSLO_CONFIG (option for testing and development)
|
||||
# If it is empty we look up those config files
|
||||
# in the following directories:
|
||||
# ~/.${project}/
|
||||
# ~/
|
||||
# /etc/${project}/
|
||||
# /etc/
|
||||
|
||||
default_config_files = ((os.getenv('REFSTACK_OSLO_CONFIG'), )
|
||||
if os.getenv('REFSTACK_OSLO_CONFIG')
|
||||
else cfg.find_config_files('refstack'))
|
||||
CONF('',
|
||||
project='refstack',
|
||||
default_config_files=default_config_files)
|
||||
|
||||
log.setup(CONF, 'refstack')
|
||||
CONF.log_opt_values(LOG, logging.DEBUG)
|
||||
|
||||
template_path = CONF.api.template_path % {'project_root': PROJECT_ROOT}
|
||||
static_root = CONF.api.static_root % {'project_root': PROJECT_ROOT}
|
||||
app_conf = dict(config.app)
|
||||
app = pecan.make_app(
|
||||
app_conf.pop('root'),
|
||||
debug=CONF.api.app_dev_mode,
|
||||
static_root=static_root,
|
||||
template_path=template_path,
|
||||
hooks=[
|
||||
JWTAuthHook(), JSONErrorHook(), CORSHook(),
|
||||
pecan.hooks.RequestViewerHook(
|
||||
{'items': ['status', 'method', 'controller', 'path', 'body']},
|
||||
headers=False, writer=WritableLogger(LOG, logging.DEBUG)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
beaker_conf = {
|
||||
'session.key': 'refstack',
|
||||
'session.type': 'ext:database',
|
||||
'session.url': CONF.database.connection,
|
||||
'session.timeout': 604800,
|
||||
'session.validate_key': api_utils.get_token(),
|
||||
'session.sa.pool_recycle': 600
|
||||
}
|
||||
app = SessionMiddleware(app, beaker_conf)
|
||||
|
||||
if CONF.api.app_dev_mode:
|
||||
LOG.debug('\n\n <<< Refstack UI is available at %s >>>\n\n',
|
||||
CONF.ui_url)
|
||||
|
||||
return app
|
||||
@@ -1,19 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from refstack.api import app
|
||||
from refstack.api import config as api_config
|
||||
|
||||
application = app.setup_app(api_config)
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Configuration for running API.
|
||||
|
||||
Custom Configurations must be in Python dictionary format:
|
||||
|
||||
foo = {'bar':'baz'}
|
||||
|
||||
All configurations are accessible at:
|
||||
pecan.conf
|
||||
"""
|
||||
|
||||
# Server Specific Configurations
|
||||
server = {
|
||||
'port': '8000',
|
||||
'host': '0.0.0.0',
|
||||
'protocol': 'http'
|
||||
}
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root': 'refstack.api.controllers.root.RootController',
|
||||
'modules': ['refstack.api'],
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
"""Constants for Refstack API."""
|
||||
|
||||
# Names of input parameters for request
|
||||
START_DATE = 'start_date'
|
||||
END_DATE = 'end_date'
|
||||
CPID = 'cpid'
|
||||
PAGE = 'page'
|
||||
SIGNED = 'signed'
|
||||
VERIFICATION_STATUS = 'verification_status'
|
||||
PRODUCT_ID = 'product_id'
|
||||
ALL_PRODUCT_TESTS = 'all_product_tests'
|
||||
OPENID = 'openid'
|
||||
USER_PUBKEYS = 'pubkeys'
|
||||
|
||||
# Guidelines tests requests parameters
|
||||
ALIAS = 'alias'
|
||||
FLAG = 'flag'
|
||||
TYPE = 'type'
|
||||
TARGET = 'target'
|
||||
|
||||
# OpenID parameters
|
||||
OPENID_MODE = 'openid.mode'
|
||||
OPENID_NS = 'openid.ns'
|
||||
OPENID_RETURN_TO = 'openid.return_to'
|
||||
OPENID_CLAIMED_ID = 'openid.claimed_id'
|
||||
OPENID_IDENTITY = 'openid.identity'
|
||||
OPENID_REALM = 'openid.realm'
|
||||
OPENID_NS_SREG = 'openid.ns.sreg'
|
||||
OPENID_NS_SREG_REQUIRED = 'openid.sreg.required'
|
||||
OPENID_NS_SREG_EMAIL = 'openid.sreg.email'
|
||||
OPENID_NS_SREG_FULLNAME = 'openid.sreg.fullname'
|
||||
OPENID_ERROR = 'openid.error'
|
||||
|
||||
# User session parameters
|
||||
CSRF_TOKEN = 'csrf_token'
|
||||
USER_OPENID = 'user_openid'
|
||||
|
||||
# Test metadata fields
|
||||
USER = 'user'
|
||||
SHARED_TEST_RUN = 'shared'
|
||||
|
||||
# Test verification statuses
|
||||
TEST_NOT_VERIFIED = 0
|
||||
TEST_VERIFIED = 1
|
||||
|
||||
# Roles
|
||||
ROLE_USER = 'user'
|
||||
ROLE_OWNER = 'owner'
|
||||
ROLE_FOUNDATION = 'foundation'
|
||||
|
||||
# Organization types.
|
||||
# OpenStack Foundation
|
||||
FOUNDATION = 0
|
||||
# User's private unofficial Vendor (allows creation and testing
|
||||
# of user's products)
|
||||
PRIVATE_VENDOR = 1
|
||||
# Vendor applied and waiting for official status.
|
||||
PENDING_VENDOR = 2
|
||||
# Official Vendor approved by the Foundation.
|
||||
OFFICIAL_VENDOR = 3
|
||||
|
||||
# Product object types.
|
||||
CLOUD = 0
|
||||
SOFTWARE = 1
|
||||
|
||||
# Product specific types.
|
||||
DISTRO = 0
|
||||
PUBLIC_CLOUD = 1
|
||||
HOSTED_PRIVATE_CLOUD = 2
|
||||
|
||||
JWT_TOKEN_HEADER = 'Authorization'
|
||||
JWT_TOKEN_ENV = 'jwt.token'
|
||||
JWT_VALIDATION_LEEWAY = 42
|
||||
@@ -1,36 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""API controllers package."""
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from refstack.api import constants as const
|
||||
|
||||
CTRLS_OPTS = [
|
||||
cfg.IntOpt('results_per_page',
|
||||
default=20,
|
||||
help='Number of results for one page'),
|
||||
cfg.StrOpt('input_date_format',
|
||||
default='%Y-%m-%d %H:%M:%S',
|
||||
help='The format for %(start)s and %(end)s parameters' % {
|
||||
'start': const.START_DATE,
|
||||
'end': const.END_DATE
|
||||
})
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
CONF.register_opts(CTRLS_OPTS, group='api')
|
||||
@@ -1,187 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Authentication controller."""
|
||||
|
||||
from urllib import parse
|
||||
|
||||
from oslo_config import cfg
|
||||
import pecan
|
||||
from pecan import rest
|
||||
|
||||
from refstack.api import constants as const
|
||||
from refstack.api import utils as api_utils
|
||||
from refstack import db
|
||||
|
||||
|
||||
OPENID_OPTS = [
|
||||
cfg.StrOpt('openstack_openid_endpoint',
|
||||
default='https://openstackid.org/accounts/openid2',
|
||||
help='OpenStackID Auth Server URI.'
|
||||
),
|
||||
cfg.StrOpt('openid_logout_endpoint',
|
||||
default='https://openstackid.org/accounts/user/logout',
|
||||
help='OpenStackID logout URI.'
|
||||
),
|
||||
cfg.StrOpt('openid_mode',
|
||||
default='checkid_setup',
|
||||
help='Interaction mode. Specifies whether Openstack Id '
|
||||
'IdP may interact with the user to determine the '
|
||||
'outcome of the request.'
|
||||
),
|
||||
cfg.StrOpt('openid_ns',
|
||||
default='http://specs.openid.net/auth/2.0',
|
||||
help='Protocol version. Value identifying the OpenID '
|
||||
'protocol version being used. This value should '
|
||||
'be "http://specs.openid.net/auth/2.0".'
|
||||
),
|
||||
cfg.StrOpt('openid_return_to',
|
||||
default='/v1/auth/signin_return',
|
||||
help='Return endpoint in Refstack\'s API. Value indicating '
|
||||
'the endpoint where the user should be returned to after '
|
||||
'signing in. Openstack Id Idp only supports HTTPS '
|
||||
'address types.'
|
||||
),
|
||||
cfg.StrOpt('openid_claimed_id',
|
||||
default='http://specs.openid.net/auth/2.0/identifier_select',
|
||||
help='Claimed identifier. This value must be set to '
|
||||
'"http://specs.openid.net/auth/2.0/identifier_select". '
|
||||
'or to user claimed identity (user local identifier '
|
||||
'or user owned identity [ex: custom html hosted on a '
|
||||
'owned domain set to html discover]).'
|
||||
),
|
||||
cfg.StrOpt('openid_identity',
|
||||
default='http://specs.openid.net/auth/2.0/identifier_select',
|
||||
help='Alternate identifier. This value must be set to '
|
||||
'http://specs.openid.net/auth/2.0/identifier_select.'
|
||||
),
|
||||
cfg.StrOpt('openid_ns_sreg',
|
||||
default='http://openid.net/extensions/sreg/1.1',
|
||||
help='Indicates request for user attribute information. '
|
||||
'This value must be set to '
|
||||
'"http://openid.net/extensions/sreg/1.1".'
|
||||
),
|
||||
cfg.StrOpt('openid_sreg_required',
|
||||
default='email,fullname',
|
||||
help='Comma-separated list of field names which, '
|
||||
'if absent from the response, will prevent the '
|
||||
'Consumer from completing the registration without '
|
||||
'End User interation. The field names are those that '
|
||||
'are specified in the Response Format, with the '
|
||||
'"openid.sreg." prefix removed. Valid values include: '
|
||||
'"country", "email", "firstname", "language", "lastname"'
|
||||
)
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
opt_group = cfg.OptGroup(name='osid',
|
||||
title='Options for the Refstack OpenID 2.0 through '
|
||||
'Openstack Authentication Server')
|
||||
CONF.register_group(opt_group)
|
||||
CONF.register_opts(OPENID_OPTS, opt_group)
|
||||
|
||||
|
||||
class AuthController(rest.RestController):
|
||||
"""Controller provides user authentication in OpenID 2.0 IdP."""
|
||||
|
||||
_custom_actions = {
|
||||
"signin": ["GET"],
|
||||
"signin_return": ["GET"],
|
||||
"signout": ["GET"]
|
||||
}
|
||||
|
||||
def _auth_failure(self, message):
|
||||
params = {
|
||||
'message': message
|
||||
}
|
||||
url = parse.urljoin(CONF.ui_url,
|
||||
'/#/auth_failure?' + parse.urlencode(params))
|
||||
pecan.redirect(url)
|
||||
|
||||
@pecan.expose()
|
||||
def signin(self):
|
||||
"""Handle signin request."""
|
||||
session = api_utils.get_user_session()
|
||||
if api_utils.is_authenticated():
|
||||
pecan.redirect(CONF.ui_url)
|
||||
else:
|
||||
api_utils.delete_params_from_user_session([const.USER_OPENID])
|
||||
|
||||
csrf_token = api_utils.get_token()
|
||||
session[const.CSRF_TOKEN] = csrf_token
|
||||
session.save()
|
||||
return_endpoint = parse.urljoin(CONF.api.api_url,
|
||||
CONF.osid.openid_return_to)
|
||||
return_to = api_utils.set_query_params(return_endpoint,
|
||||
{const.CSRF_TOKEN: csrf_token})
|
||||
|
||||
params = {
|
||||
const.OPENID_MODE: CONF.osid.openid_mode,
|
||||
const.OPENID_NS: CONF.osid.openid_ns,
|
||||
const.OPENID_RETURN_TO: return_to,
|
||||
const.OPENID_CLAIMED_ID: CONF.osid.openid_claimed_id,
|
||||
const.OPENID_IDENTITY: CONF.osid.openid_identity,
|
||||
const.OPENID_REALM: CONF.api.api_url,
|
||||
const.OPENID_NS_SREG: CONF.osid.openid_ns_sreg,
|
||||
const.OPENID_NS_SREG_REQUIRED: CONF.osid.openid_sreg_required,
|
||||
}
|
||||
url = CONF.osid.openstack_openid_endpoint
|
||||
url = api_utils.set_query_params(url, params)
|
||||
pecan.redirect(location=url)
|
||||
|
||||
@pecan.expose()
|
||||
def signin_return(self):
|
||||
"""Handle returned request from OpenID 2.0 IdP."""
|
||||
session = api_utils.get_user_session()
|
||||
if pecan.request.GET.get(const.OPENID_ERROR):
|
||||
api_utils.delete_params_from_user_session([const.CSRF_TOKEN])
|
||||
self._auth_failure(pecan.request.GET.get(const.OPENID_ERROR))
|
||||
|
||||
if pecan.request.GET.get(const.OPENID_MODE) == 'cancel':
|
||||
api_utils.delete_params_from_user_session([const.CSRF_TOKEN])
|
||||
self._auth_failure('Authentication canceled.')
|
||||
|
||||
session_token = session.get(const.CSRF_TOKEN)
|
||||
request_token = pecan.request.GET.get(const.CSRF_TOKEN)
|
||||
if request_token != session_token:
|
||||
api_utils.delete_params_from_user_session([const.CSRF_TOKEN])
|
||||
self._auth_failure('Authentication failed. Please try again.')
|
||||
|
||||
api_utils.verify_openid_request(pecan.request)
|
||||
user_info = {
|
||||
'openid': pecan.request.GET.get(const.OPENID_CLAIMED_ID),
|
||||
'email': pecan.request.GET.get(const.OPENID_NS_SREG_EMAIL),
|
||||
'fullname': pecan.request.GET.get(const.OPENID_NS_SREG_FULLNAME)
|
||||
}
|
||||
user = db.user_save(user_info)
|
||||
|
||||
api_utils.delete_params_from_user_session([const.CSRF_TOKEN])
|
||||
session[const.USER_OPENID] = user.openid
|
||||
session.save()
|
||||
|
||||
pecan.redirect(CONF.ui_url)
|
||||
|
||||
@pecan.expose('json')
|
||||
def signout(self):
|
||||
"""Handle signout request."""
|
||||
if api_utils.is_authenticated():
|
||||
api_utils.delete_params_from_user_session([const.USER_OPENID])
|
||||
|
||||
params = {
|
||||
'openid_logout': CONF.osid.openid_logout_endpoint
|
||||
}
|
||||
url = parse.urljoin(CONF.ui_url,
|
||||
'/#/logout?' + parse.urlencode(params))
|
||||
pecan.redirect(url)
|
||||
@@ -1,99 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Interop WG guidelines controller."""
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
|
||||
from refstack.api import constants as const
|
||||
from refstack.api import guidelines
|
||||
from refstack.api import utils as api_utils
|
||||
|
||||
|
||||
class TestsController(rest.RestController):
|
||||
"""v1/guidelines/<version>/tests handler.
|
||||
|
||||
This will allow users to retrieve specific test lists from specific
|
||||
guidelines for use with refstack-client.
|
||||
"""
|
||||
|
||||
@pecan.expose(content_type='text/plain')
|
||||
def get(self, version):
|
||||
"""Get the plain-text test list of the specified guideline version."""
|
||||
# Remove the .json from version if it is there.
|
||||
version.replace('.json', '')
|
||||
g = guidelines.Guidelines()
|
||||
json = g.get_guideline_contents(version)
|
||||
|
||||
if not json:
|
||||
return 'Error getting JSON content for version: ' + version
|
||||
|
||||
if pecan.request.GET.get(const.TYPE):
|
||||
types = pecan.request.GET.get(const.TYPE).split(',')
|
||||
else:
|
||||
types = None
|
||||
|
||||
if pecan.request.GET.get('alias'):
|
||||
alias = api_utils.str_to_bool(pecan.request.GET.get('alias'))
|
||||
else:
|
||||
alias = True
|
||||
|
||||
if pecan.request.GET.get('flag'):
|
||||
flag = api_utils.str_to_bool(pecan.request.GET.get('flag'))
|
||||
else:
|
||||
flag = True
|
||||
|
||||
target = pecan.request.GET.get('target', 'platform')
|
||||
try:
|
||||
target_caps = g.get_target_capabilities(json, types, target)
|
||||
test_list = g.get_test_list(json, target_caps, alias, flag)
|
||||
except KeyError:
|
||||
return 'Invalid target: ' + target
|
||||
|
||||
return '\n'.join(test_list)
|
||||
|
||||
|
||||
class GuidelinesController(rest.RestController):
|
||||
"""/v1/guidelines handler.
|
||||
|
||||
This acts as a proxy for retrieving guideline files
|
||||
from the openstack/interop Github repository.
|
||||
"""
|
||||
|
||||
tests = TestsController()
|
||||
|
||||
@pecan.expose('json')
|
||||
def get(self):
|
||||
"""Get a list of all available guidelines."""
|
||||
g = guidelines.Guidelines()
|
||||
version_list = g.get_guideline_list()
|
||||
if version_list is None:
|
||||
pecan.abort(500, 'The server was unable to get a list of '
|
||||
'guidelines from the external source.')
|
||||
else:
|
||||
return version_list
|
||||
|
||||
@pecan.expose('json')
|
||||
def get_one(self, file_name):
|
||||
"""Handler for getting contents of specific guideline file."""
|
||||
g = guidelines.Guidelines()
|
||||
json = g.get_guideline_contents(file_name)
|
||||
if json:
|
||||
return json
|
||||
else:
|
||||
pecan.abort(500, 'The server was unable to get the JSON '
|
||||
'content for the specified guideline file.')
|
||||
@@ -1,292 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Product controller."""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from oslo_db.exception import DBReferenceError
|
||||
from oslo_log import log
|
||||
import pecan
|
||||
from pecan.secure import secure
|
||||
|
||||
from refstack.api import constants as const
|
||||
from refstack.api.controllers import validation
|
||||
from refstack.api import utils as api_utils
|
||||
from refstack.api import validators
|
||||
from refstack import db
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class VersionsController(validation.BaseRestControllerWithValidation):
|
||||
"""/v1/products/<product_id>/versions handler."""
|
||||
|
||||
__validator__ = validators.ProductVersionValidator
|
||||
|
||||
@pecan.expose('json')
|
||||
def get(self, id):
|
||||
"""Get all versions for a product."""
|
||||
product = db.get_product(id)
|
||||
vendor_id = product['organization_id']
|
||||
is_admin = (api_utils.check_user_is_foundation_admin() or
|
||||
api_utils.check_user_is_vendor_admin(vendor_id))
|
||||
if not product['public'] and not is_admin:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
allowed_keys = ['id', 'product_id', 'version', 'cpid']
|
||||
return db.get_product_versions(id, allowed_keys=allowed_keys)
|
||||
|
||||
@pecan.expose('json')
|
||||
def get_one(self, id, version_id):
|
||||
"""Get specific version information."""
|
||||
product = db.get_product(id)
|
||||
vendor_id = product['organization_id']
|
||||
is_admin = (api_utils.check_user_is_foundation_admin() or
|
||||
api_utils.check_user_is_vendor_admin(vendor_id))
|
||||
if not product['public'] and not is_admin:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
allowed_keys = ['id', 'product_id', 'version', 'cpid']
|
||||
return db.get_product_version(version_id, allowed_keys=allowed_keys)
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json')
|
||||
def post(self, id):
|
||||
"""'secure' decorator doesn't work at store_item. it must be here."""
|
||||
self.product_id = id
|
||||
return super(VersionsController, self).post()
|
||||
|
||||
@pecan.expose('json')
|
||||
def store_item(self, version_info):
|
||||
"""Add a new version for the product."""
|
||||
if (not api_utils.check_user_is_product_admin(self.product_id) and
|
||||
not api_utils.check_user_is_foundation_admin()):
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
creator = api_utils.get_user_id()
|
||||
pecan.response.status = 201
|
||||
allowed_keys = ['id', 'product_id', 'version', 'cpid']
|
||||
return db.add_product_version(self.product_id, version_info['version'],
|
||||
creator, version_info.get('cpid'),
|
||||
allowed_keys)
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json', method='PUT')
|
||||
def put(self, id, version_id, **kw):
|
||||
"""Update details for a specific version.
|
||||
|
||||
Endpoint: /v1/products/<product_id>/versions/<version_id>
|
||||
"""
|
||||
if (not api_utils.check_user_is_product_admin(id) and
|
||||
not api_utils.check_user_is_foundation_admin()):
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
version_info = {'id': version_id}
|
||||
if 'cpid' in kw:
|
||||
version_info['cpid'] = kw['cpid']
|
||||
version = db.update_product_version(version_info)
|
||||
pecan.response.status = 200
|
||||
return version
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json')
|
||||
def delete(self, id, version_id):
|
||||
"""Delete a product version.
|
||||
|
||||
Endpoint: /v1/products/<product_id>/versions/<version_id>
|
||||
"""
|
||||
if (not api_utils.check_user_is_product_admin(id) and
|
||||
not api_utils.check_user_is_foundation_admin()):
|
||||
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
try:
|
||||
version = db.get_product_version(version_id,
|
||||
allowed_keys=['version'])
|
||||
if not version['version']:
|
||||
pecan.abort(400, 'Can not delete the empty version as it is '
|
||||
'used for basic product/test association. '
|
||||
'This version was implicitly created with '
|
||||
'the product, and so it cannot be deleted '
|
||||
'explicitly.')
|
||||
|
||||
db.delete_product_version(version_id)
|
||||
except DBReferenceError:
|
||||
pecan.abort(400, 'Unable to delete. There are still tests '
|
||||
'associated to this product version.')
|
||||
pecan.response.status = 204
|
||||
|
||||
|
||||
class ProductsController(validation.BaseRestControllerWithValidation):
|
||||
"""/v1/products handler."""
|
||||
|
||||
__validator__ = validators.ProductValidator
|
||||
|
||||
_custom_actions = {
|
||||
"action": ["POST"],
|
||||
}
|
||||
|
||||
versions = VersionsController()
|
||||
|
||||
@pecan.expose('json')
|
||||
def get(self):
|
||||
"""Get information of all products."""
|
||||
filters = api_utils.parse_input_params(['organization_id'])
|
||||
|
||||
allowed_keys = ['id', 'name', 'description', 'product_ref_id', 'type',
|
||||
'product_type', 'public', 'organization_id']
|
||||
user = api_utils.get_user_id()
|
||||
is_admin = user in db.get_foundation_users()
|
||||
try:
|
||||
if is_admin:
|
||||
products = db.get_products(allowed_keys=allowed_keys,
|
||||
filters=filters)
|
||||
for s in products:
|
||||
s['can_manage'] = True
|
||||
else:
|
||||
result = dict()
|
||||
filters['public'] = True
|
||||
|
||||
products = db.get_products(allowed_keys=allowed_keys,
|
||||
filters=filters)
|
||||
for s in products:
|
||||
_id = s['id']
|
||||
result[_id] = s
|
||||
result[_id]['can_manage'] = False
|
||||
|
||||
filters.pop('public')
|
||||
products = db.get_products_by_user(user,
|
||||
allowed_keys=allowed_keys,
|
||||
filters=filters)
|
||||
for s in products:
|
||||
_id = s['id']
|
||||
if _id not in result:
|
||||
result[_id] = s
|
||||
result[_id]['can_manage'] = True
|
||||
products = list(result.values())
|
||||
except Exception as ex:
|
||||
LOG.exception('An error occurred during '
|
||||
'operation with database: %s', ex)
|
||||
pecan.abort(400)
|
||||
|
||||
products.sort(key=lambda x: x['name'])
|
||||
return {'products': products}
|
||||
|
||||
@pecan.expose('json')
|
||||
def get_one(self, id):
|
||||
"""Get information about product."""
|
||||
allowed_keys = ['id', 'name', 'description',
|
||||
'product_ref_id', 'product_type',
|
||||
'public', 'properties', 'created_at', 'updated_at',
|
||||
'organization_id', 'created_by_user', 'type']
|
||||
product = db.get_product(id, allowed_keys=allowed_keys)
|
||||
vendor_id = product['organization_id']
|
||||
is_admin = (api_utils.check_user_is_foundation_admin() or
|
||||
api_utils.check_user_is_vendor_admin(vendor_id))
|
||||
if not is_admin and not product['public']:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
if not is_admin:
|
||||
admin_only_keys = ['created_by_user', 'created_at', 'updated_at',
|
||||
'properties']
|
||||
for key in list(product):
|
||||
if key in admin_only_keys:
|
||||
product.pop(key)
|
||||
|
||||
product['can_manage'] = is_admin
|
||||
return product
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json')
|
||||
def post(self):
|
||||
"""'secure' decorator doesn't work at store_item. it must be here."""
|
||||
return super(ProductsController, self).post()
|
||||
|
||||
@pecan.expose('json')
|
||||
def store_item(self, product):
|
||||
"""Handler for storing item. Should return new item id."""
|
||||
creator = api_utils.get_user_id()
|
||||
product['type'] = (const.SOFTWARE
|
||||
if product['product_type'] == const.DISTRO
|
||||
else const.CLOUD)
|
||||
if product['type'] == const.SOFTWARE:
|
||||
product['product_ref_id'] = str(uuid.uuid4())
|
||||
vendor_id = product.pop('organization_id', None)
|
||||
if not vendor_id:
|
||||
# find or create default vendor for new product
|
||||
# TODO(andrey-mp): maybe just fill with info here and create
|
||||
# at DB layer in one transaction
|
||||
default_vendor_name = 'vendor_' + creator
|
||||
vendors = db.get_organizations_by_user(creator)
|
||||
for v in vendors:
|
||||
if v['name'] == default_vendor_name:
|
||||
vendor_id = v['id']
|
||||
break
|
||||
else:
|
||||
vendor = {'name': default_vendor_name}
|
||||
vendor = db.add_organization(vendor, creator)
|
||||
vendor_id = vendor['id']
|
||||
product['organization_id'] = vendor_id
|
||||
product = db.add_product(product, creator)
|
||||
return {'id': product['id']}
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json', method='PUT')
|
||||
def put(self, id, **kw):
|
||||
"""Handler for update item. Should return full info with updates."""
|
||||
product = db.get_product(id)
|
||||
vendor_id = product['organization_id']
|
||||
vendor = db.get_organization(vendor_id)
|
||||
is_admin = (api_utils.check_user_is_foundation_admin() or
|
||||
api_utils.check_user_is_vendor_admin(vendor_id))
|
||||
if not is_admin:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
product_info = {'id': id}
|
||||
if 'name' in kw:
|
||||
product_info['name'] = kw['name']
|
||||
if 'description' in kw:
|
||||
product_info['description'] = kw['description']
|
||||
if 'product_ref_id' in kw:
|
||||
product_info['product_ref_id'] = kw['product_ref_id']
|
||||
if 'public' in kw:
|
||||
# user can mark product as public only if
|
||||
# his/her vendor is public(official)
|
||||
public = api_utils.str_to_bool(kw['public'])
|
||||
if (vendor['type'] not in
|
||||
(const.OFFICIAL_VENDOR, const.FOUNDATION) and public):
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
product_info['public'] = public
|
||||
if 'properties' in kw:
|
||||
product_info['properties'] = json.dumps(kw['properties'])
|
||||
db.update_product(product_info)
|
||||
|
||||
pecan.response.status = 200
|
||||
product = db.get_product(id)
|
||||
product['can_manage'] = True
|
||||
return product
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json')
|
||||
def delete(self, id):
|
||||
"""Delete product."""
|
||||
if (not api_utils.check_user_is_foundation_admin() and
|
||||
not api_utils.check_user_is_product_admin(id)):
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
try:
|
||||
db.delete_product(id)
|
||||
except DBReferenceError:
|
||||
pecan.abort(400, 'Unable to delete. There are still tests '
|
||||
'associated to versions of this product.')
|
||||
pecan.response.status = 204
|
||||
@@ -1,331 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test results controller."""
|
||||
import functools
|
||||
from urllib import parse
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import pecan
|
||||
from pecan import rest
|
||||
|
||||
from refstack.api import constants as const
|
||||
from refstack.api.controllers import validation
|
||||
from refstack.api import utils as api_utils
|
||||
from refstack.api import validators
|
||||
from refstack import db
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class MetadataController(rest.RestController):
|
||||
"""/v1/results/<test_id>/meta handler."""
|
||||
|
||||
rw_access_keys = ('shared', 'guideline', 'target',)
|
||||
|
||||
def _check_key(func):
|
||||
"""Decorator to check that a specific key has write access."""
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
meta_key = args[2]
|
||||
if meta_key not in args[0].rw_access_keys:
|
||||
pecan.abort(403)
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
@pecan.expose('json')
|
||||
def get(self, test_id):
|
||||
"""Get test run metadata."""
|
||||
test_info = db.get_test_result(test_id)
|
||||
role = api_utils.get_user_role(test_id)
|
||||
if role in (const.ROLE_FOUNDATION, const.ROLE_OWNER):
|
||||
return test_info['meta']
|
||||
elif role in (const.ROLE_USER):
|
||||
return {k: v for k, v in test_info['meta'].items()
|
||||
if k in self.rw_access_keys}
|
||||
pecan.abort(403)
|
||||
|
||||
@pecan.expose('json')
|
||||
def get_one(self, test_id, key):
|
||||
"""Get value for key from test run metadata."""
|
||||
role = api_utils.get_user_role(test_id)
|
||||
if role in (const.ROLE_FOUNDATION, const.ROLE_OWNER):
|
||||
return db.get_test_result_meta_key(test_id, key)
|
||||
elif role in (const.ROLE_USER) and key in self.rw_access_keys:
|
||||
return db.get_test_result_meta_key(test_id, key)
|
||||
pecan.abort(403)
|
||||
|
||||
@_check_key
|
||||
@api_utils.check_permissions(level=const.ROLE_OWNER)
|
||||
@pecan.expose('json')
|
||||
def post(self, test_id, key):
|
||||
"""Save value for key in test run metadata."""
|
||||
test = db.get_test_result(test_id)
|
||||
if test['verification_status'] == const.TEST_VERIFIED:
|
||||
pecan.abort(403, 'Can not add/alter a new metadata key for a '
|
||||
'verified test run.')
|
||||
db.save_test_result_meta_item(test_id, key, pecan.request.body)
|
||||
pecan.response.status = 201
|
||||
|
||||
@_check_key
|
||||
@api_utils.check_permissions(level=const.ROLE_OWNER)
|
||||
@pecan.expose('json')
|
||||
def delete(self, test_id, key):
|
||||
"""Delete key from test run metadata."""
|
||||
test = db.get_test_result(test_id)
|
||||
if test['verification_status'] == const.TEST_VERIFIED:
|
||||
pecan.abort(403, 'Can not delete a metadata key for a '
|
||||
'verified test run.')
|
||||
db.delete_test_result_meta_item(test_id, key)
|
||||
pecan.response.status = 204
|
||||
|
||||
|
||||
class ResultsController(validation.BaseRestControllerWithValidation):
|
||||
"""/v1/results handler."""
|
||||
|
||||
__validator__ = validators.TestResultValidator
|
||||
|
||||
meta = MetadataController()
|
||||
|
||||
def _check_authentication(self):
|
||||
x_public_key = pecan.request.headers.get('X-Public-Key')
|
||||
if x_public_key:
|
||||
public_key = x_public_key.strip().split()[1]
|
||||
stored_public_key = db.get_pubkey(public_key)
|
||||
if not stored_public_key:
|
||||
pecan.abort(401, 'User with specified key not found. '
|
||||
'Please log into the RefStack server to '
|
||||
'upload your key.')
|
||||
else:
|
||||
stored_public_key = None
|
||||
|
||||
if not CONF.api.enable_anonymous_upload and not stored_public_key:
|
||||
pecan.abort(401, 'Anonymous result uploads are disabled. '
|
||||
'Please create a user account and an api '
|
||||
'key at https://refstack.openstack.org/#/')
|
||||
|
||||
return stored_public_key
|
||||
|
||||
def _auto_version_associate(self, test, test_, pubkey):
|
||||
if test.get('cpid'):
|
||||
version = db.get_product_version_by_cpid(
|
||||
test['cpid'], allowed_keys=['id', 'product_id'])
|
||||
# Only auto-associate if there is a single product version
|
||||
# with the given cpid.
|
||||
if len(version) == 1:
|
||||
is_foundation = api_utils.check_user_is_foundation_admin(
|
||||
pubkey.openid)
|
||||
is_product_admin = api_utils.check_user_is_product_admin(
|
||||
version[0]['product_id'], pubkey.openid)
|
||||
if is_foundation or is_product_admin:
|
||||
test_['product_version_id'] = version[0]['id']
|
||||
return test_
|
||||
|
||||
@pecan.expose('json')
|
||||
@api_utils.check_permissions(level=const.ROLE_USER)
|
||||
def get_one(self, test_id):
|
||||
"""Handler for getting item."""
|
||||
user_role = api_utils.get_user_role(test_id)
|
||||
if user_role in (const.ROLE_FOUNDATION, const.ROLE_OWNER):
|
||||
test_info = db.get_test_result(
|
||||
test_id, allowed_keys=['id', 'cpid', 'created_at',
|
||||
'duration_seconds', 'meta',
|
||||
'product_version',
|
||||
'verification_status']
|
||||
)
|
||||
else:
|
||||
test_info = db.get_test_result(test_id)
|
||||
test_list = db.get_test_results(test_id)
|
||||
test_name_list = [test_dict['name'] for test_dict in test_list]
|
||||
test_info.update({'results': test_name_list,
|
||||
'user_role': user_role})
|
||||
|
||||
if user_role not in (const.ROLE_FOUNDATION, const.ROLE_OWNER):
|
||||
# Don't expose product information if product is not public.
|
||||
if (test_info.get('product_version') and
|
||||
not test_info['product_version']
|
||||
['product_info']['public']):
|
||||
|
||||
test_info['product_version'] = None
|
||||
|
||||
test_info['meta'] = {
|
||||
k: v for k, v in test_info['meta'].items()
|
||||
if k in MetadataController.rw_access_keys
|
||||
}
|
||||
return test_info
|
||||
|
||||
def store_item(self, test):
|
||||
"""Handler for storing item. Should return new item id."""
|
||||
# If we need a key, or the key isn't available, this will throw
|
||||
# an exception with a 401
|
||||
pubkey = self._check_authentication()
|
||||
test_ = test.copy()
|
||||
if pubkey:
|
||||
if 'meta' not in test_:
|
||||
test_['meta'] = {}
|
||||
test_['meta'][const.USER] = pubkey.openid
|
||||
test_ = self._auto_version_associate(test, test_, pubkey)
|
||||
|
||||
test_id = db.store_test_results(test_)
|
||||
return {'test_id': test_id,
|
||||
'url': parse.urljoin(CONF.ui_url,
|
||||
CONF.api.test_results_url) % test_id}
|
||||
|
||||
@pecan.expose('json')
|
||||
@api_utils.check_permissions(level=const.ROLE_OWNER)
|
||||
def delete(self, test_id):
|
||||
"""Delete test run."""
|
||||
test = db.get_test_result(test_id)
|
||||
if test['verification_status'] == const.TEST_VERIFIED:
|
||||
pecan.abort(403, 'Can not delete a verified test run.')
|
||||
|
||||
db.delete_test_result(test_id)
|
||||
pecan.response.status = 204
|
||||
|
||||
@pecan.expose('json')
|
||||
def get(self):
|
||||
"""Get information of all uploaded test results.
|
||||
|
||||
Get information of all uploaded test results in descending
|
||||
chronological order. Make it possible to specify some
|
||||
input parameters for filtering.
|
||||
For example:
|
||||
/v1/results?page=<page number>&cpid=1234.
|
||||
By default, page is set to page number 1,
|
||||
if the page parameter is not specified.
|
||||
"""
|
||||
expected_input_params = [
|
||||
const.START_DATE,
|
||||
const.END_DATE,
|
||||
const.CPID,
|
||||
const.SIGNED,
|
||||
const.VERIFICATION_STATUS,
|
||||
const.PRODUCT_ID
|
||||
]
|
||||
|
||||
filters = api_utils.parse_input_params(expected_input_params)
|
||||
|
||||
if const.PRODUCT_ID in filters:
|
||||
product = db.get_product(filters[const.PRODUCT_ID])
|
||||
vendor_id = product['organization_id']
|
||||
is_admin = (api_utils.check_user_is_foundation_admin() or
|
||||
api_utils.check_user_is_vendor_admin(vendor_id))
|
||||
if is_admin:
|
||||
filters[const.ALL_PRODUCT_TESTS] = True
|
||||
elif not product['public']:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
records_count = db.get_test_result_records_count(filters)
|
||||
page_number, total_pages_number = \
|
||||
api_utils.get_page_number(records_count)
|
||||
|
||||
try:
|
||||
per_page = CONF.api.results_per_page
|
||||
results = db.get_test_result_records(
|
||||
page_number, per_page, filters)
|
||||
is_foundation = api_utils.check_user_is_foundation_admin()
|
||||
for result in results:
|
||||
|
||||
if not (api_utils.check_owner(result['id']) or is_foundation):
|
||||
|
||||
# Don't expose product info if the product is not public.
|
||||
if (result.get('product_version') and not
|
||||
result['product_version']['product_info']
|
||||
['public']):
|
||||
|
||||
result['product_version'] = None
|
||||
# Only show all metadata if the user is the owner or a
|
||||
# member of the Foundation group.
|
||||
result['meta'] = {
|
||||
k: v for k, v in result['meta'].items()
|
||||
if k in MetadataController.rw_access_keys
|
||||
}
|
||||
result.update({'url': parse.urljoin(
|
||||
CONF.ui_url, CONF.api.test_results_url
|
||||
) % result['id']})
|
||||
|
||||
page = {'results': results,
|
||||
'pagination': {
|
||||
'current_page': page_number,
|
||||
'total_pages': total_pages_number
|
||||
}}
|
||||
except Exception as ex:
|
||||
LOG.debug('An error occurred during '
|
||||
'operation with database: %s', str(ex))
|
||||
pecan.abort(500)
|
||||
|
||||
return page
|
||||
|
||||
@api_utils.check_permissions(level=const.ROLE_OWNER)
|
||||
@pecan.expose('json')
|
||||
def put(self, test_id, **kw):
|
||||
"""Update a test result."""
|
||||
test_info = {'id': test_id}
|
||||
is_foundation_admin = api_utils.check_user_is_foundation_admin()
|
||||
|
||||
if 'product_version_id' in kw:
|
||||
test = db.get_test_result(test_id)
|
||||
if test['verification_status'] == const.TEST_VERIFIED:
|
||||
pecan.abort(403, 'Can not update product_version_id for a '
|
||||
'verified test run.')
|
||||
|
||||
if kw['product_version_id']:
|
||||
# Verify that the user is a member of the product's vendor.
|
||||
version = db.get_product_version(kw['product_version_id'],
|
||||
allowed_keys=['product_id'])
|
||||
is_vendor_admin = (
|
||||
api_utils
|
||||
.check_user_is_product_admin(version['product_id'])
|
||||
)
|
||||
else:
|
||||
# No product vendor to check membership for, so just set
|
||||
# is_vendor_admin to True.
|
||||
is_vendor_admin = True
|
||||
kw['product_version_id'] = None
|
||||
|
||||
if not is_vendor_admin and not is_foundation_admin:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
test_info['product_version_id'] = kw['product_version_id']
|
||||
|
||||
if 'verification_status' in kw:
|
||||
if not is_foundation_admin:
|
||||
pecan.abort(403, 'You do not have permission to change a '
|
||||
'verification status.')
|
||||
|
||||
if kw['verification_status'] not in (0, 1):
|
||||
pecan.abort(400, 'Invalid verification_status value: %d' %
|
||||
kw['verification_status'])
|
||||
|
||||
# Check pre-conditions are met to mark a test verified.
|
||||
if (kw['verification_status'] == 1 and
|
||||
not (db.get_test_result_meta_key(test_id, 'target') and
|
||||
db.get_test_result_meta_key(test_id, 'guideline') and
|
||||
db.get_test_result_meta_key(test_id,
|
||||
const.SHARED_TEST_RUN))):
|
||||
|
||||
pecan.abort(403, 'In order to mark a test verified, the '
|
||||
'test must be shared and have been '
|
||||
'associated to a guideline and target '
|
||||
'program.')
|
||||
|
||||
test_info['verification_status'] = kw['verification_status']
|
||||
|
||||
test = db.update_test_result(test_info)
|
||||
pecan.response.status = 201
|
||||
return test
|
||||
@@ -1,39 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Root controller."""
|
||||
|
||||
from oslo_config import cfg
|
||||
from pecan import expose
|
||||
|
||||
from refstack.api.controllers import v1
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class RootController(object):
|
||||
"""Root handler."""
|
||||
|
||||
v1 = v1.V1Controller()
|
||||
|
||||
if CONF.api.app_dev_mode:
|
||||
@expose(generic=True, template='index.html')
|
||||
def index(self):
|
||||
"""Return index.html in development mode.
|
||||
|
||||
It allows to run both API and UI with pecan serve.
|
||||
Template path should point into UI app folder
|
||||
"""
|
||||
return dict()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user