commit 73c2bcf7af6b45527f2940c57efc1f925d75b7cd Author: Shu Muto Date: Fri Jul 22 13:06:25 2016 +0900 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be2a45a --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +AUTHORS +ChangeLog +build +doc/source/sourcecode +zun_ui/test/.secret_key_store +.venv +.tox +*.pyc +*.lock +*.egg* +*.swp diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..41dff74 --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=review.openstack.org +port= # need to set for zun-ui +project=openstack/zun-ui.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68c771a --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + + 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. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f1cb737 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include setup.py diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..e85af78 --- /dev/null +++ b/README.rst @@ -0,0 +1,59 @@ +========== +zun-ui +========== + +Zun UI + +* Free software: Apache license +* Source: http://git.openstack.org/cgit/openstack/zun-ui +* Bugs: http://bugs.launchpad.net/zun-ui + +Enabling in DevStack +-------------------- + +Add this repo as an external repository into your ``local.conf`` file:: + + [[local|localrc]] + enable_plugin zun-ui https://github.com/openstack/zun-ui + +Manual Installation +------------------- + +Begin by cloning the Horizon and Zun UI repositories:: + + git clone https://github.com/openstack/horizon + git clone https://github.com/openstack/zun-ui + +Create a virtual environment and install Horizon dependencies:: + + cd horizon + python tools/install_venv.py + +Set up your ``local_settings.py`` file:: + + cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py + +Open up the copied ``local_settings.py`` file in your preferred text +editor. You will want to customize several settings: + +- ``OPENSTACK_HOST`` should be configured with the hostname of your + OpenStack server. Verify that the ``OPENSTACK_KEYSTONE_URL`` and + ``OPENSTACK_KEYSTONE_DEFAULT_ROLE`` settings are correct for your + environment. (They should be correct unless you modified your + OpenStack server to change them.) + +Install Zun UI with all dependencies in your virtual environment:: + + tools/with_venv.sh pip install -e ../zun-ui/ + +And enable it in Horizon:: + + cp ../zun-ui/zun_ui/enabled/_1330_project_container_panelgroup.py openstack_dashboard/local/enabled + cp ../zun-ui/zun_ui/enabled/_1331_project_container_containers_panel.py openstack_dashboard/local/enabled + +To run horizon with the newly enabled Zun UI plugin run:: + + ./run_tests.sh --runserver 0.0.0.0:8080 + +to have the application start on port 8080 and the horizon dashboard will be +available in your browser at http://localhost:8080/ diff --git a/babel-django.cfg b/babel-django.cfg new file mode 100644 index 0000000..ad09d34 --- /dev/null +++ b/babel-django.cfg @@ -0,0 +1,5 @@ +[extractors] +django = django_babel.extract:extract_django + +[python: **.py] +[django: templates/**.html] diff --git a/babel-djangojs.cfg b/babel-djangojs.cfg new file mode 100644 index 0000000..a8273b6 --- /dev/null +++ b/babel-djangojs.cfg @@ -0,0 +1,14 @@ +[extractors] +# We use a custom extractor to find translatable strings in AngularJS +# templates. The extractor is included in horizon.utils for now. +# See http://babel.pocoo.org/docs/messages/#referencing-extraction-methods for +# details on how this works. +angular = horizon.utils.babel_extract_angular:extract_angular + +[javascript: **.js] + +# We need to look into all static folders for HTML files. +# The **/static ensures that we also search within +# /openstack_dashboard/dashboards/XYZ/static which will ensure +# that plugins are also translated. +[angular: **/static/**.html] diff --git a/devstack/plugin.sh b/devstack/plugin.sh new file mode 100644 index 0000000..8a0a20a --- /dev/null +++ b/devstack/plugin.sh @@ -0,0 +1,60 @@ +# plugin.sh - DevStack plugin.sh dispatch script zun-ui + +HIGGINS_UI_DIR=$(cd $(dirname $BASH_SOURCE)/.. && pwd) + +function install_zun_ui { + # NOTE(shu-mutou): workaround for devstack bug: 1540328 + # where devstack install 'test-requirements' but should not do it + # for zun-ui project as it installs Horizon from url. + # Remove following two 'mv' commands when mentioned bug is fixed. + mv $HIGGINS_UI_DIR/test-requirements.txt $HIGGINS_UI_DIR/_test-requirements.txt + + setup_develop ${HIGGINS_UI_DIR} + + mv $HIGGINS_UI_DIR/_test-requirements.txt $HIGGINS_UI_DIR/test-requirements.txt +} + +function configure_zun_ui { + cp -a ${HIGGINS_UI_DIR}/enabled/* ${DEST}/horizon/openstack_dashboard/local/enabled/ + # NOTE: If locale directory does not exist, compilemessages will fail, + # so check for an existence of locale directory is required. + if [ -d ${HIGGINS_UI_DIR}/zun_ui/locale ]; then + (cd ${HIGGINS_UI_DIR}/zun_ui; DJANGO_SETTINGS_MODULE=openstack_dashboard.settings ../manage.py compilemessages) + fi +} + +# check for service enabled +if is_service_enabled zun-ui; then + + if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then + # Set up system services + # no-op + : + + elif [[ "$1" == "stack" && "$2" == "install" ]]; then + # Perform installation of service source + echo_summary "Installing Zun UI" + install_zun_ui + + elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then + # Configure after the other layer 1 and 2 services have been configured + echo_summary "Configurng Zun UI" + configure_zun_ui + + elif [[ "$1" == "stack" && "$2" == "extra" ]]; then + # no-op + : + fi + + if [[ "$1" == "unstack" ]]; then + # no-op + : + fi + + if [[ "$1" == "clean" ]]; then + # Remove state and transient data + # Remember clean.sh first calls unstack.sh + # no-op + : + fi +fi diff --git a/devstack/settings b/devstack/settings new file mode 100644 index 0000000..9d1facc --- /dev/null +++ b/devstack/settings @@ -0,0 +1,2 @@ +# settings file for zun-ui plugin +enable_service zun-ui diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..4006293 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,152 @@ +# Makefile for Sphinx documentation + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Zun-UI.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Zun-UI.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Zun-UI" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Zun-UI" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..5d55c44 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,441 @@ +# 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. +# +# Horizon documentation build configuration file, created by +# sphinx-quickstart on Thu Oct 27 11:38:59 2011. +# +# 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. + +from __future__ import print_function + +import os +import subprocess +import sys + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) + +sys.path.insert(0, ROOT) + +# This is required for ReadTheDocs.org, but isn't a bad idea anyway. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', + 'zun_ui.test.settings') + +from zun_ui \ + import version as zunui_ver + + +def write_autodoc_index(): + + def find_autodoc_modules(module_name, sourcedir): + """returns a list of modules in the SOURCE directory.""" + modlist = [] + os.chdir(os.path.join(sourcedir, module_name)) + print("SEARCHING %s" % sourcedir) + for root, dirs, files in os.walk("."): + for filename in files: + if filename == 'tests.py': + continue + if filename.endswith(".py"): + # remove the pieces of the root + elements = root.split(os.path.sep) + # replace the leading "." with the module name + elements[0] = module_name + # and get the base module name + base, extension = os.path.splitext(filename) + if not (base == "__init__"): + elements.append(base) + result = ".".join(elements) + # print result + modlist.append(result) + return modlist + + RSTDIR = os.path.abspath(os.path.join(BASE_DIR, "sourcecode")) + SRCS = [('zun_ui', ROOT), ] + + EXCLUDED_MODULES = () + CURRENT_SOURCES = {} + + if not(os.path.exists(RSTDIR)): + os.mkdir(RSTDIR) + CURRENT_SOURCES[RSTDIR] = ['autoindex.rst'] + + INDEXOUT = open(os.path.join(RSTDIR, "autoindex.rst"), "w") + INDEXOUT.write(""" +================= +Source Code Index +================= + +.. contents:: + :depth: 1 + :local: + +""") + + for modulename, path in SRCS: + sys.stdout.write("Generating source documentation for %s\n" % + modulename) + INDEXOUT.write("\n%s\n" % modulename.capitalize()) + INDEXOUT.write("%s\n" % ("=" * len(modulename),)) + INDEXOUT.write(".. toctree::\n") + INDEXOUT.write(" :maxdepth: 1\n") + INDEXOUT.write("\n") + + MOD_DIR = os.path.join(RSTDIR, modulename) + CURRENT_SOURCES[MOD_DIR] = [] + if not(os.path.exists(MOD_DIR)): + os.mkdir(MOD_DIR) + for module in find_autodoc_modules(modulename, path): + if any([module.startswith(exclude) for exclude + in EXCLUDED_MODULES]): + print("Excluded module %s." % module) + continue + mod_path = os.path.join(path, *module.split(".")) + generated_file = os.path.join(MOD_DIR, "%s.rst" % module) + + INDEXOUT.write(" %s/%s\n" % (modulename, module)) + + # Find the __init__.py module if this is a directory + if os.path.isdir(mod_path): + source_file = ".".join((os.path.join(mod_path, "__init__"), + "py",)) + else: + source_file = ".".join((os.path.join(mod_path), "py")) + + CURRENT_SOURCES[MOD_DIR].append("%s.rst" % module) + # Only generate a new file if the source has changed or we don't + # have a doc file to begin with. + if not os.access(generated_file, os.F_OK) or ( + os.stat(generated_file).st_mtime < + os.stat(source_file).st_mtime): + print("Module %s updated, generating new documentation." + % module) + FILEOUT = open(generated_file, "w") + header = "The :mod:`%s` Module" % module + FILEOUT.write("%s\n" % ("=" * len(header),)) + FILEOUT.write("%s\n" % header) + FILEOUT.write("%s\n" % ("=" * len(header),)) + FILEOUT.write(".. automodule:: %s\n" % module) + FILEOUT.write(" :members:\n") + FILEOUT.write(" :undoc-members:\n") + FILEOUT.write(" :show-inheritance:\n") + FILEOUT.write(" :noindex:\n") + FILEOUT.close() + + INDEXOUT.close() + + # Delete auto-generated .rst files for sources which no longer exist + for directory, subdirs, files in list(os.walk(RSTDIR)): + for old_file in files: + if old_file not in CURRENT_SOURCES.get(directory, []): + print("Removing outdated file for %s" % old_file) + os.remove(os.path.join(directory, old_file)) + + +write_autodoc_index() + +# 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 = ['sphinx.ext.autodoc', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', + 'oslosphinx', + ] + +# 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'Zun UI' +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 = zunui_ver.version_info.version_string() +# The full version, including alpha/beta/rc tags. +release = zunui_ver.version_info.release_string() + +# 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 = ['**/#*', '**~', '**/#*#'] + +# 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 = [] + +primary_domain = 'py' +nitpicky = 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_path = ['.'] +# html_theme = '_theme' + +# 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 = { + "nosidebar": "false" +} + +# 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 +# " v 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'] + +# 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' +git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local", + "-n1"] +html_last_updated_fmt = subprocess.check_output(git_cmd, + stdin=subprocess.PIPE) + +# 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 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 = 'Horizondoc' + + +# -- 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]). +latex_documents = [ + ('index', 'Horizon.tex', u'Horizon 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', u'Zun UI Documentation', + 'Documentation for the Zun UI plugin to the Openstack\ + Dashboard (Horizon)', + [u'OpenStack'], 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', 'Horizon', u'Horizon Documentation', u'OpenStack', + 'Horizon', 'One line description of project.', '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' + + +# -- Options for Epub output -------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'Horizon' +epub_author = u'OpenStack' +epub_publisher = u'OpenStack' +epub_copyright = u'2012, OpenStack' + +# 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 an 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 = () + +# 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 = [] + +# The depth of the table of contents in toc.ncx. +# epub_tocdepth = 3 + +# Allow duplicate toc entries. +# epub_tocdup = True diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..333dfc1 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,79 @@ +========== +zun-ui +========== + +Zun UI + +* Free software: Apache license +* Source: http://git.openstack.org/cgit/openstack/zun-ui +* Bugs: http://bugs.launchpad.net/zun-ui + +Enabling in DevStack +-------------------- + +Add this repo as an external repository into your ``local.conf`` file:: + + [[local|localrc]] + enable_plugin zun-ui https://github.com/openstack/zun-ui + +Manual Installation +------------------- + +Begin by cloning the Horizon and Zun UI repositories:: + + git clone https://github.com/openstack/horizon + git clone https://github.com/openstack/zun-ui + +Create a virtual environment and install Horizon dependencies:: + + cd horizon + python tools/install_venv.py + +Set up your ``local_settings.py`` file:: + + cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py + +Open up the copied ``local_settings.py`` file in your preferred text +editor. You will want to customize several settings: + +- ``OPENSTACK_HOST`` should be configured with the hostname of your + OpenStack server. Verify that the ``OPENSTACK_KEYSTONE_URL`` and + ``OPENSTACK_KEYSTONE_DEFAULT_ROLE`` settings are correct for your + environment. (They should be correct unless you modified your + OpenStack server to change them.) + +Install Zun UI with all dependencies in your virtual environment:: + + tools/with_venv.sh pip install -e ../zun-ui/ + +And enable it in Horizon:: + + cp ../zun-ui/zun_ui/enabled/_1330_project_container_panelgroup.py openstack_dashboard/local/enabled + cp ../zun-ui/zun_ui/enabled/_1331_project_container_containers_panel.py openstack_dashboard/local/enabled + +To run horizon with the newly enabled Zun UI plugin run:: + + ./run_tests.sh --runserver 0.0.0.0:8080 + +to have the application start on port 8080 and the horizon dashboard will be +available in your browser at http://localhost:8080/ + +Release Notes +============= + +.. toctree:: + :glob: + :maxdepth: 1 + + releases/* + +Source Code Reference +===================== + +.. toctree:: + :glob: + :maxdepth: 1 + + sourcecode/autoindex + + diff --git a/doc/source/releases/0.1.0.rst b/doc/source/releases/0.1.0.rst new file mode 100644 index 0000000..d99c280 --- /dev/null +++ b/doc/source/releases/0.1.0.rst @@ -0,0 +1,2 @@ +Zun UI 0.1.0 +============ diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..73ed1f9 --- /dev/null +++ b/manage.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# 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. + +import os +import sys + +from django.core.management import execute_from_command_line # noqa + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", + "zun_ui.test.settings") + execute_from_command_line(sys.argv) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1a0af2c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,40 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +# Order matters to the pip dependency resolver, so sorting this file +# changes how packages are installed. New dependencies should be +# added in alphabetical order, however, some dependencies may need to +# be installed in a specific order. +# +# PBR should always appear first +pbr>=1.6 # Apache-2.0 +# If python-higginsclient will be created, we will use it. +#python-higginsclient>=0.1.0 # Apache-2.0 +Babel>=2.3.4 # BSD +Django<1.9,>=1.8 # BSD +django-babel>=0.5.1 # BSD +django-compressor>=2.0 # MIT +django-openstack-auth>=2.2.0 # Apache-2.0 +django-pyscss>=2.0.2 # BSD License (2 clause) +XStatic>=1.0.0 # MIT License +XStatic-Angular>=1.3.7 # MIT License +XStatic-Angular-Bootstrap>=0.11.0.2 # MIT License +XStatic-Angular-Gettext>=2.1.0.2 # MIT License +XStatic-Angular-lrdragndrop>=1.0.2.2 # MIT License +XStatic-Bootstrap-Datepicker>=1.3.1.0 # Apache 2.0 License +XStatic-Bootstrap-SCSS>=3 # Apache 2.0 License +XStatic-D3>=3.1.6.2 # BSD License (3 clause) +XStatic-Hogan>=2.0.0.2 # Apache 2.0 License +XStatic-Font-Awesome>=4.3.0 # SIL OFL 1.1 License, MIT License +XStatic-Jasmine>=2.1.2.0 # MIT License +XStatic-jQuery>=1.7.2 # MIT License +XStatic-JQuery-Migrate>=1.2.1.1 # MIT License +XStatic-JQuery.quicksearch>=2.0.3.1 # MIT License +XStatic-JQuery.TableSorter>=2.14.5.1 # MIT License +XStatic-jquery-ui>=1.10.1 # MIT License +XStatic-JSEncrypt>=2.0.0.2 # MIT License +XStatic-Magic-Search>=0.2.5.1 # Apache 2.0 License +XStatic-Rickshaw>=1.5.0 # BSD License (prior) +XStatic-smart-table>=1.4.5.3 # MIT License +XStatic-Spin>=1.2.5.2 # MIT License +XStatic-term.js>=0.0.4 # MIT License diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..ae24324 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,583 @@ +#!/bin/bash + +set -o errexit + +function usage { + echo "Usage: $0 [OPTION]..." + echo "Run Horizon's test suite(s)" + echo "" + echo " -V, --virtual-env Always use virtualenv. Install automatically" + echo " if not present" + echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local" + echo " environment" + echo " -c, --coverage Generate reports using Coverage" + echo " -f, --force Force a clean re-build of the virtual" + echo " environment. Useful when dependencies have" + echo " been added." + echo " -m, --manage Run a Django management command." + echo " --makemessages Create/Update English translation files." + echo " --compilemessages Compile all translation files." + echo " --check-only Do not update translation files (--makemessages only)." + echo " --pseudo Pseudo translate a language." + echo " -p, --pep8 Just run pep8" + echo " -8, --pep8-changed []" + echo " Just run PEP8 and HACKING compliance check" + echo " on files changed since HEAD~1 (or )" + echo " -P, --no-pep8 Don't run pep8 by default" + echo " -t, --tabs Check for tab characters in files." + echo " -y, --pylint Just run pylint" + echo " -e, --eslint Just run eslint" + echo " -k, --karma Just run karma" + echo " -q, --quiet Run non-interactively. (Relatively) quiet." + echo " Implies -V if -N is not set." + echo " --only-selenium Run only the Selenium unit tests" + echo " --with-selenium Run unit tests including Selenium tests" + echo " --selenium-headless Run Selenium tests headless" + echo " --integration Run the integration tests (requires a running " + echo " OpenStack environment)" + echo " --runserver Run the Django development server for" + echo " openstack_dashboard in the virtual" + echo " environment." + echo " --docs Just build the documentation" + echo " --backup-environment Make a backup of the environment on exit" + echo " --restore-environment Restore the environment before running" + echo " --destroy-environment Destroy the environment and exit" + echo " -h, --help Print this usage message" + echo "" + echo "Note: with no options specified, the script will try to run the tests in" + echo " a virtual environment, If no virtualenv is found, the script will ask" + echo " if you would like to create one. If you prefer to run tests NOT in a" + echo " virtual environment, simply pass the -N option." + exit +} + +# DEFAULTS FOR RUN_TESTS.SH +# +root=`pwd -P` +venv=$root/.venv +venv_env_version=$venv/environments +with_venv=tools/with_venv.sh +included_dirs="zun_ui" + +always_venv=0 +backup_env=0 +command_wrapper="" +destroy=0 +force=0 +just_pep8=0 +just_pep8_changed=0 +no_pep8=0 +just_pylint=0 +just_docs=0 +just_tabs=0 +just_eslint=0 +just_karma=0 +never_venv=0 +quiet=0 +restore_env=0 +runserver=0 +only_selenium=0 +with_selenium=0 +selenium_headless=0 +integration=0 +testopts="" +testargs="" +with_coverage=0 +makemessages=0 +compilemessages=0 +check_only=0 +pseudo=0 +manage=0 + +# Jenkins sets a "JOB_NAME" variable, if it's not set, we'll make it "default" +[ "$JOB_NAME" ] || JOB_NAME="default" + +function process_option { + # If running manage command, treat the rest of options as arguments. + if [ $manage -eq 1 ]; then + testargs="$testargs $1" + return 0 + fi + + case "$1" in + -h|--help) usage;; + -V|--virtual-env) always_venv=1; never_venv=0;; + -N|--no-virtual-env) always_venv=0; never_venv=1;; + -p|--pep8) just_pep8=1;; + -8|--pep8-changed) just_pep8_changed=1;; + -P|--no-pep8) no_pep8=1;; + -y|--pylint) just_pylint=1;; + -e|--eslint) just_eslint=1;; + -k|--karma) just_karma=1;; + -f|--force) force=1;; + -t|--tabs) just_tabs=1;; + -q|--quiet) quiet=1;; + -c|--coverage) with_coverage=1;; + -m|--manage) manage=1;; + --makemessages) makemessages=1;; + --compilemessages) compilemessages=1;; + --check-only) check_only=1;; + --pseudo) pseudo=1;; + --only-selenium) only_selenium=1;; + --with-selenium) with_selenium=1;; + --selenium-headless) selenium_headless=1;; + --integration) integration=1;; + --docs) just_docs=1;; + --runserver) runserver=1;; + --backup-environment) backup_env=1;; + --restore-environment) restore_env=1;; + --destroy-environment) destroy=1;; + -*) testopts="$testopts $1";; + *) testargs="$testargs $1" + esac +} + +function run_management_command { + ${command_wrapper} python $root/manage.py $testopts $testargs +} + +function run_server { + echo "Starting Django development server..." + ${command_wrapper} python $root/manage.py runserver $testopts $testargs + echo "Server stopped." +} + +function run_pylint { + echo "Running pylint ..." + PYTHONPATH=$root ${command_wrapper} pylint --rcfile=.pylintrc -f parseable $included_dirs > pylint.txt || true + CODE=$? + grep Global -A2 pylint.txt + if [ $CODE -lt 32 ]; then + echo "Completed successfully." + exit 0 + else + echo "Completed with problems." + exit $CODE + fi +} + +function run_eslint { + echo "Running eslint ..." + if [ "`which npm`" == '' ] ; then + echo "npm is not present; please install, e.g. sudo apt-get install npm" + else + npm install + npm run lint + fi +} + +function run_karma { + echo "Running karma ..." + npm install + npm run test +} + +function warn_on_flake8_without_venv { + set +o errexit + ${command_wrapper} python -c "import hacking" 2>/dev/null + no_hacking=$? + set -o errexit + if [ $never_venv -eq 1 -a $no_hacking -eq 1 ]; then + echo "**WARNING**:" >&2 + echo "OpenStack hacking is not installed on your host. Its detection will be missed." >&2 + echo "Please install or use virtual env if you need OpenStack hacking detection." >&2 + fi +} + +function run_pep8 { + echo "Running flake8 ..." + warn_on_flake8_without_venv + DJANGO_SETTINGS_MODULE=zun_ui.test.settings ${command_wrapper} flake8 +} + +function run_pep8_changed { + # NOTE(gilliard) We want use flake8 to check the entirety of every file that has + # a change in it. Unfortunately the --filenames argument to flake8 only accepts + # file *names* and there are no files named (eg) "nova/compute/manager.py". The + # --diff argument behaves surprisingly as well, because although you feed it a + # diff, it actually checks the file on disk anyway. + local base_commit=${testargs:-HEAD~1} + files=$(git diff --name-only $base_commit | tr '\n' ' ') + echo "Running flake8 on ${files}" + warn_on_flake8_without_venv + diff -u --from-file /dev/null ${files} | DJANGO_SETTINGS_MODULE=zun_ui.test.settings ${command_wrapper} flake8 --diff + exit +} + +function run_sphinx { + echo "Building sphinx..." + DJANGO_SETTINGS_MODULE=zun_ui.test.settings ${command_wrapper} python setup.py build_sphinx + echo "Build complete." +} + +function tab_check { + TAB_VIOLATIONS=`find $included_dirs -type f -regex ".*\.\(css\|js\|py\|html\)" -print0 | xargs -0 awk '/\t/' | wc -l` + if [ $TAB_VIOLATIONS -gt 0 ]; then + echo "TABS! $TAB_VIOLATIONS of them! Oh no!" + HORIZON_FILES=`find $included_dirs -type f -regex ".*\.\(css\|js\|py|\html\)"` + for TABBED_FILE in $HORIZON_FILES + do + TAB_COUNT=`awk '/\t/' $TABBED_FILE | wc -l` + if [ $TAB_COUNT -gt 0 ]; then + echo "$TABBED_FILE: $TAB_COUNT" + fi + done + fi + return $TAB_VIOLATIONS; +} + +function destroy_venv { + echo "Cleaning environment..." + echo "Removing virtualenv..." + rm -rf $venv + echo "Virtualenv removed." +} + +function environment_check { + echo "Checking environment." + if [ -f $venv_env_version ]; then + set +o errexit + cat requirements.txt test-requirements.txt | cmp $venv_env_version - > /dev/null + local env_check_result=$? + set -o errexit + if [ $env_check_result -eq 0 ]; then + # If the environment exists and is up-to-date then set our variables + command_wrapper="${root}/${with_venv}" + echo "Environment is up to date." + return 0 + fi + fi + + if [ $always_venv -eq 1 ]; then + install_venv + else + if [ ! -e ${venv} ]; then + echo -e "Environment not found. Install? (Y/n) \c" + else + echo -e "Your environment appears to be out of date. Update? (Y/n) \c" + fi + read update_env + if [ "x$update_env" = "xY" -o "x$update_env" = "x" -o "x$update_env" = "xy" ]; then + install_venv + else + # Set our command wrapper anyway. + command_wrapper="${root}/${with_venv}" + fi + fi +} + +function sanity_check { + # Anything that should be determined prior to running the tests, server, etc. + # Don't sanity-check anything environment-related in -N flag is set + if [ $never_venv -eq 0 ]; then + if [ ! -e ${venv} ]; then + echo "Virtualenv not found at $venv. Did install_venv.py succeed?" + exit 1 + fi + fi + # Remove .pyc files. This is sanity checking because they can linger + # after old files are deleted. + find . -name "*.pyc" -exec rm -rf {} \; +} + +function backup_environment { + if [ $backup_env -eq 1 ]; then + echo "Backing up environment \"$JOB_NAME\"..." + if [ ! -e ${venv} ]; then + echo "Environment not installed. Cannot back up." + return 0 + fi + if [ -d /tmp/.horizon_environment/$JOB_NAME ]; then + mv /tmp/.horizon_environment/$JOB_NAME /tmp/.horizon_environment/$JOB_NAME.old + rm -rf /tmp/.horizon_environment/$JOB_NAME + fi + mkdir -p /tmp/.horizon_environment/$JOB_NAME + cp -r $venv /tmp/.horizon_environment/$JOB_NAME/ + # Remove the backup now that we've completed successfully + rm -rf /tmp/.horizon_environment/$JOB_NAME.old + echo "Backup completed" + fi +} + +function restore_environment { + if [ $restore_env -eq 1 ]; then + echo "Restoring environment from backup..." + if [ ! -d /tmp/.horizon_environment/$JOB_NAME ]; then + echo "No backup to restore from." + return 0 + fi + + cp -r /tmp/.horizon_environment/$JOB_NAME/.venv ./ || true + echo "Environment restored successfully." + fi +} + +function install_venv { + # Install with install_venv.py + export PIP_DOWNLOAD_CACHE=${PIP_DOWNLOAD_CACHE-/tmp/.pip_download_cache} + export PIP_USE_MIRRORS=true + if [ $quiet -eq 1 ]; then + export PIP_NO_INPUT=true + fi + echo "Fetching new src packages..." + rm -rf $venv/src + python tools/install_venv.py + command_wrapper="$root/${with_venv}" + # Make sure it worked and record the environment version + sanity_check + chmod -R 754 $venv + cat requirements.txt test-requirements.txt > $venv_env_version +} + +function run_tests { + sanity_check + + if [ $with_selenium -eq 1 ]; then + export WITH_SELENIUM=1 + elif [ $only_selenium -eq 1 ]; then + export WITH_SELENIUM=1 + export SKIP_UNITTESTS=1 + fi + + if [ $with_selenium -eq 0 -a $integration -eq 0 ]; then + testopts="$testopts --exclude-dir=zun_ui/test/integration_tests" + fi + + if [ $selenium_headless -eq 1 ]; then + export SELENIUM_HEADLESS=1 + fi + + if [ -z "$testargs" ]; then + run_tests_all + else + run_tests_subset + fi +} + +function run_tests_subset { + project=`echo $testargs | awk -F. '{print $1}'` + ${command_wrapper} python $root/manage.py test --settings=$project.test.settings $testopts $testargs +} + +function run_tests_all { + echo "Running zun UI tests" + export NOSE_XUNIT_FILE=zun_ui/nosetests.xml + if [ "$NOSE_WITH_HTML_OUTPUT" = '1' ]; then + export NOSE_HTML_OUT_FILE='dashboard_nose_results.html' + fi + ${command_wrapper} ${coverage_run} $root/manage.py test zun_ui --settings=openstack_dashboard.test.settings $testopts + # get results of the openstack_dashboard tests + DASHBOARD_RESULT=$? + + if [ $with_coverage -eq 1 ]; then + echo "Generating coverage reports" + ${command_wrapper} python -m coverage.__main__ combine + ${command_wrapper} python -m coverage.__main__ xml -i --include="horizon/*,openstack_dashboard/*" --omit='/usr*,setup.py,*egg*,.venv/*' + ${command_wrapper} python -m coverage.__main__ html -i --include="horizon/*,openstack_dashboard/*" --omit='/usr*,setup.py,*egg*,.venv/*' -d reports + fi + # Remove the leftover coverage files from the -p flag earlier. + rm -f .coverage.* + + PEP8_RESULT=0 + if [ $no_pep8 -eq 0 ] && [ $only_selenium -eq 0 ]; then + run_pep8 + PEP8_RESULT=$? + fi + + TEST_RESULT=$(($DASHBOARD_RESULT || $PEP8_RESULT)) + if [ $TEST_RESULT -eq 0 ]; then + echo "Tests completed successfully." + else + echo "Tests failed." + fi + exit $TEST_RESULT +} + +function run_integration_tests { + export INTEGRATION_TESTS=1 + + if [ $selenium_headless -eq 1 ]; then + export SELENIUM_HEADLESS=1 + fi + + echo "Running Horizon integration tests..." + if [ -z "$testargs" ]; then + ${command_wrapper} nosetests openstack_dashboard/test/integration_tests/tests + else + ${command_wrapper} nosetests $testargs + fi + exit 0 +} + +function babel_extract { + DOMAIN=$1 + KEYWORDS="-k gettext_noop -k gettext_lazy -k ngettext_lazy:1,2" + KEYWORDS+=" -k ugettext_noop -k ugettext_lazy -k ungettext_lazy:1,2" + KEYWORDS+=" -k npgettext:1c,2,3 -k pgettext_lazy:1c,2 -k npgettext_lazy:1c,2,3" + + ${command_wrapper} pybabel extract -F ../babel-${DOMAIN}.cfg -o locale/${DOMAIN}.pot $KEYWORDS . +} + +function run_makemessages { + + echo -n "zun ui: " + cd zun_ui + babel_extract django + zun_PY_RESULT=$? + + echo -n "zun ui javascript: " + babel_extract djangojs + zun_JS_RESULT=$? + + cd .. + if [ $check_only -eq 1 ]; then + rm zun_ui/locale/django*.pot + fi + + exit $(($zun_PY_RESULT || $zun_JS_RESULT)) +} + +function run_compilemessages { + cd horizon + ${command_wrapper} $root/manage.py compilemessages + HORIZON_PY_RESULT=$? + cd ../openstack_dashboard + ${command_wrapper} $root/manage.py compilemessages + DASHBOARD_RESULT=$? + exit $(($HORIZON_PY_RESULT || $DASHBOARD_RESULT)) +} + +function run_pseudo { + for lang in $testargs + # Use English pot file as the source file/pot file just like real Horizon translations + do + ${command_wrapper} $root/tools/pseudo.py openstack_dashboard/locale/django.pot openstack_dashboard/locale/$lang/LC_MESSAGES/django.po $lang + ${command_wrapper} $root/tools/pseudo.py openstack_dashboard/locale/djangojs.pot openstack_dashboard/locale/$lang/LC_MESSAGES/djangojs.po $lang + ${command_wrapper} $root/tools/pseudo.py horizon/locale/django.pot horizon/locale/$lang/LC_MESSAGES/django.po $lang + ${command_wrapper} $root/tools/pseudo.py horizon/locale/djangojs.pot horizon/locale/$lang/LC_MESSAGES/djangojs.po $lang + done + exit $? +} + + +# ---------PREPARE THE ENVIRONMENT------------ # + +# PROCESS ARGUMENTS, OVERRIDE DEFAULTS +for arg in "$@"; do + process_option $arg +done + +if [ $quiet -eq 1 ] && [ $never_venv -eq 0 ] && [ $always_venv -eq 0 ] +then + always_venv=1 +fi + +# If destroy is set, just blow it away and exit. +if [ $destroy -eq 1 ]; then + destroy_venv + exit 0 +fi + +# Ignore all of this if the -N flag was set +if [ $never_venv -eq 0 ]; then + + # Restore previous environment if desired + if [ $restore_env -eq 1 ]; then + restore_environment + fi + + # Remove the virtual environment if --force used + if [ $force -eq 1 ]; then + destroy_venv + fi + + # Then check if it's up-to-date + environment_check + + # Create a backup of the up-to-date environment if desired + if [ $backup_env -eq 1 ]; then + backup_environment + fi +fi + +# ---------EXERCISE THE CODE------------ # + +# Run management commands +if [ $manage -eq 1 ]; then + run_management_command + exit $? +fi + +# Build the docs +if [ $just_docs -eq 1 ]; then + run_sphinx + exit $? +fi + +# Update translation files +if [ $makemessages -eq 1 ]; then + run_makemessages + exit $? +fi + +# Compile translation files +if [ $compilemessages -eq 1 ]; then + run_compilemessages + exit $? +fi + +# Generate Pseudo translation +if [ $pseudo -eq 1 ]; then + run_pseudo + exit $? +fi + +# PEP8 +if [ $just_pep8 -eq 1 ]; then + run_pep8 + exit $? +fi + +if [ $just_pep8_changed -eq 1 ]; then + run_pep8_changed + exit $? +fi + +# Pylint +if [ $just_pylint -eq 1 ]; then + run_pylint + exit $? +fi + +# ESLint +if [ $just_eslint -eq 1 ]; then + run_eslint + exit $? +fi + +# Karma +if [ $just_karma -eq 1 ]; then + run_karma + exit $? +fi + +# Tab checker +if [ $just_tabs -eq 1 ]; then + tab_check + exit $? +fi + +# Integration tests +if [ $integration -eq 1 ]; then + run_integration_tests + exit $? +fi + +# Django development server +if [ $runserver -eq 1 ]; then + run_server + exit $? +fi + +# Full test suite +run_tests || exit diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..24cbcb5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,23 @@ +[metadata] +name = zun-ui +summary = Zun User Interface +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + +[files] +packages = + zun_ui diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ddd1771 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +# 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. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr>=1.8'], + pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..a6593d9 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,29 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +# Order matters to the pip dependency resolver, so sorting this file +# changes how packages are installed. New dependencies should be +# added in alphabetical order, however, some dependencies may need to +# be installed in a specific order. +# +# Hacking should appear first in case something else depends on pep8 +hacking<0.11,>=0.10.2 +# +coverage>=3.6 # Apache-2.0 +django-nose>=1.2 # BSD +mock>=2.0 # BSD +mox3>=0.7.0 # Apache-2.0 +nodeenv>=0.9.4 # BSD License # BSD +nose # LGPL +nose-exclude # LGPL +nosehtmloutput>=0.0.3 # Apache-2.0 +nosexcover # BSD +openstack.nose-plugin>=0.7 # Apache-2.0 +oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 +selenium>=2.50.1 # Apache-2.0 +sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD +testtools>=1.4.0 # MIT +# This also needs xvfb library installed on your OS +xvfbwrapper>=0.1.3 #license: MIT +# Include horizon as test requirement +http://tarballs.openstack.org/horizon/horizon-master.tar.gz#egg=horizon diff --git a/tools/install_venv.py b/tools/install_venv.py new file mode 100644 index 0000000..e96521e --- /dev/null +++ b/tools/install_venv.py @@ -0,0 +1,71 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2010 OpenStack Foundation +# Copyright 2013 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. + +import os +import sys + +import install_venv_common as install_venv # noqa + + +def print_help(venv, root): + help = """ + OpenStack development environment setup is complete. + + OpenStack development uses virtualenv to track and manage Python + dependencies while in development and testing. + + To activate the OpenStack virtualenv for the extent of your current shell + session you can run: + + $ source %s/bin/activate + + Or, if you prefer, you can run commands in the virtualenv on a case by case + basis by running: + + $ %s/tools/with_venv.sh + + Also, make test will automatically use the virtualenv. + """ + print(help % (venv, root)) + + +def main(argv): + root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + + if os.environ.get('tools_path'): + root = os.environ['tools_path'] + venv = os.path.join(root, '.venv') + if os.environ.get('venv'): + venv = os.environ['venv'] + + pip_requires = os.path.join(root, 'requirements.txt') + test_requires = os.path.join(root, 'test-requirements.txt') + py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) + project = 'OpenStack' + install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, + py_version, project) + options = install.parse_args(argv) + install.check_python_version() + install.check_dependencies() + install.create_virtualenv(no_site_packages=options.no_site_packages) + install.install_dependencies() + print_help(venv, root) + +if __name__ == '__main__': + main(sys.argv) diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py new file mode 100644 index 0000000..e279159 --- /dev/null +++ b/tools/install_venv_common.py @@ -0,0 +1,172 @@ +# Copyright 2013 OpenStack Foundation +# Copyright 2013 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. + +"""Provides methods needed by installation script for OpenStack development +virtual environments. + +Since this script is used to bootstrap a virtualenv from the system's Python +environment, it should be kept strictly compatible with Python 2.6. + +Synced in from openstack-common +""" + +from __future__ import print_function + +import optparse +import os +import subprocess +import sys + + +class InstallVenv(object): + + def __init__(self, root, venv, requirements, + test_requirements, py_version, + project): + self.root = root + self.venv = venv + self.requirements = requirements + self.test_requirements = test_requirements + self.py_version = py_version + self.project = project + + def die(self, message, *args): + print(message % args, file=sys.stderr) + sys.exit(1) + + def check_python_version(self): + if sys.version_info < (2, 6): + self.die("Need Python Version >= 2.6") + + def run_command_with_code(self, cmd, redirect_output=True, + check_exit_code=True): + """Runs a command in an out-of-process shell. + + Returns the output of that command. Working directory is self.root. + """ + if redirect_output: + stdout = subprocess.PIPE + else: + stdout = None + + proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) + output = proc.communicate()[0] + if check_exit_code and proc.returncode != 0: + self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) + return (output, proc.returncode) + + def run_command(self, cmd, redirect_output=True, check_exit_code=True): + return self.run_command_with_code(cmd, redirect_output, + check_exit_code)[0] + + def get_distro(self): + if (os.path.exists('/etc/fedora-release') or + os.path.exists('/etc/redhat-release')): + return Fedora( + self.root, self.venv, self.requirements, + self.test_requirements, self.py_version, self.project) + else: + return Distro( + self.root, self.venv, self.requirements, + self.test_requirements, self.py_version, self.project) + + def check_dependencies(self): + self.get_distro().install_virtualenv() + + def create_virtualenv(self, no_site_packages=True): + """Creates the virtual environment and installs PIP. + + Creates the virtual environment and installs PIP only into the + virtual environment. + """ + if not os.path.isdir(self.venv): + print('Creating venv...', end=' ') + if no_site_packages: + self.run_command(['virtualenv', '-q', '--no-site-packages', + self.venv]) + else: + self.run_command(['virtualenv', '-q', self.venv]) + print('done.') + else: + print("venv already exists...") + pass + + def pip_install(self, *args): + self.run_command(['tools/with_venv.sh', + 'pip', 'install', '--upgrade'] + list(args), + redirect_output=False) + + def install_dependencies(self): + print('Installing dependencies with pip (this can take a while)...') + + # First things first, make sure our venv has the latest pip and + # setuptools and pbr + self.pip_install('pip>=1.4') + self.pip_install('setuptools') + self.pip_install('pbr') + + self.pip_install('-r', self.requirements, '-r', self.test_requirements) + + def parse_args(self, argv): + """Parses command-line arguments.""" + parser = optparse.OptionParser() + parser.add_option('-n', '--no-site-packages', + action='store_true', + help="Do not inherit packages from global Python " + "install.") + return parser.parse_args(argv[1:])[0] + + +class Distro(InstallVenv): + + def check_cmd(self, cmd): + return bool(self.run_command(['which', cmd], + check_exit_code=False).strip()) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if self.check_cmd('easy_install'): + print('Installing virtualenv via easy_install...', end=' ') + if self.run_command(['easy_install', 'virtualenv']): + print('Succeeded') + return + else: + print('Failed') + + self.die('ERROR: virtualenv not found.\n\n%s development' + ' requires virtualenv, please install it using your' + ' favorite package management tool' % self.project) + + +class Fedora(Distro): + """This covers all Fedora-based distributions. + + Includes: Fedora, RHEL, CentOS, Scientific Linux + """ + + def check_pkg(self, pkg): + return self.run_command_with_code(['rpm', '-q', pkg], + check_exit_code=False)[1] == 0 + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if not self.check_pkg('python-virtualenv'): + self.die("Please install 'python-virtualenv'.") + + super(Fedora, self).install_virtualenv() diff --git a/tools/with_venv.sh b/tools/with_venv.sh new file mode 100755 index 0000000..7303990 --- /dev/null +++ b/tools/with_venv.sh @@ -0,0 +1,7 @@ +#!/bin/bash +TOOLS_PATH=${TOOLS_PATH:-$(dirname $0)} +VENV_PATH=${VENV_PATH:-${TOOLS_PATH}} +VENV_DIR=${VENV_NAME:-/../.venv} +TOOLS=${TOOLS_PATH} +VENV=${VENV:-${VENV_PATH}/${VENV_DIR}} +source ${VENV}/bin/activate && "$@" diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..ed72ed2 --- /dev/null +++ b/tox.ini @@ -0,0 +1,107 @@ +[tox] +envlist = py27,py27dj18,pep834 +minversion = 1.6 +skipsdist = True + +[testenv] +usedevelop = True +setenv = VIRTUAL_ENV={envdir} + NOSE_WITH_OPENSTACK=1 + NOSE_OPENSTACK_COLOR=1 + NOSE_OPENSTACK_RED=0.05 + NOSE_OPENSTACK_YELLOW=0.025 + NOSE_OPENSTACK_SHOW_ELAPSED=1 +# Note the hash seed is set to 0 until horizon can be tested with a +# random hash seed successfully. + PYTHONHASHSEED=0 +install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -U {opts} {packages} +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +commands = /bin/bash run_tests.sh -N --no-pep8 {posargs} + +[testenv:pep8] +commands = + /bin/bash run_tests.sh -N --pep8 + /bin/bash run_tests.sh -N --makemessages --check-only + +[testenv:venv] +# NOTE(shu-mutou) The setting of the install_command in this location +# is only required because currently infra does not actually +# support constraints files for the post job, and while +# the environment variable UPPER_CONSTRAINTS_FILE is set, there is +# no file there. It can be removed when infra changes this. +install_command = pip install -U {opts} {packages} +commands = {posargs} + +[testenv:cover] +# NOTE(shu-mutou) The setting of the install_command in this location +# is only required because currently infra does not actually +# support constraints files for the post job, and while +# the environment variable UPPER_CONSTRAINTS_FILE is set, there is +# no file there. It can be removed when infra changes this. +install_command = pip install -U {opts} {packages} +commands = /bin/bash run_tests.sh -N --no-pep8 --coverage {posargs} + +[testenv:py27dj18] +# NOTE(shu-mutou) The setting of the install_command in this location +# is only required because currently infra does not actually +# support constraints files for the post job, and while +# the environment variable UPPER_CONSTRAINTS_FILE is set, there is +# no file there. It can be removed when infra changes this. +install_command = pip install -U {opts} {packages} +basepython = python2.7 +commands = pip install django>=1.8,<1.9 + /bin/bash run_tests.sh -N --no-pep8 {posargs} + +[testenv:py27integration] +# NOTE(shu-mutou) The setting of the install_command in this location +# is only required because currently infra does not actually +# support constraints files for the post job, and while +# the environment variable UPPER_CONSTRAINTS_FILE is set, there is +# no file there. It can be removed when infra changes this. +install_command = pip install -U {opts} {packages} +basepython = python2.7 +commands = /bin/bash run_tests.sh -N --integration --selenium-headless {posargs} + +[testenv:eslint] +passenv = * +commands = nodeenv -p + npm install + /bin/bash run_tests.sh -N --eslint + +[testenv:docs] +# NOTE(shu-mutou) The setting of the install_command in this location +# is only required because currently infra does not actually +# support constraints files for the post job, and while +# the environment variable UPPER_CONSTRAINTS_FILE is set, there is +# no file there. It can be removed when infra changes this. +install_command = pip install -U {opts} {packages} +setenv = DJANGO_SETTINGS_MODULE=openstack_dashboard.test.settings +commands = python setup.py build_sphinx + +[flake8] +exclude = .venv,.git,.tox,dist,*openstack/common*,*lib/python*,*egg,build,panel_template,dash_template,local_settings.py,*/local/*,*/test/test_plugins/*,.ropeproject +max-complexity = 20 + +[hacking] +import_exceptions = collections.defaultdict, + django.conf.settings, + django.conf.urls.include, + django.conf.urls.patterns, + django.conf.urls.url, + django.core.urlresolvers.reverse, + django.core.urlresolvers.reverse_lazy, + django.template.loader.render_to_string, + django.test.utils.override_settings, + django.utils.datastructures.SortedDict, + django.utils.encoding.force_text, + django.utils.html.conditional_escape, + django.utils.html.escape, + django.utils.http.urlencode, + django.utils.safestring.mark_safe, + django.utils.translation.npgettext_lazy, + django.utils.translation.pgettext_lazy, + django.utils.translation.ugettext_lazy, + django.utils.translation.ungettext_lazy, + operator.attrgetter, + StringIO.StringIO diff --git a/zun_ui/__init__.py b/zun_ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zun_ui/api/__init__.py b/zun_ui/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zun_ui/api/zun_rest_api.py b/zun_ui/api/zun_rest_api.py new file mode 100644 index 0000000..0e787c5 --- /dev/null +++ b/zun_ui/api/zun_rest_api.py @@ -0,0 +1,75 @@ +# 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 django.views import generic + +from zun_ui.api import zunclient + +from openstack_dashboard.api.rest import urls +from openstack_dashboard.api.rest import utils as rest_utils + + +def change_to_id(obj): + """Change key named 'uuid' to 'id' + + Zun returns objects with a field called 'uuid' many of Horizons + directives however expect objects to have a field called 'id'. + """ + obj['id'] = obj.pop('uuid') + return obj + + +@urls.register +class Container(generic.View): + """API for retrieving a single container""" + url_regex = r'zun/containers/(?P[^/]+)$' + + @rest_utils.ajax() + def get(self, request, id): + """Get a specific container""" + return change_to_id(zunclient.container_show(request, id).to_dict()) + + +@urls.register +class Containers(generic.View): + """API for Zun Containers""" + url_regex = r'zun/containers/$' + + @rest_utils.ajax() + def get(self, request): + """Get a list of the Containers for a project. + + The returned result is an object with property 'items' and each + item under this is a Container. + """ + result = zunclient.container_list(request) + return {'items': [change_to_id(n.to_dict()) for n in result]} + + @rest_utils.ajax(data_required=True) + def delete(self, request): + """Delete one or more Containers by id. + + Returns HTTP 204 (no content) on successful deletion. + """ + for id in request.DATA: + zunclient.container_delete(request, id) + + @rest_utils.ajax(data_required=True) + def post(self, request): + """Create a new Container. + + Returns the new Container object on success. + """ + new_container = zunclient.container_create(request, **request.DATA) + return rest_utils.CreatedResponse( + '/api/zun/container/%s' % new_container.uuid, + new_container.to_dict()) diff --git a/zun_ui/api/zunclient.py b/zun_ui/api/zunclient.py new file mode 100644 index 0000000..74a2431 --- /dev/null +++ b/zun_ui/api/zunclient.py @@ -0,0 +1,113 @@ +# 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 __future__ import absolute_import +import logging +#from zunclient.v1 import client as zun_client + +from horizon import exceptions +from horizon.utils.memoized import memoized +from openstack_dashboard.api import base + +# for stab, should remove when use CLI API +import copy +import uuid + + +LOG = logging.getLogger(__name__) + +CONTAINER_CREATE_ATTRS = ['name'] + +STUB_DATA = {} + +# for stab, should be removed when use CLI API +class StubResponse(object): + + def __init__(self, info): + self._info = info + + def __repr__(self): + reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_') + info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) + return "<%s %s>" % (self.__class__.__name__, info) + + def to_dict(self): + return copy.deepcopy(self._info) + + +@memoized +def zunclient(request): + zun_url = "" + """" + try: + zun_url = base.url_for(request, 'container') + except exceptions.ServiceCatalogException: + LOG.debug('No Container Management service is configured.') + return None + + LOG.debug('zunclient connection created using the token "%s" and url' + '"%s"' % (request.user.token.id, zun_url)) + c = zun_client.Client(username=request.user.username, + project_id=request.user.tenant_id, + input_auth_token=request.user.token.id, + zun_url=zun_url) + return c + """ + + +def container_create(request, **kwargs): + args = {} + for (key, value) in kwargs.items(): + if key in CONTAINER_CREATE_ATTRS: + args[str(key)] = str(value) + else: + raise exceptions.BadRequest( + "Key must be in %s" % ",".join(CONTAINER_CREATE_ATTRS)) + if key == "labels": + labels = {} + vals = value.split(",") + for v in vals: + kv = v.split("=", 1) + labels[kv[0]] = kv[1] + args["labels"] = labels + #created = zunclient(request).containers.create(**args) + + # create dummy response + args["uuid"] = uuid.uuid1().hex + created = StubResponse(args) + for k in args: + setattr(created, k, args[k]) + STUB_DATA[created.uuid] = created + + return created + + +def container_delete(request, id): + #deleted = zunclient(request).containers.delete(id) + deleted = STUB_DATA.pop(id) + + return deleted + + +def container_list(request, limit=None, marker=None, sort_key=None, + sort_dir=None, detail=True): + #list = zunclient(request).containers.list(limit, marker, sort_key, + # sort_dir, detail) + list = [STUB_DATA[data] for data in STUB_DATA] + return list + + +def container_show(request, id): + #show = zunclient(request).containers.get(id) + show = STUB_DATA.get(id) + return show diff --git a/zun_ui/content/__init__.py b/zun_ui/content/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zun_ui/content/container/__init__.py b/zun_ui/content/container/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zun_ui/content/container/containers/__init__.py b/zun_ui/content/container/containers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zun_ui/content/container/containers/panel.py b/zun_ui/content/container/containers/panel.py new file mode 100644 index 0000000..14fb57c --- /dev/null +++ b/zun_ui/content/container/containers/panel.py @@ -0,0 +1,22 @@ +# 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 django.utils.translation import ugettext_lazy as _ +import horizon + +# This panel will be loaded from horizon, because specified in enabled file. +# To register REST api, import below here. +from zun_ui.api import zun_rest_api # noqa + +class Containers(horizon.Panel): + name = _("Containers") + slug = "container.containers" diff --git a/zun_ui/content/container/containers/tests.py b/zun_ui/content/container/containers/tests.py new file mode 100644 index 0000000..1f3a475 --- /dev/null +++ b/zun_ui/content/container/containers/tests.py @@ -0,0 +1,19 @@ +# 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 openstack_dashboard.test import helpers as test + + +class ContainerTests(test.TestCase): + # Unit tests for container. + def test_me(self): + self.assertTrue(1 + 1 == 2) diff --git a/zun_ui/content/container/containers/urls.py b/zun_ui/content/container/containers/urls.py new file mode 100644 index 0000000..9ac2adc --- /dev/null +++ b/zun_ui/content/container/containers/urls.py @@ -0,0 +1,19 @@ +# 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 django.conf.urls import url +from zun_ui.content.container.containers.views import IndexView + + +urlpatterns = [ + url('', IndexView.as_view(), name='index'), +] diff --git a/zun_ui/content/container/containers/views.py b/zun_ui/content/container/containers/views.py new file mode 100644 index 0000000..82a7949 --- /dev/null +++ b/zun_ui/content/container/containers/views.py @@ -0,0 +1,17 @@ +# 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 django.views import generic + + +class IndexView(generic.TemplateView): + template_name = 'angular.html' diff --git a/zun_ui/enabled/_1330_project_container_panelgroup.py b/zun_ui/enabled/_1330_project_container_panelgroup.py new file mode 100644 index 0000000..67ffc0d --- /dev/null +++ b/zun_ui/enabled/_1330_project_container_panelgroup.py @@ -0,0 +1,36 @@ +# 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 django.utils.translation import ugettext_lazy as _ + +# The slug of the panel group to be added to HORIZON_CONFIG. Required. +PANEL_GROUP = 'container' +# The display name of the PANEL_GROUP. Required. +PANEL_GROUP_NAME = _('Container') +# The slug of the dashboard the PANEL_GROUP associated with. Required. +PANEL_GROUP_DASHBOARD = 'project' + +ADD_INSTALLED_APPS = ['zun_ui'] + +ADD_ANGULAR_MODULES = [ + 'horizon.dashboard.container' +] + +ADD_JS_FILES = [ + 'horizon/lib/angular/angular-route.js' +] + +ADD_SCSS_FILES = [ + 'dashboard/container/container.scss' +] + +AUTO_DISCOVER_STATIC_FILES = True diff --git a/zun_ui/enabled/_1331_project_container_containers_panel.py b/zun_ui/enabled/_1331_project_container_containers_panel.py new file mode 100644 index 0000000..f99d490 --- /dev/null +++ b/zun_ui/enabled/_1331_project_container_containers_panel.py @@ -0,0 +1,21 @@ +# 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. + +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'container.containers' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'container' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'zun_ui.content.container.containers.panel.Containers' diff --git a/zun_ui/models.py b/zun_ui/models.py new file mode 100644 index 0000000..1b3d5f9 --- /dev/null +++ b/zun_ui/models.py @@ -0,0 +1,3 @@ +""" +Stub file to work around django bug: https://code.djangoproject.com/ticket/7198 +""" diff --git a/zun_ui/static/dashboard/container/container.module.js b/zun_ui/static/dashboard/container/container.module.js new file mode 100644 index 0000000..a94af1a --- /dev/null +++ b/zun_ui/static/dashboard/container/container.module.js @@ -0,0 +1,37 @@ +/** + * 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'; + + /** + * @ngdoc overview + * @name horizon.dashboard.container + * @description + * Dashboard module to host various container panels. + */ + angular + .module('horizon.dashboard.container', [ + 'horizon.dashboard.container.containers', + 'ngRoute' + ]) + .config(config) + + config.$inject = ['$provide', '$windowProvider', '$routeProvider']; + + function config($provide, $windowProvider, $routeProvider) { + var path = $windowProvider.$get().STATIC_URL + 'dashboard/container/'; + $provide.constant('horizon.dashboard.container.basePath', path); + } +})(); diff --git a/zun_ui/static/dashboard/container/container.module.spec.js b/zun_ui/static/dashboard/container/container.module.spec.js new file mode 100644 index 0000000..379e147 --- /dev/null +++ b/zun_ui/static/dashboard/container/container.module.spec.js @@ -0,0 +1,23 @@ +/** + * 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'; + + describe('horizon.dashboard.container', function() { + it('should exist', function() { + expect(angular.module('horizon.dashboard.container')).toBeDefined(); + }); + }); + +})(); diff --git a/zun_ui/static/dashboard/container/container.scss b/zun_ui/static/dashboard/container/container.scss new file mode 100644 index 0000000..92a703f --- /dev/null +++ b/zun_ui/static/dashboard/container/container.scss @@ -0,0 +1,8 @@ +@import "containers/containers"; + +.batch-action { + float: right; + action-list { + padding-left: 0.3em; + } +} diff --git a/zun_ui/static/dashboard/container/containers/actions.module.js b/zun_ui/static/dashboard/container/containers/actions.module.js new file mode 100644 index 0000000..57f0bf2 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/actions.module.js @@ -0,0 +1,73 @@ +/** + * 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'; + + /** + * @ngdoc overview + * @ngname horizon.dashboard.container.containers.actions + * + * @description + * Provides all of the actions for containers. + */ + angular.module('horizon.dashboard.container.containers.actions', ['horizon.framework', 'horizon.dashboard.container']) + .run(registerContainerActions); + + registerContainerActions.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.dashboard.container.containers.create.service', + 'horizon.dashboard.container.containers.delete.service', + 'horizon.dashboard.container.containers.resourceType', + ]; + + function registerContainerActions( + registry, + gettext, + createContainerService, + deleteContainerService, + resourceType) + { + var containersResourceType = registry.getResourceType(resourceType); + containersResourceType.itemActions + .append({ + id: 'deleteContainerAction', + service: deleteContainerService, + template: { + type: 'delete', + text: gettext('Delete Container') + } + }); + + containersResourceType.batchActions + .append({ + id: 'createContainerAction', + service: createContainerService, + template: { + type: 'create', + text: gettext('Create Container') + } + }) + .append({ + id: 'batchDeleteContainerAction', + service: deleteContainerService, + template: { + type: 'delete-selected', + text: gettext('Delete Containers') + } + }); + } + +})(); diff --git a/zun_ui/static/dashboard/container/containers/containers.module.js b/zun_ui/static/dashboard/container/containers/containers.module.js new file mode 100644 index 0000000..7b112fb --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/containers.module.js @@ -0,0 +1,133 @@ +/** + * 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'; + + /** + * @ngdoc overview + * @name horizon.dashboard.container.containers + * @ngModule + * @description + * Provides all the services and widgets require to display the container + * panel + */ + angular + .module('horizon.dashboard.container.containers', [ + 'ngRoute', + 'horizon.dashboard.container.containers.actions', + 'horizon.dashboard.container.containers.details' + ]) + .constant('horizon.dashboard.container.containers.events', events()) + .constant('horizon.dashboard.container.containers.resourceType', 'OS::Zun::Container') + .run(run) + .config(config); + + /** + * @ngdoc constant + * @name horizon.dashboard.container.containers.events + * @description A list of events used by Container + */ + function events() { + return { + CREATE_SUCCESS: 'horizon.dashboard.container.containers.CREATE_SUCCESS', + DELETE_SUCCESS: 'horizon.dashboard.container.containers.DELETE_SUCCESS' + }; + } + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'horizon.app.core.openstack-service-api.zun', + 'horizon.dashboard.container.containers.basePath', + 'horizon.dashboard.container.containers.resourceType' + ]; + + function run(registry, zun, basePath, resourceType) { + registry.getResourceType(resourceType) + .setNames(gettext('Container'), gettext('Containers')) + // for detail summary view on table row + .setSummaryTemplateUrl(basePath + 'details/drawer.html') + // for table row items and detail summary view. + .setProperty('name', { + label: gettext('Name') + }) + .setProperty('id', { + label: gettext('ID') + }) + .setListFunction(listFunction) + .tableColumns + .append({ + id: 'name', + priority: 1, + sortDefault: true, + filters: ['noName'], + urlFunction: urlFunction + }) + .append({ + id: 'id', + priority: 2 + }); + // for magic-search + registry.getResourceType(resourceType).filterFacets + .append({ + 'label': gettext('Name'), + 'name': 'name', + 'singleton': true + }) + .append({ + 'label': gettext('ID'), + 'name': 'id', + 'singleton': true + }); + + function listFunction(params) { + return zun.getContainers(params).then(modifyResponse); + + function modifyResponse(response) { + return {data: {items: response.data.items.map(addTrackBy)}}; + + function addTrackBy(item) { + item.trackBy = item.id; + return item; + } + } + } + + function urlFunction(item) { + return 'project/ngdetails/OS::Zun::Container/' + item.id; + } + } + + config.$inject = [ + '$provide', + '$windowProvider', + '$routeProvider' + ]; + + /** + * @name config + * @param {Object} $provide + * @param {Object} $windowProvider + * @param {Object} $routeProvider + * @description Routes used by this module. + * @returns {undefined} Returns nothing + */ + function config($provide, $windowProvider, $routeProvider) { + var path = $windowProvider.$get().STATIC_URL + 'dashboard/container/containers/'; + $provide.constant('horizon.dashboard.container.containers.basePath', path); + $routeProvider.when('/project/container/containers/', { + templateUrl: path + 'panel.html' + }); + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/containers.module.spec.js b/zun_ui/static/dashboard/container/containers/containers.module.spec.js new file mode 100644 index 0000000..fb1e2b3 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/containers.module.spec.js @@ -0,0 +1,23 @@ +/** + * 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'; + + describe('horizon.dashboard.container.containers', function() { + it('should exist', function() { + expect(angular.module('horizon.dashboard.container.containers')).toBeDefined(); + }); + }); + +})(); diff --git a/zun_ui/static/dashboard/container/containers/containers.scss b/zun_ui/static/dashboard/container/containers/containers.scss new file mode 100644 index 0000000..e69de29 diff --git a/zun_ui/static/dashboard/container/containers/create/container-model.js b/zun_ui/static/dashboard/container/containers/create/container-model.js new file mode 100644 index 0000000..8f58908 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/create/container-model.js @@ -0,0 +1,67 @@ +/** + * 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('horizon.dashboard.container.containers') + .factory('horizon.dashboard.container.containers.containerModel', containerModel); + + containerModel.$inject = [ + 'horizon.app.core.openstack-service-api.zun' + ]; + + function containerModel(zun) { + var model = { + newContainerSpec: {}, + + // API methods + init: init, + createContainer: createContainer + }; + + function initNewContainerSpec() { + model.newContainerSpec = { + name: null, + }; + } + + function init() { + // Reset the new Container spec + initNewContainerSpec(); + } + + function createContainer() { + var finalSpec = angular.copy(model.newContainerSpec); + + cleanNullProperties(finalSpec); + + return zun.createContainer(finalSpec); + } + + function cleanNullProperties(finalSpec) { + // Initially clean fields that don't have any value. + // Not only "null", blank too. + for (var key in finalSpec) { + if (finalSpec.hasOwnProperty(key) && finalSpec[key] === null + || finalSpec[key] === "") { + delete finalSpec[key]; + } + } + } + + return model; + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/create/create-workflow.service.js b/zun_ui/static/dashboard/container/containers/create/create-workflow.service.js new file mode 100644 index 0000000..59b3bf0 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/create/create-workflow.service.js @@ -0,0 +1,50 @@ +/** + * 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('horizon.dashboard.container.containers') + .factory('horizon.dashboard.container.containers.workflow', containerWorkflow); + + containerWorkflow.$inject = [ + 'horizon.dashboard.container.basePath', + 'horizon.app.core.workflow.factory', + 'horizon.framework.util.i18n.gettext' + ]; + + function containerWorkflow(basePath, dashboardWorkflow, gettext) { + return dashboardWorkflow({ + title: gettext('Create Container'), + + steps: [ + { + title: gettext('Info'), + templateUrl: basePath + 'containers/create/info/info.html', + helpUrl: basePath + 'containers/create/info/info.help.html', + formName: 'containerInfoForm' + } + ], + + btnText: { + finish: gettext('Create') + }, + + btnIcon: { + finish: 'fa fa-check' + } + }); + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/create/create.service.js b/zun_ui/static/dashboard/container/containers/create/create.service.js new file mode 100644 index 0000000..b3e421f --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/create/create.service.js @@ -0,0 +1,100 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @name horizon.dashboard.container.containers.create.service + * @description Service for the container create modal + */ + angular + .module('horizon.dashboard.container.containers') + .factory('horizon.dashboard.container.containers.create.service', createService); + + createService.$inject = [ + '$location', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.modal.wizard-modal.service', + 'horizon.framework.widgets.toast.service', + 'horizon.dashboard.container.containers.containerModel', + 'horizon.dashboard.container.containers.events', + 'horizon.dashboard.container.containers.resourceType', + 'horizon.dashboard.container.containers.workflow' + ]; + + function createService( + $location, policy, actionResult, gettext, $qExtensions, wizardModalService, toast, model, events, resourceType, createWorkflow + ) { + + var scope; + var message = { + success: gettext('Container %s was successfully created.') + }; + + var service = { + initScope: initScope, + perform: perform, + allowed: allowed + }; + + return service; + + ////////////// + + function initScope($scope) { + scope = $scope; + scope.workflow = createWorkflow; + scope.model = model; + scope.$on('$destroy', function() { + }); + } + + function perform(selected) { + scope.model.init(); + // for creation according to selected item + scope.selected = selected; + return wizardModalService.modal({ + scope: scope, + workflow: createWorkflow, + submit: submit + }).result; + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + //return policy.ifAllowed({ rules: [['container', 'add_container']] }); + } + + function submit(){ + return model.createContainer().then(success); + } + + function success(response) { + response.data.id = response.data.uuid; + toast.add('success', interpolate(message.success, [response.data.id])); + var result = actionResult.getActionResult() + .created(resourceType, response.data.id); + if(result.result.failed.length == 0 && result.result.created.length > 0){ + $location.path('/project/container/containers'); + }else{ + return result.result; + } + } + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/create/info/container.info.controller.js b/zun_ui/static/dashboard/container/containers/create/info/container.info.controller.js new file mode 100644 index 0000000..3a35265 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/create/info/container.info.controller.js @@ -0,0 +1,40 @@ +/** + * 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'; + + /** + * @ngdoc controller + * @name createContainerInfoController + * @ngController + * @description + * Controller for the container info step in create workflow + */ + angular + .module('horizon.dashboard.container.containers') + .controller('createContainerInfoController', createContainerInfoController); + + createContainerInfoController.$inject = [ + '$q', + '$scope', + 'horizon.dashboard.container.basePath', + 'horizon.app.core.openstack-service-api.zun', + 'horizon.framework.util.i18n.gettext' + ]; + + function createContainerInfoController($q, $scope, basePath, zun, gettext) { + var ctrl = this; + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/create/info/info.help.html b/zun_ui/static/dashboard/container/containers/create/info/info.help.html new file mode 100644 index 0000000..f0eac99 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/create/info/info.help.html @@ -0,0 +1,4 @@ +
+
Container Name
+
An arbitrary human-readable name
+
diff --git a/zun_ui/static/dashboard/container/containers/create/info/info.html b/zun_ui/static/dashboard/container/containers/create/info/info.html new file mode 100644 index 0000000..b4ac03d --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/create/info/info.html @@ -0,0 +1,12 @@ +
+
+
+
+ + +
+
+
+
diff --git a/zun_ui/static/dashboard/container/containers/delete/delete.service.js b/zun_ui/static/dashboard/container/containers/delete/delete.service.js new file mode 100644 index 0000000..b53640b --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/delete/delete.service.js @@ -0,0 +1,151 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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('horizon.dashboard.container.containers') + .factory('horizon.dashboard.container.containers.delete.service', deleteService); + + deleteService.$inject = [ + '$location', + '$q', + 'horizon.app.core.openstack-service-api.zun', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.modal.deleteModalService', + 'horizon.framework.widgets.toast.service', + 'horizon.dashboard.container.containers.resourceType', + 'horizon.dashboard.container.containers.events' + ]; + + /** + * @ngDoc factory + * @name horizon.dashboard.container.containers.delete.service + * @Description + * Brings up the delete containers confirmation modal dialog. + * On submit, delete selected resources. + * On cancel, do nothing. + */ + function deleteService( + $location, $q, zun, policy, actionResult, gettext, $qExtensions, deleteModal, toast, resourceType, events + ) { + var scope; + var context = { + labels: null, + deleteEntity: deleteEntity, + successEvent: events.DELETE_SUCCESS + }; + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + var notAllowedMessage = gettext("You are not allowed to delete containers: %s"); + + return service; + + ////////////// + + // include this function in your service + // if you plan to emit events to the parent controller + function initScope($scope) { + scope = $scope; + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + // delete selected resource objects + function perform(selected) { + var selected = angular.isArray(selected) ? selected : [selected]; + context.labels = labelize(selected.length); + return $qExtensions.allSettled(selected.map(checkPermission)).then(afterCheck); + } + + function labelize(count){ + return { + title: ngettext('Confirm Delete Container', + 'Confirm Delete Containers', count), + /* eslint-disable max-len */ + message: ngettext('You have selected "%s". Please confirm your selection. Deleted container is not recoverable.', + 'You have selected "%s". Please confirm your selection. Deleted containers are not recoverable.', count), + /* eslint-enable max-len */ + submit: ngettext('Delete Container', + 'Delete Containers', count), + success: ngettext('Deleted Container: %s.', + 'Deleted Containers: %s.', count), + error: ngettext('Unable to delete Container: %s.', + 'Unable to delete Containers: %s.', count) + }; + } + + // for batch delete + function checkPermission(selected) { + return {promise: allowed(selected), context: selected}; + } + + // for batch delete + function afterCheck(result){ + var outcome = $q.reject(); // Reject the promise by default + if (result.fail.length > 0) { + toast.add('error', getMessage(notAllowedMessage, result.fail)); + outcome = $q.reject(result.fail); + } + if (result.pass.length > 0) { + outcome = deleteModal.open(scope, result.pass.map(getEntity), context).then(createResult); + } + return outcome; + } + + function createResult(deleteModalResult) { + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + var result = actionResult.getActionResult(); + deleteModalResult.pass.forEach(function markDeleted(item) { + result.deleted(resourceType, getEntity(item).id); + }); + deleteModalResult.fail.forEach(function markFailed(item) { + result.failed(resourceType, getEntity(item).id); + }); + if(result.result.failed.length == 0 && result.result.deleted.length > 0){ + $location.path('/project/container/containers'); + }else{ + return result.result; + } + } + + function getMessage(message, entities) { + return interpolate(message, [entities.map(getName).join(", ")]); + } + + function getName(result) { + return getEntity(result).name; + } + + // for batch delete + function getEntity(result) { + return result.context; + } + + // call delete REST API + function deleteEntity(id){ + return zun.deleteContainer(id, true); + } + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/details/details.module.js b/zun_ui/static/dashboard/container/containers/details/details.module.js new file mode 100644 index 0000000..8060aa4 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/details/details.module.js @@ -0,0 +1,55 @@ +/** + * 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'; + + /** + * @ngdoc overview + * @ngname horizon.dashboard.container.containers.details + * + * @description + * Provides details features for container. + */ + angular.module('horizon.dashboard.container.containers.details', + ['horizon.framework.conf', 'horizon.app.core']) + .run(registerDetails); + + registerDetails.$inject = [ + 'horizon.dashboard.container.containers.basePath', + 'horizon.dashboard.container.containers.resourceType', + 'horizon.app.core.openstack-service-api.zun', + 'horizon.framework.conf.resource-type-registry.service' + ]; + + function registerDetails( + basePath, + resourceType, + zun, + registry + ) { + registry.getResourceType(resourceType) + .setLoadFunction(loadFunction) + .detailsViews.append({ + id: 'containerDetailsOverview', + name: gettext('Overview'), + template: basePath + 'details/overview.html' + }); + + function loadFunction(identifier) { + return zun.getContainer(identifier); + } + } + +})(); diff --git a/zun_ui/static/dashboard/container/containers/details/drawer.controller.js b/zun_ui/static/dashboard/container/containers/details/drawer.controller.js new file mode 100644 index 0000000..578f609 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/details/drawer.controller.js @@ -0,0 +1,39 @@ +/** + * 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'; + + /** + * @ngdoc controller + * @name horizon.dashboard.container.containers.DrawerController + * @description + * This is the controller for the drawer (summary) view. + * Its primary purpose is to provide the metadata definitions to + * the template via the ctrl.metadataDefs member. + */ + angular + .module('horizon.dashboard.container.containers') + .controller('horizon.dashboard.container.containers.DrawerController', controller); + + controller.$inject = [ + 'horizon.app.core.openstack-service-api.zun', + 'horizon.dashboard.container.containers.resourceType' + ]; + + function controller(zun, resourceType) { + var ctrl = this; + } + +})(); diff --git a/zun_ui/static/dashboard/container/containers/details/drawer.html b/zun_ui/static/dashboard/container/containers/details/drawer.html new file mode 100644 index 0000000..e5bb69a --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/details/drawer.html @@ -0,0 +1,16 @@ +
+
+ +
+
ID
+
{$ item.id $}
+
+
+
+
+
+
Name
+
{$ item.name $}
+
+
+
diff --git a/zun_ui/static/dashboard/container/containers/details/overview.controller.js b/zun_ui/static/dashboard/container/containers/details/overview.controller.js new file mode 100644 index 0000000..6583a5a --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/details/overview.controller.js @@ -0,0 +1,43 @@ +/* + * 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('horizon.dashboard.container.containers') + .controller('horizon.dashboard.container.containers.OverviewController', controller); + + controller.$inject = [ + '$scope', + 'horizon.dashboard.container.containers.resourceType', + 'horizon.dashboard.container.containers.events', + 'horizon.framework.conf.resource-type-registry.service' + ]; + + function controller( + $scope, + resourceType, + events, + registry + ) { + var ctrl = this; + ctrl.container = {}; + + $scope.context.loadPromise.then(onGetContainer); + + function onGetContainer(container) { + ctrl.container = container.data; + } + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/details/overview.html b/zun_ui/static/dashboard/container/containers/details/overview.html new file mode 100644 index 0000000..8307482 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/details/overview.html @@ -0,0 +1,26 @@ +
+
+
+

Info

+
+
+
Name
+
{$ ctrl.container.name|noName $}
+
+
+
+
+
+

Record Properties

+
+
+
Created
+
{$ ctrl.container.created_at | date:'short' $}
+
Updated
+
{$ ctrl.container.updated_at | date:'short' $}
+
ID
+
{$ ctrl.container.id $}
+
+
+
+
diff --git a/zun_ui/static/dashboard/container/containers/panel.html b/zun_ui/static/dashboard/container/containers/panel.html new file mode 100644 index 0000000..83e3215 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/panel.html @@ -0,0 +1,4 @@ + + + diff --git a/zun_ui/static/dashboard/container/zun.service.js b/zun_ui/static/dashboard/container/zun.service.js new file mode 100644 index 0000000..1be1046 --- /dev/null +++ b/zun_ui/static/dashboard/container/zun.service.js @@ -0,0 +1,79 @@ +/** + * 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('horizon.app.core.openstack-service-api') + .factory('horizon.app.core.openstack-service-api.zun', ZunAPI); + + ZunAPI.$inject = [ + 'horizon.framework.util.http.service', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.util.i18n.gettext' + ]; + + function ZunAPI(apiService, toastService, gettext) { + var service = { + createContainer: createContainer, + getContainer: getContainer, + getContainers: getContainers, + deleteContainer: deleteContainer, + deleteContainers: deleteContainers, + }; + + return service; + + /////////////// + // Containers // + /////////////// + + function createContainer(params) { + return apiService.post('/api/zun/containers/', params) + .error(function() { + toastService.add('error', gettext('Unable to create Container')); + }); + } + + function getContainer(id) { + return apiService.get('/api/zun/containers/' + id) + .error(function() { + toastService.add('error', gettext('Unable to retrieve the Container.')); + }); + } + + function getContainers() { + return apiService.get('/api/zun/containers/') + .error(function() { + toastService.add('error', gettext('Unable to retrieve the Containers.')); + }); + } + + function deleteContainer(id, suppressError) { + var promise = apiService.delete('/api/zun/containers/', [id]); + return suppressError ? promise : promise.error(function() { + var msg = gettext('Unable to delete the Container with id: %(id)s'); + toastService.add('error', interpolate(msg, { id: id }, true)); + }); + } + + // FIXME(shu-mutou): Unused for batch-delete in Horizon framework in Feb, 2016. + function deleteContainers(ids) { + return apiService.delete('/api/zun/containers/', ids) + .error(function() { + toastService.add('error', gettext('Unable to delete the Containers.')); + }) + } + } +}()); diff --git a/zun_ui/test/__init__.py b/zun_ui/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zun_ui/version.py b/zun_ui/version.py new file mode 100644 index 0000000..a7a039d --- /dev/null +++ b/zun_ui/version.py @@ -0,0 +1,3 @@ +import pbr.version + +version_info = pbr.version.VersionInfo('zun-ui')