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:
Christian Berendt
2025-09-23 17:00:56 +02:00
parent fa704ce067
commit 5a6b29620b
195 changed files with 5 additions and 30265 deletions

View File

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

View File

@@ -1,12 +0,0 @@
*.egg*
*.py[cod]
.coverage
.testrepository/
.tox/
.venv/
AUTHORS
ChangeLog
build/
cover/
dist
.git/

View File

@@ -1 +0,0 @@
refstack-ui/app/assets/lib

View File

@@ -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
View File

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

View File

@@ -1,3 +0,0 @@
[DEFAULT]
test_path=./refstack/tests/unit
top_dir=./

View File

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

View File

@@ -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
View File

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

View File

@@ -1,65 +1,6 @@
========
RefStack
========
This project is no longer maintained.
What is RefStack?
#################
- Toolset for testing interoperability between OpenStack clouds.
- Database backed website supporting collection and publication of
Community Test results for OpenStack.
- User interface to display individual test run results.
RefStack intends on being THE source of tools for interoperability testing
of OpenStack clouds.
RefStack provides users in the OpenStack community with a Tempest wrapper,
refstack-client, that helps to verify interoperability of their cloud
with other OpenStack clouds. It does so by validating any cloud
implementation against the OpenStack Tempest API tests.
Refstack's Use Case
###################
**RefStack and Interop Working Group** - The prototypical use case for RefStack
provides the Interop Working Group - formerly known as DefCore committee - the
tools for vendors and other users to run API tests against their clouds to
provide the WG with a reliable overview of what APIs and capabilities are
being used in the marketplace. This will help to guide the Interop
Working Group defined capabilities and help ensure interoperability across
the entire OpenStack ecosystem. It can also be used to validate clouds
against existing capability lists, giving you assurance that your cloud
faithfully implements OpenStack standards.
**Value add for openstack distro or solution vendors** - Vendors can use
RefStack to demonstrate that their distros, and/or their customers' installed
clouds remain OpenStack compliant after their software has been incorporated
into the distro or cloud.
**RefStack consists of two parts:**
* **refstack-api**
Our API isn't just for us to collect data from private and public cloud
vendors. It can be used by vendors in house to compare interoperability
data over time.
* documentation: https://docs.opendev.org/openinfra/refstack/latest/
* repository: https://opendev.org/openinfra/refstack
* reviews: https://review.opendev.org/#/q/status:open+project:openinfra/refstack
* bugs: https://storyboard.openstack.org/#!/project/openinfra/refstack
* Web-site: https://refstack.openstack.org
* **refstack-client**
refstack-client contains the tools you will need to run the
Interop Working Group tests.
* documentation: https://docs.opendev.org/openinfra/refstack-client/latest/
* repository: https://opendev.org/openinfra/refstack-client
* reviews: https://review.opendev.org/#/q/status:open+project:openinfra/refstack-client
* bugs: https://storyboard.openstack.org/#!/project/openinfra/refstack-client
Get Involved!
#############
See the `CONTRIBUTING <https://docs.opendev.org/openinfra/refstack/latest/contributing.html>`_
guide on how to get involved.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".

View File

@@ -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()

View File

@@ -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()

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
sphinx>=1.6.2
openstackdocstheme>=1.11.0 # Apache-2.0

View File

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

View File

@@ -1,5 +0,0 @@
============
Contributing
============
.. include:: ../../CONTRIBUTING.rst

View File

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

View File

@@ -1,2 +0,0 @@
.. include:: ../../README.rst

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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&param2=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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,85 +0,0 @@
<h3>OpenStack Powered&#8482; 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>

View File

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

View File

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

View File

@@ -1,46 +0,0 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">&times;</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&#8482; 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>
&nbsp;
<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>

View File

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

View File

@@ -1 +0,0 @@
<div cg-busy="{promise:ctrl.redirectWait,message:'Logging you out...'}"></div>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,75 +0,0 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">&times;</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>&nbsp;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>

View File

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

View File

@@ -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>
&nbsp;
<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>

View File

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

View File

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

View File

@@ -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();">&nbsp;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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,70 +0,0 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">&times;</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>

View File

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

View File

@@ -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"> &mdash;
<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"> &mdash;
<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>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,62 +0,0 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">&times;</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>&nbsp;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>

View File

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

View File

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

View File

@@ -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();">&nbsp;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">&nbsp;OpenStack</span>
<span ng-show="vendor.type == 1" class="glyphicon glyphicon-eye-close">&nbsp;Private</span>
<span ng-show="vendor.type == 2" class="glyphicon glyphicon-transfer">&nbsp;Pending Approval</span>
<span ng-show="vendor.type == 3" class="glyphicon glyphicon-check">&nbsp;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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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&#8482; 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.')

View 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

View File

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

View File

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