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 @@
|
|||||||
========
|
This project is no longer maintained.
|
||||||
RefStack
|
|
||||||
========
|
|
||||||
|
|
||||||
What is RefStack?
|
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
|
||||||
- Toolset for testing interoperability between OpenStack clouds.
|
previous commit with "git checkout HEAD^1".
|
||||||
- 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.
|
|
||||||
|
|||||||
@@ -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