Watcher Dashboard - Initial commit
This patchset puts in open source the code initially made internally. This dashboard follows the new plugin architecture that Horizon introduced. DocImpact Implements: blueprint watcher-ui Change-Id: Ic8736b45ae5e111a817f64568ed0534c1521c84d
This commit is contained in:
parent
6a23bf3c79
commit
b4fb6f7573
59
.gitignore
vendored
Normal file
59
.gitignore
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
.eggs
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
nosetests.xml
|
||||
.testrepository
|
||||
.venv
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Complexity
|
||||
output/*.html
|
||||
output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
# Editors
|
||||
*~
|
||||
.*.swp
|
||||
.*sw?
|
||||
|
||||
|
||||
# Horizon related
|
||||
*.lock
|
||||
watcher_dashboard/test/.secret_key_store
|
60
HACKING.rst
Normal file
60
HACKING.rst
Normal file
@ -0,0 +1,60 @@
|
||||
Contributing
|
||||
============
|
||||
|
||||
The code repository is located at `OpenStack <https://github.com/openstack>`__.
|
||||
Please go there if you want to check it out:
|
||||
|
||||
git clone https://github.com/openstack/watcher-dashboard.git
|
||||
|
||||
The list of bugs and blueprints is on Launchpad:
|
||||
|
||||
`<https://launchpad.net/watcher-dashboard>`__
|
||||
|
||||
We use OpenStack's Gerrit for the code contributions:
|
||||
|
||||
`<https://review.openstack.org/#/q/status:open+project:openstack/watcher-dashboard,n,z>`__
|
||||
|
||||
and we follow the `OpenStack Gerrit Workflow <http://docs.openstack.org/infra/manual/developers.html#development-workflow>`__.
|
||||
|
||||
If you're interested in the code, here are some key places to start:
|
||||
|
||||
* `watcher_dashboard/api.py <https://github.com/openstack/watcher-dashboard/blob/master/watcher_dashboard/api.py>`_
|
||||
- This file contains all the API calls made to the Watcher API
|
||||
(through python-watcherclient).
|
||||
* `watcher_dashboard/infra_optim <https://github.com/openstack/watcher-dashboard/tree/master/watcher_dashboard/infra_optim>`_
|
||||
- The Watcher Dashboard code is contained within this directory.
|
||||
|
||||
Running tests
|
||||
=============
|
||||
|
||||
There are several ways to run tests for watcher-dashboard.
|
||||
|
||||
Using ``tox``:
|
||||
|
||||
This is the easiest way to run tests. When run, tox installs dependencies,
|
||||
prepares the virtual python environment, then runs test commands. The gate
|
||||
tests in gerrit usually also use tox to run tests. For avaliable tox
|
||||
environments, see ``tox.ini``.
|
||||
|
||||
By running ``run_tests.sh``:
|
||||
|
||||
Tests can also be run using the ``run_tests.sh`` script, to see available
|
||||
options, run it with the ``--help`` option. It handles preparing the
|
||||
virtual environment and executing tests, but in contrast with tox, it does
|
||||
not install all dependencies, e.g. ``jshint`` must be installed before
|
||||
running the jshint testcase.
|
||||
|
||||
Manual tests:
|
||||
|
||||
To manually check watcher-dashboard, it is possible to run a development server
|
||||
for watcher-dashboard by running ``run_tests.sh --runserver``.
|
||||
|
||||
To run the server with the settings used by the test environment:
|
||||
``run_tests.sh --runserver 0.0.0.0:8000``
|
||||
|
||||
OpenStack Style Commandments
|
||||
============================
|
||||
|
||||
- Step 1: Read http://www.python.org/dev/peps/pep-0008/
|
||||
- Step 2: Read http://www.python.org/dev/peps/pep-0008/ again
|
||||
- Step 3: Read https://github.com/openstack-dev/hacking/blob/master/HACKING.rst
|
176
LICENSE
Normal file
176
LICENSE
Normal file
@ -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.
|
||||
|
7
MANIFEST.in
Normal file
7
MANIFEST.in
Normal file
@ -0,0 +1,7 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
||||
recursive-include watcher_dashboard/templates *
|
126
README.rst
Normal file
126
README.rst
Normal file
@ -0,0 +1,126 @@
|
||||
OpenStack Dashboard plugin for Watcher project
|
||||
==============================================
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
|
||||
First off, create a virtual environment and install the Horizon dependencies::
|
||||
|
||||
$ git clone https://github.com/openstack/horizon
|
||||
$ cd horizon
|
||||
$ python tools/install_venv.py
|
||||
|
||||
We will refer to the folder you are now in as ``<HORIZON_DIR>``.
|
||||
If you want more details on how to install Horizon, you can have a look at the
|
||||
`Horizon documentation`_, especially their `quickstart tutorial`_.
|
||||
|
||||
Then, you need to install Watcher Dashboard on the server running Horizon.
|
||||
To do so, you can issue the following commands::
|
||||
|
||||
$ git clone git://git.openstack.org/openstack/watcher-dashboard
|
||||
$ cd watcher-dashboard
|
||||
$ pip install -e .
|
||||
|
||||
We will refer to the folder you are now in as ``<DASHBOARD_DIR>``.
|
||||
|
||||
The next step is now to register the Watcher Dashboard plugins against your
|
||||
Horizon. To do so, you can execute the ``tools/register_plugin.sh``::
|
||||
|
||||
$ cd <DASHBOARD_DIR>
|
||||
$ ./tools/register_plugin.sh . <HORIZON_DIR>
|
||||
|
||||
This script will then create the needed symlinks within Horizon so that it can
|
||||
load the Watcher plugin when it starts.
|
||||
|
||||
If you wish to have Horizon running being an Apache server, do not forget to
|
||||
start the service via the following command::
|
||||
|
||||
$ sudo service apache2 restart
|
||||
|
||||
For more details on how to configure Horizon for a production environment, you
|
||||
can refer to their online `installation guide`_.
|
||||
|
||||
.. _Horizon documentation: http://docs.openstack.org/developer/horizon
|
||||
.. _quickstart tutorial: http://docs.openstack.org/developer/horizon/quickstart.html
|
||||
.. _installation guide: http://docs.openstack.org/developer/horizon/topics/install.html
|
||||
|
||||
|
||||
DevStack setup
|
||||
--------------
|
||||
|
||||
Add the following to your DevStack ``local.conf`` file
|
||||
|
||||
::
|
||||
|
||||
enable_plugin watcher-dashboard git://git.openstack.org/openstack/watcher-dashboard
|
||||
|
||||
|
||||
Unit testing
|
||||
------------
|
||||
|
||||
First of all, you have to create an environment to run your tests in. This step
|
||||
is actually part of the ``run_tests.sh`` script which creates and maintains a
|
||||
clean virtual environment.
|
||||
|
||||
Here below is the basic command to run Watcher Dashboard tests::
|
||||
|
||||
$ ./run_tests.sh
|
||||
|
||||
The first time you will issue the command above, you will be asked if you want
|
||||
to create a virtual environment. So unless you have installed everything
|
||||
manually (in which case you should use the ``-N`` flag), you need to accept
|
||||
|
||||
|
||||
Integration testing
|
||||
-------------------
|
||||
|
||||
Before being able to run integration tests, you need to have a Horizon server
|
||||
running with Watcher Dashboard plugin configured. To do so, you can run a test
|
||||
server using the following command::
|
||||
|
||||
$ ./run_tests.sh --runserver 0.0.0.0:8000
|
||||
|
||||
By default, integration tests expect to find a running Horizon server at
|
||||
``http://localhost:8000/`` but this can be customized by editing the
|
||||
``watcher_dashboard/test/integration_tests/horizon.conf`` configuration file.
|
||||
Likewise, this Horizon will be looking, by default, for a Keystone backend at
|
||||
``http://localhost:5000/v2.0``. So in order to customize its location, you will
|
||||
have to edit ``watcher_dashboard/test/settings.py`` by updating the
|
||||
``OPENSTACK_KEYSTONE_URL`` variable.
|
||||
|
||||
To run integration tests::
|
||||
|
||||
$ ./run_tests.sh --integration
|
||||
|
||||
You can use PhantomJS as a headless browser to execute your integration tests.
|
||||
On an Ubuntu distribution you can install it via the following command::
|
||||
|
||||
$ sudo apt-get install phantomjs
|
||||
|
||||
Then you can run your integration tests like this::
|
||||
|
||||
$ ./run_tests.sh --integration --selenium-headless
|
||||
|
||||
Please note that these commands are also available via ``tox``.
|
||||
|
||||
.. note::
|
||||
|
||||
As of the Mitaka release, the dashboard for watcher is now maintained
|
||||
outside of the Horizon codebase, in this repository.
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
Watcher project: https://git.openstack.org/openstack/watcher
|
||||
|
||||
Watcher at wiki.openstack.org: https://wiki.openstack.org/wiki/Watcher
|
||||
|
||||
Launchpad project: https://launchpad.net/watcher
|
||||
|
||||
Join us on IRC (Internet Relay Chat)::
|
||||
|
||||
Network: Freenode (irc.freenode.net/watcher)
|
||||
Channel: #openstack-watcher
|
||||
|
||||
Or send an email to openstack-dev@lists.openstack.org.
|
5
babel-django.cfg
Normal file
5
babel-django.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
[extractors]
|
||||
django = django_babel.extract:extract_django
|
||||
|
||||
[python: watcher_dashboard/**.py]
|
||||
[django: watcher_dashboard/**/templates/**.html]
|
14
babel-djangojs.cfg
Normal file
14
babel-djangojs.cfg
Normal file
@ -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: watcher_dashboard/**.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: watcher_dashboard/**/static/**.html]
|
54
devstack/plugin.sh
Normal file
54
devstack/plugin.sh
Normal file
@ -0,0 +1,54 @@
|
||||
# plugin.sh - DevStack plugin.sh dispatch script watcher-dashboard
|
||||
|
||||
WATCHER_DASHBOARD_DIR=$(cd $(dirname $BASH_SOURCE)/.. && pwd)
|
||||
|
||||
function install_watcher_dashboard {
|
||||
setup_develop ${WATCHER_DASHBOARD_DIR}
|
||||
}
|
||||
|
||||
function configure_watcher_dashboard {
|
||||
cp -a ${WATCHER_DASHBOARD_DIR}/watcher_dashboard/enabled/* ${DEST}/horizon/openstack_dashboard/local/enabled/
|
||||
}
|
||||
|
||||
function init_watcher_dashboard {
|
||||
# Setup alias for django-admin which could be different depending on distro
|
||||
python ${DEST}/horizon/manage.py collectstatic --noinput
|
||||
python ${DEST}/horizon//manage.py compress --force
|
||||
}
|
||||
|
||||
# check for service enabled
|
||||
if is_service_enabled watcher-dashboard; 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 Watcher Dashboard"
|
||||
install_watcher_dashboard
|
||||
|
||||
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
|
||||
# Configure after the other layer 1 and 2 services have been configured
|
||||
echo_summary "Configurng Watcher Dashboard"
|
||||
configure_watcher_dashboard
|
||||
init_watcher_dashboard
|
||||
|
||||
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
|
||||
# no-op
|
||||
:
|
||||
fi
|
||||
|
||||
if [[ "$1" == "unstack" ]]; then
|
||||
rm -f ${DEST}/horizon/openstack_dashboard/local/enabled/_310*
|
||||
|
||||
fi
|
||||
|
||||
if [[ "$1" == "clean" ]]; then
|
||||
# Remove state and transient data
|
||||
# Remember clean.sh first calls unstack.sh
|
||||
# no-op
|
||||
:
|
||||
fi
|
||||
fi
|
2
devstack/settings
Normal file
2
devstack/settings
Normal file
@ -0,0 +1,2 @@
|
||||
# settings file for watcher-dashboard plugin
|
||||
enable_service watcher-dashboard
|
104
doc/source/conf.py
Executable file
104
doc/source/conf.py
Executable file
@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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.
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# 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.viewcode',
|
||||
'oslosphinx',
|
||||
]
|
||||
|
||||
wsme_protocols = ['restjson']
|
||||
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Watcher'
|
||||
copyright = u'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.
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# release =
|
||||
# The short X.Y version.
|
||||
# version = watcher_version.version_info.version_string()
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
modindex_common_prefix = ['watcher.']
|
||||
|
||||
exclude_patterns = [
|
||||
# The man directory includes some snippet files that are included
|
||||
# in other documents during the build but that should not be
|
||||
# included in the toctree themselves, so tell Sphinx to ignore
|
||||
# them when scanning for input files.
|
||||
'man/footer.rst',
|
||||
'man/general-options.rst',
|
||||
]
|
||||
|
||||
# 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
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for man page output --------------------------------------------
|
||||
|
||||
# Grouping the document tree for man pages.
|
||||
# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual'
|
||||
|
||||
man_pages = []
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
# html_theme_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
# html_static_path = ['static']
|
||||
html_theme_options = {'incubating': True}
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
# intersphinx_mapping = {'http://docs.python.org/': None}
|
1
doc/source/deploy/installation.rst
Normal file
1
doc/source/deploy/installation.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../../../README.rst
|
53
doc/source/index.rst
Normal file
53
doc/source/index.rst
Normal file
@ -0,0 +1,53 @@
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
==========================================
|
||||
Welcome to Watcher Dashboard documentation
|
||||
==========================================
|
||||
|
||||
OpenStack Watcher provides a flexible and scalable resource optimization
|
||||
service for multi-tenant OpenStack-based clouds.
|
||||
Watcher provides a complete optimization loop—including everything from a
|
||||
metrics receiver, complex event processor and profiler, optimization processor
|
||||
and an action plan applier. This provides a robust framework to realize a wide
|
||||
range of cloud optimization goals, including the reduction of data center
|
||||
operating costs, increased system performance via intelligent virtual machine
|
||||
migration, increased energy efficiency—and more!
|
||||
|
||||
Watcher project consists of several source code repositories:
|
||||
|
||||
* `watcher`_ - is the main repository. It contains code for Watcher API server,
|
||||
Watcher Decision Engine and Watcher Applier.
|
||||
* `python-watcherclient`_ - Client library and CLI client for Watcher.
|
||||
* `watcher-dashboard`_ - Watcher Horizon plugin.
|
||||
|
||||
The documentation provided here is continually kept up-to-date based
|
||||
on the latest code, and may not represent the state of the project at any
|
||||
specific prior release.
|
||||
|
||||
.. _watcher: https://git.openstack.org/cgit/openstack/watcher/
|
||||
.. _python-watcherclient: https://git.openstack.org/cgit/openstack/python-watcherclient/
|
||||
.. _watcher-dashboard: https://git.openstack.org/cgit/openstack/watcher-dashboard/
|
||||
|
||||
|
||||
Developer Guide
|
||||
===============
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
deploy/installation
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
23
manage.py
Executable file
23
manage.py
Executable file
@ -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
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
|
||||
"watcher_dashboard.test.settings")
|
||||
execute_from_command_line(sys.argv)
|
13
requirements.txt
Normal file
13
requirements.txt
Normal file
@ -0,0 +1,13 @@
|
||||
# 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
|
||||
|
||||
pbr>=1.8
|
||||
# Horizon Core Requirements
|
||||
Django>=1.8,<1.9 # BSD
|
||||
django_compressor>=1.4 # MIT
|
||||
django_openstack_auth>=2.0.0 # Apache-2.0
|
||||
python-keystoneclient>=1.6.0,!=1.8.0,!=2.1.0 # Apache-2.0
|
||||
pytz>=2013.6 # MIT
|
||||
|
||||
# Watcher-specific requirements
|
||||
python-watcherclient
|
617
run_tests.sh
Executable file
617
run_tests.sh
Executable file
@ -0,0 +1,617 @@
|
||||
#!/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 [<basecommit>]"
|
||||
echo " Just run PEP8 and HACKING compliance check"
|
||||
echo " on files changed since HEAD~1 (or <basecommit>)"
|
||||
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 " --selenium-phantomjs Run Selenium tests using phantomjs (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="watcher_dashboard"
|
||||
|
||||
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
|
||||
selenium_phantomjs=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;;
|
||||
--selenium-phantomjs) selenium_phantomjs=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=watcher_dashboard.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=watcher_dashboard.test.settings ${command_wrapper} flake8 --diff
|
||||
exit
|
||||
}
|
||||
|
||||
function run_sphinx {
|
||||
echo "Building sphinx..."
|
||||
DJANGO_SETTINGS_MODULE=watcher_dashboard.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=watcher_dashboard/test/integration_tests/"
|
||||
fi
|
||||
|
||||
if [ $selenium_headless -eq 1 ]; then
|
||||
export SELENIUM_HEADLESS=1
|
||||
fi
|
||||
|
||||
if [ $selenium_phantomjs -eq 1 ]; then
|
||||
export SELENIUM_PHANTOMJS=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 Horizon application tests"
|
||||
export NOSE_XUNIT_FILE=horizon/nosetests.xml
|
||||
if [ "$NOSE_WITH_HTML_OUTPUT" = '1' ]; then
|
||||
export NOSE_HTML_OUT_FILE='horizon_nose_results.html'
|
||||
fi
|
||||
if [ $with_coverage -eq 1 ]; then
|
||||
${command_wrapper} python -m coverage.__main__ erase
|
||||
coverage_run="python -m coverage.__main__ run -p"
|
||||
fi
|
||||
${command_wrapper} ${coverage_run} $root/manage.py test horizon --settings=horizon.test.settings $testopts
|
||||
# get results of the Horizon tests
|
||||
HORIZON_RESULT=$?
|
||||
|
||||
echo "Running openstack_dashboard tests"
|
||||
export NOSE_XUNIT_FILE=openstack_dashboard/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 openstack_dashboard --settings=watcher_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=$(($HORIZON_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
|
||||
|
||||
if [ $selenium_phantomjs -eq 1 ]; then
|
||||
export SELENIUM_PHANTOMJS=1
|
||||
fi
|
||||
|
||||
echo "Running Watcher Horizon integration tests..."
|
||||
if [ -z "$testargs" ]; then
|
||||
${command_wrapper} nosetests watcher_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 "horizon: "
|
||||
cd horizon
|
||||
babel_extract django
|
||||
HORIZON_PY_RESULT=$?
|
||||
|
||||
echo -n "horizon javascript: "
|
||||
babel_extract djangojs
|
||||
HORIZON_JS_RESULT=$?
|
||||
|
||||
echo -n "openstack_dashboard: "
|
||||
cd ../openstack_dashboard
|
||||
babel_extract django
|
||||
DASHBOARD_RESULT=$?
|
||||
|
||||
echo -n "openstack_dashboard javascript: "
|
||||
babel_extract djangojs
|
||||
DASHBOARD_JS_RESULT=$?
|
||||
|
||||
cd ..
|
||||
if [ $check_only -eq 1 ]; then
|
||||
git checkout -- horizon/locale/django*.pot
|
||||
git checkout -- openstack_dashboard/locale/django*.pot
|
||||
fi
|
||||
|
||||
exit $(($HORIZON_PY_RESULT || $HORIZON_JS_RESULT || $DASHBOARD_RESULT || $DASHBOARD_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
|
40
setup.cfg
Normal file
40
setup.cfg
Normal file
@ -0,0 +1,40 @@
|
||||
[metadata]
|
||||
name = watcher-dashboard
|
||||
summary = Watcher Management Dashboard
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://www.openstack.org/
|
||||
classifier =
|
||||
Development Status :: 5 - Production/Stable
|
||||
Environment :: OpenStack
|
||||
Framework :: Django
|
||||
Intended Audience :: Developers
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: OS Independent
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 2.6
|
||||
Topic :: Internet :: WWW/HTTP
|
||||
|
||||
[global]
|
||||
setup-hooks =
|
||||
pbr.hooks.setup_hook
|
||||
|
||||
[files]
|
||||
packages =
|
||||
watcher_dashboard
|
||||
|
||||
[build_sphinx]
|
||||
all_files = 1
|
||||
build-dir = doc/build
|
||||
source-dir = doc/source
|
||||
|
||||
[nosetests]
|
||||
verbosity=2
|
||||
detailed-errors=1
|
30
setup.py
Executable file
30
setup.py
Executable file
@ -0,0 +1,30 @@
|
||||
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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)
|
26
test-requirements.txt
Normal file
26
test-requirements.txt
Normal file
@ -0,0 +1,26 @@
|
||||
# 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.
|
||||
# Hacking already pins down pep8, pyflakes and flake8
|
||||
|
||||
-e git://github.com/openstack/horizon.git#egg=horizon
|
||||
|
||||
hacking>=0.10.2,<0.11 # Apache-2.0
|
||||
coverage>=3.6 # Apache-2.0
|
||||
ddt>=1.0.1 # MIT
|
||||
django-nose>=1.2 # BSD
|
||||
discover # BSD
|
||||
mock>=1.2 # BSD
|
||||
mox3>=0.7.0 # Apache-2.0
|
||||
nose-exclude # LGPL
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
selenium!=2.49,!=2.50 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
# This also needs xvfb library installed on your OS
|
||||
xvfbwrapper>=0.1.3,!=0.2.8 #license: MIT
|
||||
|
||||
# Doc requirements
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
71
tools/install_venv.py
Normal file
71
tools/install_venv.py
Normal file
@ -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 <your command>
|
||||
|
||||
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)
|
172
tools/install_venv_common.py
Normal file
172
tools/install_venv_common.py
Normal file
@ -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()
|
16
tools/register_plugin.sh
Executable file
16
tools/register_plugin.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
src_path=`cd "$1"; pwd`
|
||||
dest_path=`cd "$2"; pwd`
|
||||
# echo "$src_path --> $dest_path"
|
||||
|
||||
for filepath in $src_path/watcher_dashboard/enabled/*.py; do
|
||||
filename=$(basename $filepath)
|
||||
if [ $filename != "__init__.py" ]; then
|
||||
echo $filepath
|
||||
src_filepath="`cd "$(dirname $filepath)"; pwd`/$filename"
|
||||
dest_filepath="$dest_path/openstack_dashboard/local/enabled/$filename"
|
||||
echo "$src_filepath --> $dest_filepath"
|
||||
ln -s $src_filepath $dest_filepath
|
||||
fi
|
||||
done
|
13
tools/with_venv.sh
Executable file
13
tools/with_venv.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/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}}
|
||||
HORIZON_DIR=${TOOLS%/tools}
|
||||
|
||||
# This horrible mangling of the PYTHONPATH is required to get the
|
||||
# babel-angular-gettext extractor to work. To fix this the extractor needs to
|
||||
# be packaged on pypi and added to global requirements. That work is in progress.
|
||||
export PYTHONPATH="$HORIZON_DIR"
|
||||
source ${VENV}/bin/activate && "$@"
|
58
tox.ini
Normal file
58
tox.ini
Normal file
@ -0,0 +1,58 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py34,py27,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
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
|
||||
DJANGO_SETTINGS_MODULE=watcher_dashboard.test.settings
|
||||
# Note the hash seed is set to 0 until horizon can be tested with a
|
||||
# random hash seed successfully.
|
||||
PYTHONHASHSEED=0
|
||||
whitelist_externals = /bin/bash
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
python manage.py test --settings=watcher_dashboard.test.settings \
|
||||
--exclude-dir=watcher_dashboard/test/integration_tests \
|
||||
watcher_dashboard.test
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
# Django-1.8 is LTS
|
||||
[testenv:py27dj18]
|
||||
basepython = python2.7
|
||||
commands = pip install django>=1.8,<1.9
|
||||
/bin/bash run_tests.sh -N --no-pep8 {posargs}
|
||||
|
||||
[testenv:py27integration]
|
||||
basepython = python2.7
|
||||
commands = /bin/bash run_tests.sh -N --integration --selenium-headless {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
|
||||
[testenv:debug]
|
||||
commands = oslo_debug_helper {posargs}
|
||||
|
||||
[flake8]
|
||||
show-source = True
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
# H405 multi line docstring summary not separated with an empty line
|
||||
ignore = E123,E125,H405
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,.ropeproject,tools
|
0
watcher_dashboard/__init__.py
Normal file
0
watcher_dashboard/__init__.py
Normal file
0
watcher_dashboard/api/__init__.py
Normal file
0
watcher_dashboard/api/__init__.py
Normal file
430
watcher_dashboard/api/watcher.py
Normal file
430
watcher_dashboard/api/watcher.py
Normal file
@ -0,0 +1,430 @@
|
||||
# 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 unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from openstack_dashboard.api import base
|
||||
from watcherclient import client as wc
|
||||
|
||||
from watcher_dashboard.utils import errors as errors_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
WATCHER_SERVICE = 'infra-optim'
|
||||
|
||||
|
||||
def watcherclient(request, password=None):
|
||||
api_version = "1"
|
||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||
ca_file = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
|
||||
endpoint = base.url_for(request, WATCHER_SERVICE)
|
||||
|
||||
LOG.debug('watcherclient connection created using token "%s" and url "%s"'
|
||||
% (request.user.token.id, endpoint))
|
||||
|
||||
client = wc.get_client(
|
||||
api_version,
|
||||
watcher_url=endpoint,
|
||||
insecure=insecure,
|
||||
ca_file=ca_file,
|
||||
username=request.user.username,
|
||||
password=password,
|
||||
os_auth_token=request.user.token.id
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
class Audit(base.APIResourceWrapper):
|
||||
_attrs = ('uuid', 'created_at', 'modified_at', 'deleted_at',
|
||||
'deadline', 'state', 'type', 'audit_template_uuid',
|
||||
'audit_template_name')
|
||||
|
||||
def __init__(self, apiresource, request=None):
|
||||
super(Audit, self).__init__(apiresource)
|
||||
self._request = request
|
||||
|
||||
@classmethod
|
||||
def create(cls, request, audit_template_uuid, type, deadline):
|
||||
"""Create an audit in Watcher
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param audit_template: audit audit_template
|
||||
:type audit_template: string
|
||||
|
||||
:param type: audit type
|
||||
:type type: string
|
||||
|
||||
:param deadline: audit deadline
|
||||
:type deadline: string
|
||||
|
||||
:return: the created Audit object
|
||||
:rtype: watcher_dashboard.api.watcher.Audit
|
||||
"""
|
||||
audit = watcherclient(request).audit.create(
|
||||
audit_template_uuid=audit_template_uuid, type=type,
|
||||
deadline=deadline)
|
||||
return cls(audit, request=request)
|
||||
|
||||
@classmethod
|
||||
def list(cls, request, audit_template_filter):
|
||||
"""Return a list of audits in Watcher
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param audit_template_filter: audit_template filter, name or uuid
|
||||
:type audit_template_filter: string
|
||||
|
||||
:return: list of audits, or an empty list if there are none
|
||||
:rtype: list of watcher_dashboard.api.watcher.Audit
|
||||
"""
|
||||
audits = watcherclient(request).audit.list(
|
||||
audit_template=audit_template_filter)
|
||||
return [cls(audit, request=request) for audit in audits]
|
||||
|
||||
@classmethod
|
||||
@errors_utils.handle_errors(_("Unable to retrieve audit"))
|
||||
def get(cls, request, audit_id):
|
||||
"""Return the audit that matches the ID
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param audit_id: id of audit to be retrieved
|
||||
:type audit_id: int
|
||||
|
||||
:return: matching audit, or None if no audit matches
|
||||
the ID
|
||||
:rtype: watcher_dashboard.api.watcher.Audit
|
||||
"""
|
||||
audit = watcherclient(request).audit.get(audit_id=audit_id)
|
||||
return cls(audit, request=request)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, request, audit_id):
|
||||
"""Delete an audit
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param audit_id: audit id
|
||||
:type audit_id: int
|
||||
"""
|
||||
watcherclient(request).audit.delete(audit_id=audit_id)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.uuid
|
||||
|
||||
|
||||
class AuditTemplate(base.APIDictWrapper):
|
||||
_attrs = ('uuid', 'created_at', 'updated_at', 'deleted_at',
|
||||
'description', 'host_aggregate', 'name',
|
||||
'extra', 'goal')
|
||||
|
||||
def __init__(self, apiresource, request=None):
|
||||
super(AuditTemplate, self).__init__(apiresource)
|
||||
self._request = request
|
||||
|
||||
@classmethod
|
||||
def create(cls, request, name, goal, description, host_aggregate):
|
||||
"""Create an audit template in Watcher
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param name: Name for this audit template
|
||||
:type name: string
|
||||
|
||||
:param goal: Goal Type associated to this audit template
|
||||
:type goal: string
|
||||
|
||||
:param description: Descrition of the audit template
|
||||
:type description: string
|
||||
|
||||
:param host_aggregate: Name or ID of the host aggregate targeted\
|
||||
by this audit template
|
||||
:type host_aggregate: string
|
||||
|
||||
:param audit_template: audit audit_template
|
||||
:type audit_template: string
|
||||
|
||||
:return: the created Audit Template object
|
||||
:rtype: watcher_dashboard.api.watcher.AuditTemplate
|
||||
"""
|
||||
audit_template = watcherclient(request).audit_template.create(
|
||||
name=name,
|
||||
goal=goal,
|
||||
description=description,
|
||||
host_aggregate=host_aggregate
|
||||
)
|
||||
|
||||
return audit_template
|
||||
|
||||
@classmethod
|
||||
def patch(cls, request, audit_template_id, parameters):
|
||||
"""Update an audit in Watcher
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param audit_template_id: id of the audit template we want to update
|
||||
:type audit_template_id: string
|
||||
|
||||
:param parameters: new values for the audit template's parameters
|
||||
:type parameters: dict
|
||||
|
||||
:return: the updated Audit Template object
|
||||
:rtype: watcher_dashboard.api.watcher.AuditTemplate
|
||||
"""
|
||||
parameter_list = [{
|
||||
'name': str(name),
|
||||
'value': str(value),
|
||||
} for (name, value) in parameters.items()]
|
||||
audit_template = watcherclient(request).audit_template.patch(
|
||||
audit_template_id, parameter_list)
|
||||
return audit_template
|
||||
|
||||
@classmethod
|
||||
def list(cls, request, filter):
|
||||
"""Return a list of audit templates in Watcher
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param filter: audit template filter
|
||||
:type filter: string
|
||||
|
||||
:return: list of audit templates, or an empty list if there are none
|
||||
:rtype: list of watcher_dashboard.api.watcher.AuditTemplate
|
||||
"""
|
||||
|
||||
audit_templates = watcherclient(request).audit_template.list(
|
||||
name=filter)
|
||||
return audit_templates
|
||||
|
||||
@classmethod
|
||||
@errors_utils.handle_errors(_("Unable to retrieve audit template"))
|
||||
def get(cls, request, audit_template_id):
|
||||
"""Return the audit template that matches the ID
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param audit_template_id: id of audit template to be retrieved
|
||||
:type audit_template_id: int
|
||||
|
||||
:return: matching audit template, or None if no audit template matches
|
||||
the ID
|
||||
:rtype: watcher_dashboard.api.watcher.AuditTemplate
|
||||
"""
|
||||
audit_template = watcherclient(request).audit_template.get(
|
||||
audit_template_id=audit_template_id)
|
||||
# return cls(audit, request=request)
|
||||
return audit_template
|
||||
|
||||
@classmethod
|
||||
@errors_utils.handle_errors(_("Unable to retrieve audit template goal"))
|
||||
def get_goals(cls, request):
|
||||
"""Return the audit template goal that matches the ID
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param audit_template_id: id of audit template to be retrieved
|
||||
:type audit_template_id: int
|
||||
|
||||
:return: matching audit template, or None if no audit template matches
|
||||
the ID
|
||||
:rtype: watcher_dashboard.api.watcher.AuditTemplate
|
||||
"""
|
||||
|
||||
goals = watcherclient(request).goal.list()
|
||||
return map(lambda goal: goal.name, goals)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, request, audit_template_id):
|
||||
"""Delete an audit_template
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param audit_template_id: audit id
|
||||
:type audit_template_id: int
|
||||
"""
|
||||
watcherclient(request).audit_template.delete(
|
||||
audit_template_id=audit_template_id)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.uuid
|
||||
|
||||
|
||||
class ActionPlan(base.APIResourceWrapper):
|
||||
_attrs = ('uuid', 'created_at', 'updated_at', 'deleted_at',
|
||||
'audit_uuid', 'state')
|
||||
|
||||
def __init__(self, apiresource, request=None):
|
||||
super(ActionPlan, self).__init__(apiresource)
|
||||
self._request = request
|
||||
|
||||
@classmethod
|
||||
def list(cls, request, audit_filter):
|
||||
"""Return a list of action plans in Watcher
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param audit_filter: audit id filter
|
||||
:type audit_filter: string
|
||||
|
||||
:return: list of action plans, or an empty list if there are none
|
||||
:rtype: list of watcher_dashboard.api.watcher.ActionPlan
|
||||
"""
|
||||
action_plans = watcherclient(request).action_plan.list(
|
||||
audit=audit_filter)
|
||||
return [cls(action_plan, request=request)
|
||||
for action_plan in action_plans]
|
||||
|
||||
@classmethod
|
||||
@errors_utils.handle_errors(_("Unable to retrieve action plan"))
|
||||
def get(cls, request, action_plan_id):
|
||||
"""Return the action plan that matches the ID
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param action_plan_id: id of action plan to be retrieved
|
||||
:type action_plan_id: int
|
||||
|
||||
:return: matching action plan, or None if no action plan matches
|
||||
the ID
|
||||
:rtype: watcher_dashboard.api.watcher.ActionPlan
|
||||
"""
|
||||
action_plan = watcherclient(request).action_plan.get(
|
||||
action_plan_id=action_plan_id)
|
||||
return cls(action_plan, request=request)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, request, action_plan_id):
|
||||
"""Delete an action plan
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param action_plan_id: audit id
|
||||
:type action_plan_id: int
|
||||
"""
|
||||
watcherclient(request).action_plan.delete(
|
||||
action_plan_id=action_plan_id)
|
||||
|
||||
@classmethod
|
||||
def start(cls, request, action_plan_id):
|
||||
"""Start an Action Plan
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param action_plan_id: audit id
|
||||
:type action_plan_id: int
|
||||
"""
|
||||
patch = []
|
||||
patch.append({'op': 'replace', 'path': '/state', 'value': 'TRIGGERED'})
|
||||
watcherclient(request).action_plan.update(action_plan_id, patch)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.uuid
|
||||
|
||||
|
||||
class Action(base.APIResourceWrapper):
|
||||
_attrs = ('uuid', 'created_at', 'updated_at', 'deleted_at', 'next_uuid',
|
||||
'description', 'alarm', 'state', 'action_plan_uuid',
|
||||
'action_type', 'applies_to', 'src', 'dst', 'parameter')
|
||||
|
||||
def __init__(self, apiresource, request=None):
|
||||
super(Action, self).__init__(apiresource)
|
||||
self._request = request
|
||||
|
||||
@classmethod
|
||||
def list(cls, request, action_plan_filter):
|
||||
"""Return a list of actions in Watcher
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param action_plan_filter: action_plan id filter
|
||||
:type action_plan_filter: string
|
||||
|
||||
:return: list of actions, or an empty list if there are none
|
||||
:rtype: list of watcher_dashboard.api.watcher.Action
|
||||
"""
|
||||
|
||||
actions = watcherclient(request).action.list(
|
||||
action_plan=action_plan_filter, detail=True)
|
||||
return [cls(action, request=request)
|
||||
for action in actions]
|
||||
|
||||
@classmethod
|
||||
@errors_utils.handle_errors(_("Unable to retrieve action"))
|
||||
def get(cls, request, action_id):
|
||||
"""Return the action that matches the ID
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param action_id: id of action to be retrieved
|
||||
:type action_id: int
|
||||
|
||||
:return: matching action, or None if no action matches
|
||||
the ID
|
||||
:rtype: watcher_dashboard.api.watcher.Action
|
||||
"""
|
||||
action = watcherclient(request).action.get(
|
||||
action_id=action_id)
|
||||
return cls(action, request=request)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, request, action_id):
|
||||
"""Delete an action
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param action_id: action_plan id
|
||||
:type action_id: int
|
||||
"""
|
||||
watcherclient(request).action.delete(
|
||||
action_id=action_id)
|
||||
|
||||
@classmethod
|
||||
def start(cls, request, action_id):
|
||||
"""Start an Action Plan
|
||||
|
||||
:param request: request object
|
||||
:type request: django.http.HttpRequest
|
||||
|
||||
:param action_id: action_plan id
|
||||
:type action_id: int
|
||||
"""
|
||||
patch = []
|
||||
patch.append({'op': 'replace', 'path': '/state', 'value': 'TRIGGERED'})
|
||||
watcherclient(request).action.update(action_id, patch)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.uuid
|
0
watcher_dashboard/common/__init__.py
Normal file
0
watcher_dashboard/common/__init__.py
Normal file
24
watcher_dashboard/common/exceptions.py
Normal file
24
watcher_dashboard/common/exceptions.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 import exceptions
|
||||
from watcherclient.common.apiclient import exceptions as watcherclient
|
||||
|
||||
NOT_FOUND = exceptions.NOT_FOUND
|
||||
RECOVERABLE = exceptions.RECOVERABLE + (
|
||||
watcherclient.ClientException,
|
||||
)
|
||||
UNAUTHORIZED = exceptions.UNAUTHORIZED
|
0
watcher_dashboard/content/__init__.py
Normal file
0
watcher_dashboard/content/__init__.py
Normal file
0
watcher_dashboard/content/action_plans/__init__.py
Normal file
0
watcher_dashboard/content/action_plans/__init__.py
Normal file
23
watcher_dashboard/content/action_plans/panel.py
Normal file
23
watcher_dashboard/content/action_plans/panel.py
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
class ActionPlans(horizon.Panel):
|
||||
name = _("Action Plans")
|
||||
slug = "action_plans"
|
182
watcher_dashboard/content/action_plans/tables.py
Normal file
182
watcher_dashboard/content/action_plans/tables.py
Normal file
@ -0,0 +1,182 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from django.template.defaultfilters import title # noqa
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
import horizon.exceptions
|
||||
import horizon.messages
|
||||
import horizon.tables
|
||||
from horizon.utils import filters
|
||||
|
||||
from watcher_dashboard.api import watcher
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
ACTION_PLAN_STATE_DISPLAY_CHOICES = (
|
||||
("NO STATE", pgettext_lazy("State of an action plan", u"No State")),
|
||||
("ONGOING", pgettext_lazy("State of an action plan", u"On Going")),
|
||||
("SUCCESS", pgettext_lazy("State of an action plan", u"Sucess")),
|
||||
("SUBMITTED", pgettext_lazy("State of an action plan", u"Submitted")),
|
||||
("FAILED", pgettext_lazy("State of an action plan", u"Failed")),
|
||||
("DELETED", pgettext_lazy("State of an action plan", u"Deleted")),
|
||||
("RECOMMENDED", pgettext_lazy("State of an action plan", u"Recommended")),
|
||||
)
|
||||
|
||||
|
||||
class ActionPlansFilterAction(horizon.tables.FilterAction):
|
||||
# server = choices query = text
|
||||
filter_type = "server"
|
||||
filter_choices = (
|
||||
('audit_filter', _("Audit ="), True),
|
||||
)
|
||||
|
||||
|
||||
class ArchiveActionPlan(horizon.tables.BatchAction):
|
||||
name = "archive_action_plans"
|
||||
# policy_rules = (("compute", "compute:delete"),)
|
||||
help_text = _("Archive an action plan.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Archive Action Plan",
|
||||
u"Archive Action Plans",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Action Plan archived",
|
||||
u"Action Plans archived",
|
||||
count
|
||||
)
|
||||
|
||||
def action(self, request, obj_id):
|
||||
watcher.ActionPlan.delete(request, obj_id)
|
||||
|
||||
|
||||
class StartActionPlan(horizon.tables.BatchAction):
|
||||
name = "start_action_plan"
|
||||
classes = ('btn-confirm',)
|
||||
# policy_rules = (("compute", "compute:delete"),)
|
||||
help_text = _("Execute an action plan.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Start Action Plan",
|
||||
u"Start Action Plans",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Action Plan started",
|
||||
u"Action Plans started",
|
||||
count
|
||||
)
|
||||
|
||||
def action(self, request, action_plan_id):
|
||||
try:
|
||||
watcher.ActionPlan.start(request, action_plan_id)
|
||||
except Exception:
|
||||
msg = _('Failed to start the action plan.')
|
||||
LOG.info(msg)
|
||||
horizon.messages.warning(request, msg)
|
||||
|
||||
def allowed(self, request, action_plan):
|
||||
return ((action_plan is None) or
|
||||
(action_plan.state in ("RECOMMENDED", "FAILED")))
|
||||
|
||||
|
||||
class UpdateRow(horizon.tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, action_plan_id):
|
||||
action_plan = None
|
||||
|
||||
try:
|
||||
action_plan = watcher.Action.get(request, action_plan_id)
|
||||
except Exception:
|
||||
msg = _('Failed to get the action_plan.')
|
||||
LOG.info(msg)
|
||||
horizon.messages.warning(request, msg)
|
||||
|
||||
return action_plan
|
||||
|
||||
|
||||
# class CancelActionPlan(horizon.tables.DeleteAction):
|
||||
# verbose_name = _(u"Cancel ActionPlans")
|
||||
# icon = "trash"
|
||||
|
||||
# @staticmethod
|
||||
# def action_present(count):
|
||||
# return ungettext_lazy(
|
||||
# u"Cancel ActionPlan",
|
||||
# u"Cancel ActionPlans",
|
||||
# count
|
||||
# )
|
||||
|
||||
# @staticmethod
|
||||
# def action_past(count):
|
||||
# return ungettext_lazy(
|
||||
# u"Canceled ActionPlan",
|
||||
# u"canceled ActionPlans",
|
||||
# count
|
||||
# )
|
||||
|
||||
|
||||
class ActionPlansTable(horizon.tables.DataTable):
|
||||
name = horizon.tables.Column(
|
||||
'id',
|
||||
verbose_name=_("ID"),
|
||||
link="horizon:admin:action_plans:detail")
|
||||
audit = horizon.tables.Column(
|
||||
'audit_uuid',
|
||||
verbose_name=_('Audit'),
|
||||
filters=(title, filters.replace_underscores))
|
||||
updated_at = horizon.tables.Column(
|
||||
'updated_at',
|
||||
filters=(filters.parse_isotime,
|
||||
filters.timesince_sortable),
|
||||
verbose_name=_("Updated At"))
|
||||
status = horizon.tables.Column(
|
||||
'state',
|
||||
verbose_name=_('State'),
|
||||
status=True,
|
||||
status_choices=ACTION_PLAN_STATE_DISPLAY_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "action_plans"
|
||||
verbose_name = _("ActionPlans")
|
||||
table_actions = (
|
||||
# CancelActionPlan,
|
||||
ActionPlansFilterAction,
|
||||
)
|
||||
row_actions = (
|
||||
StartActionPlan,
|
||||
# CreateActionPlans,
|
||||
ArchiveActionPlan,
|
||||
# CreateActionPlans,
|
||||
# DeleteActionPlans,
|
||||
)
|
||||
row_class = UpdateRow
|
33
watcher_dashboard/content/action_plans/tabs.py
Normal file
33
watcher_dashboard/content/action_plans/tabs.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 _
|
||||
from horizon import tabs
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "infra_optim/action_plans/_detail_overview.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
return {"action_plan": self.tab_group.kwargs['action_plans']}
|
||||
|
||||
|
||||
class ActionPlanDetailTabs(tabs.TabGroup):
|
||||
slug = "action_plan_details"
|
||||
tabs = (OverviewTab,)
|
||||
sticky = True
|
30
watcher_dashboard/content/action_plans/urls.py
Normal file
30
watcher_dashboard/content/action_plans/urls.py
Normal file
@ -0,0 +1,30 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 import urls
|
||||
|
||||
from watcher_dashboard.content.action_plans import views
|
||||
|
||||
|
||||
urlpatterns = urls.patterns(
|
||||
'watcher_dashboard.content.action_plans.views',
|
||||
urls.url(r'^$',
|
||||
views.IndexView.as_view(), name='index'),
|
||||
urls.url(r'^(?P<action_plan_id>[^/]+)/detail$',
|
||||
views.DetailView.as_view(), name='detail'),
|
||||
urls.url(r'^archive/$',
|
||||
views.ArchiveView.as_view(), name='archive'),
|
||||
)
|
134
watcher_dashboard/content/action_plans/views.py
Normal file
134
watcher_dashboard/content/action_plans/views.py
Normal file
@ -0,0 +1,134 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import horizon.exceptions
|
||||
from horizon import forms
|
||||
import horizon.tables
|
||||
import horizon.tabs
|
||||
from horizon.utils import memoized
|
||||
import horizon.workflows
|
||||
|
||||
from watcher_dashboard.api import watcher
|
||||
from watcher_dashboard.content.action_plans import tables
|
||||
from watcher_dashboard.content.actions import tables as action_tables
|
||||
from watcher_dashboard.content.audits import forms as wforms
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(horizon.tables.DataTableView):
|
||||
table_class = tables.ActionPlansTable
|
||||
template_name = 'infra_optim/action_plans/index.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(IndexView, self).get_context_data(**kwargs)
|
||||
context['action_plans_count'] = self.get_action_plans_count()
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
action_plans = []
|
||||
search_opts = self.get_filters()
|
||||
try:
|
||||
action_plans = watcher.ActionPlan.list(
|
||||
self.request,
|
||||
audit_filter=search_opts)
|
||||
except Exception:
|
||||
horizon.exceptions.handle(
|
||||
self.request,
|
||||
_("Unable to retrieve action_plan information."))
|
||||
return action_plans
|
||||
|
||||
def get_action_plans_count(self):
|
||||
return len(self.get_data())
|
||||
|
||||
def get_filters(self):
|
||||
filter = None
|
||||
filter_action = self.table._meta._filter_action
|
||||
if filter_action:
|
||||
filter_field = self.table.get_filter_field()
|
||||
if filter_action.is_api_filter(filter_field):
|
||||
filter_string = self.table.get_filter_string()
|
||||
if filter_field and filter_string:
|
||||
filter = filter_string
|
||||
return filter
|
||||
|
||||
|
||||
class ArchiveView(forms.ModalFormView):
|
||||
form_class = wforms.CreateForm
|
||||
form_id = "create_audit_form"
|
||||
modal_header = _("Create Audit")
|
||||
template_name = 'infra_optim/audits/create.html'
|
||||
page_title = _("Create Audit")
|
||||
submit_label = _("Create Audit")
|
||||
|
||||
|
||||
class DetailView(horizon.tables.MultiTableView):
|
||||
table_classes = (action_tables.ActionsTable,)
|
||||
template_name = 'infra_optim/action_plans/details.html'
|
||||
page_title = _("Action Plan Details: {{ action_plan.uuid }}")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
action_plan_id = None
|
||||
try:
|
||||
action_plan_id = self.kwargs['action_plan_id']
|
||||
action_plan = watcher.ActionPlan.get(self.request, action_plan_id)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve details for action_plan "%s".') \
|
||||
% action_plan_id
|
||||
horizon.exceptions.handle(
|
||||
self.request, msg,
|
||||
redirect=self.redirect_url)
|
||||
return action_plan
|
||||
|
||||
def get_wactions_data(self):
|
||||
try:
|
||||
action_plan = self._get_data()
|
||||
actions = watcher.Action.list(self.request,
|
||||
action_plan_filter=action_plan.id)
|
||||
except Exception:
|
||||
actions = []
|
||||
msg = _('Action list can not be retrieved.')
|
||||
horizon.exceptions.handle(self.request, msg)
|
||||
return actions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
action_plan = self._get_data()
|
||||
context["action_plan"] = action_plan
|
||||
LOG.info('*********************************')
|
||||
LOG.info('*********************************')
|
||||
LOG.info('*********************************')
|
||||
LOG.info('*********************************')
|
||||
LOG.info('*********************************')
|
||||
LOG.info('*********************************')
|
||||
LOG.info('*********************************')
|
||||
LOG.info(action_plan)
|
||||
LOG.info('*********************************')
|
||||
LOG.info('*********************************')
|
||||
LOG.info('*********************************')
|
||||
LOG.info('*********************************')
|
||||
return context
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
action_plan = self._get_data()
|
||||
# ports = self._get_ports()
|
||||
return self.tab_group_class(request, action_plan=action_plan,
|
||||
# ports=ports,
|
||||
**kwargs)
|
0
watcher_dashboard/content/actions/__init__.py
Normal file
0
watcher_dashboard/content/actions/__init__.py
Normal file
23
watcher_dashboard/content/actions/panel.py
Normal file
23
watcher_dashboard/content/actions/panel.py
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
class Actions(horizon.Panel):
|
||||
name = _("Actions ")
|
||||
slug = "actions"
|
89
watcher_dashboard/content/actions/tables.py
Normal file
89
watcher_dashboard/content/actions/tables.py
Normal file
@ -0,0 +1,89 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from django.template.defaultfilters import title # noqa
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import horizon.exceptions
|
||||
import horizon.messages
|
||||
import horizon.tables
|
||||
from horizon.utils import filters
|
||||
|
||||
from watcher_dashboard.api import watcher
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
ACTION_STATE_DISPLAY_CHOICES = (
|
||||
("NO STATE", pgettext_lazy("Power state of an Instance", u"No State")),
|
||||
("ONGOING", pgettext_lazy("Power state of an Instance", u"On Going")),
|
||||
("SUCCESS", pgettext_lazy("Power state of an Instance", u"Success")),
|
||||
("CANCELLED", pgettext_lazy("Power state of an Instance", u"Cancelled")),
|
||||
("FAILED", pgettext_lazy("Power state of an Instance", u"Failed")),
|
||||
("DELETED", pgettext_lazy("Power state of an Instance", u"Deleted")),
|
||||
("PENDING", pgettext_lazy("Power state of an Instance", u"Pending")),
|
||||
)
|
||||
|
||||
|
||||
class UpdateRow(horizon.tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, action_id):
|
||||
action = None
|
||||
try:
|
||||
action = watcher.Action.get(request, action_id)
|
||||
except Exception:
|
||||
msg = _('Failed to get the action.')
|
||||
LOG.info(msg)
|
||||
horizon.messages.warning(request, msg)
|
||||
|
||||
return action
|
||||
|
||||
|
||||
class ActionsFilterAction(horizon.tables.FilterAction):
|
||||
filter_type = "server"
|
||||
filter_choices = (('action_plan', _("Action Plan ID ="), True),)
|
||||
|
||||
|
||||
class ActionsTable(horizon.tables.DataTable):
|
||||
name = horizon.tables.Column(
|
||||
'id',
|
||||
verbose_name=_("ID"))
|
||||
action_type = horizon.tables.Column(
|
||||
'action_type',
|
||||
verbose_name=_('Type'),
|
||||
filters=(title, filters.replace_underscores))
|
||||
description = horizon.tables.Column(
|
||||
'description',
|
||||
verbose_name=_('Description'))
|
||||
state = horizon.tables.Column(
|
||||
'state',
|
||||
verbose_name=_('State'),
|
||||
status_choices=ACTION_STATE_DISPLAY_CHOICES)
|
||||
|
||||
next_action = horizon.tables.Column(
|
||||
'next_uuid',
|
||||
verbose_name=_('Next Action'))
|
||||
ajax = True
|
||||
|
||||
class Meta(object):
|
||||
name = "wactions"
|
||||
verbose_name = _("Actions")
|
||||
table_actions = (ActionsFilterAction, )
|
||||
hidden_title = False
|
||||
row_class = UpdateRow
|
33
watcher_dashboard/content/actions/tabs.py
Normal file
33
watcher_dashboard/content/actions/tabs.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 _
|
||||
from horizon import tabs
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "infra_optim/actions/_detail_overview.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
return {"action": self.tab_group.kwargs['action']}
|
||||
|
||||
|
||||
class ActionDetailTabs(tabs.TabGroup):
|
||||
slug = "action_details"
|
||||
tabs = (OverviewTab,)
|
||||
sticky = True
|
28
watcher_dashboard/content/actions/urls.py
Normal file
28
watcher_dashboard/content/actions/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 import urls
|
||||
|
||||
from watcher_dashboard.content.actions import views
|
||||
|
||||
|
||||
urlpatterns = urls.patterns(
|
||||
'watcher_dashboard.content.actions.views',
|
||||
urls.url(r'^$',
|
||||
views.IndexView.as_view(), name='index'),
|
||||
urls.url(r'^(?P<action_id>[^/]+)/$',
|
||||
views.DetailView.as_view(), name='details'),
|
||||
)
|
96
watcher_dashboard/content/actions/views.py
Normal file
96
watcher_dashboard/content/actions/views.py
Normal file
@ -0,0 +1,96 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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.exceptions
|
||||
import horizon.tables
|
||||
import horizon.tabs
|
||||
from horizon.utils import memoized
|
||||
import horizon.workflows
|
||||
|
||||
from watcher_dashboard.api import watcher
|
||||
from watcher_dashboard.content.actions import tables
|
||||
from watcher_dashboard.content.actions import tabs as wtabs
|
||||
|
||||
|
||||
class IndexView(horizon.tables.DataTableView):
|
||||
table_class = tables.ActionsTable
|
||||
template_name = 'infra_optim/actions/index.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(IndexView, self).get_context_data(**kwargs)
|
||||
context['audits_count'] = self.get_actions_count()
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
actions = []
|
||||
search_opts = self.get_filters()
|
||||
try:
|
||||
actions = watcher.Action.list(self.request,
|
||||
action_plan_filter=search_opts)
|
||||
except Exception:
|
||||
horizon.exceptions.handle(
|
||||
self.request,
|
||||
_("Unable to retrieve action information."))
|
||||
return actions
|
||||
|
||||
def get_actions_count(self):
|
||||
return len(self.get_data())
|
||||
|
||||
def get_filters(self):
|
||||
filter = None
|
||||
filter_action = self.table._meta._filter_action
|
||||
if filter_action:
|
||||
filter_field = self.table.get_filter_field()
|
||||
if filter_action.is_api_filter(filter_field):
|
||||
filter_string = self.table.get_filter_string()
|
||||
if filter_field and filter_string:
|
||||
filter = filter_string
|
||||
return filter
|
||||
|
||||
|
||||
class DetailView(horizon.tabs.TabbedTableView):
|
||||
tab_group_class = wtabs.ActionDetailTabs
|
||||
template_name = 'infra_optim/actions/details.html'
|
||||
redirect_url = 'horizon:admin:actions:index'
|
||||
page_title = _("Action Details: {{ action.uuid }}")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
action_plan_id = None
|
||||
try:
|
||||
action_plan_id = self.kwargs['action_plan_id']
|
||||
action = watcher.Action.get(self.request, action_plan_id)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve details for action "%s".') \
|
||||
% action_plan_id
|
||||
horizon.exceptions.handle(
|
||||
self.request, msg,
|
||||
redirect=self.redirect_url)
|
||||
return action
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
action = self._get_data()
|
||||
context["action"] = action
|
||||
return context
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
action = self._get_data()
|
||||
# ports = self._get_ports()
|
||||
return self.tab_group_class(request, action=action,
|
||||
# ports=ports,
|
||||
**kwargs)
|
85
watcher_dashboard/content/audit_templates/forms.py
Normal file
85
watcher_dashboard/content/audit_templates/forms.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
|
||||
# All rights reserved.
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Forms for starting Watcher Audit Templates.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from watcher_dashboard.api import watcher
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateForm(forms.SelfHandlingForm):
|
||||
name = forms.CharField(max_length=255, label=_("Name"))
|
||||
description = forms.CharField(max_length=255, label=_("Description"),
|
||||
required=False)
|
||||
goal = forms.ChoiceField(label=_('Goal'),
|
||||
required=True,
|
||||
)
|
||||
|
||||
failure_url = 'horizon:admin:audit_templates:index'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CreateForm, self).__init__(request, *args, **kwargs)
|
||||
goals = self._get_goal_list(request)
|
||||
if goals:
|
||||
self.fields['goal'].choices = goals
|
||||
else:
|
||||
del self.fields['goal']
|
||||
|
||||
def _get_goal_list(self, request):
|
||||
try:
|
||||
goals = watcher.AuditTemplate.get_goals(self.request)
|
||||
except Exception as exc:
|
||||
msg = _('Failed to get goals list.')
|
||||
LOG.info(msg)
|
||||
messages.warning(request, msg)
|
||||
messages.warning(request, exc)
|
||||
goals = []
|
||||
|
||||
choices = [
|
||||
(goal, goal)
|
||||
for goal in goals
|
||||
]
|
||||
|
||||
if choices:
|
||||
choices.insert(0, ("", _("Select Goal")))
|
||||
return choices
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
params = {'name': data['name']}
|
||||
params['goal'] = data['goal']
|
||||
params['description'] = data['description']
|
||||
params['host_aggregate'] = None
|
||||
audit_temp = watcher.AuditTemplate.create(request, **params)
|
||||
message = _('Audit Template was successfully created.')
|
||||
messages.success(request, message)
|
||||
return audit_temp
|
||||
except Exception as exc:
|
||||
msg = _('Failed to create audit template"%s".') % data['name']
|
||||
LOG.info(exc)
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
return False
|
23
watcher_dashboard/content/audit_templates/panel.py
Normal file
23
watcher_dashboard/content/audit_templates/panel.py
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
class AuditTemplates(horizon.Panel):
|
||||
name = _("Audit Templates")
|
||||
slug = "audit_templates"
|
145
watcher_dashboard/content/audit_templates/tables.py
Normal file
145
watcher_dashboard/content/audit_templates/tables.py
Normal file
@ -0,0 +1,145 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 unicode_literals
|
||||
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
import horizon.exceptions
|
||||
import horizon.messages
|
||||
import horizon.tables
|
||||
|
||||
from watcher_dashboard.api import watcher
|
||||
|
||||
|
||||
AUDIT_TEMPLATE_GOAL_DISPLAY_CHOICES = (
|
||||
("BASIC_CONSOLIDATION", pgettext_lazy(
|
||||
"Goal of an Audit",
|
||||
"Consolidate Servers")),
|
||||
("MINIMIZE_ENERGY_CONSUMPTION", pgettext_lazy(
|
||||
"Goal of an Audit",
|
||||
"Minimize Energy")),
|
||||
("BALANCE_LOAD", pgettext_lazy(
|
||||
"Goal of an Audit",
|
||||
"Load Balancing")),
|
||||
("MINIMIZE_LICENSING_COST", pgettext_lazy(
|
||||
"Goal of an Audit",
|
||||
"Minimize Licensing Cost")),
|
||||
("PREPARED_PLAN_OPERATION", pgettext_lazy(
|
||||
"Goal of an Audit",
|
||||
"Prepared Plan Operation")),
|
||||
)
|
||||
|
||||
|
||||
class CreateAuditTemplates(horizon.tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Template")
|
||||
url = "horizon:admin:audit_templates:create"
|
||||
classes = ("ajax-modal", "btn-launch")
|
||||
|
||||
|
||||
class AuditTemplatesFilterAction(horizon.tables.FilterAction):
|
||||
filter_type = "server"
|
||||
filter_choices = (
|
||||
('name', _("Template Name ="), True),
|
||||
)
|
||||
|
||||
|
||||
class LaunchAudit(horizon.tables.BatchAction):
|
||||
name = "launch_audit"
|
||||
verbose_name = _("Launch Audit")
|
||||
data_type_singular = _("Launch Audit")
|
||||
data_type_plural = _("Launch Audits")
|
||||
success_url = "horizon:admin:audits:index"
|
||||
# icon = "cloud-upload"
|
||||
# policy_rules = (("compute", "compute:create"),)
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
"Launch Audit",
|
||||
"Launch Audits",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
"Launched Audit",
|
||||
"Launched Audits",
|
||||
count
|
||||
)
|
||||
|
||||
def action(self, request, obj_id):
|
||||
params = {'audit_template_uuid': obj_id}
|
||||
params['type'] = 'ONE_SHOT'
|
||||
params['deadline'] = None
|
||||
watcher.Audit.create(request, **params)
|
||||
|
||||
|
||||
class DeleteAuditTemplates(horizon.tables.DeleteAction):
|
||||
verbose_name = _("Delete Templates")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
"Delete Template",
|
||||
"Delete Templates",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
"Deleted Template",
|
||||
"Deleted Templates",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
watcher.AuditTemplate.delete(request, obj_id)
|
||||
|
||||
|
||||
class AuditTemplatesTable(horizon.tables.DataTable):
|
||||
name = horizon.tables.Column(
|
||||
'name',
|
||||
verbose_name=_("Name"),
|
||||
link="horizon:admin:audit_templates:detail")
|
||||
goal = horizon.tables.Column(
|
||||
'goal',
|
||||
verbose_name=_('Goal'),
|
||||
status=True,
|
||||
status_choices=AUDIT_TEMPLATE_GOAL_DISPLAY_CHOICES
|
||||
)
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return datum.uuid
|
||||
|
||||
class Meta(object):
|
||||
name = "audit_templates"
|
||||
verbose_name = _("Available")
|
||||
table_actions = (
|
||||
CreateAuditTemplates,
|
||||
DeleteAuditTemplates,
|
||||
AuditTemplatesFilterAction,
|
||||
# LaunchAuditTemplates,
|
||||
)
|
||||
row_actions = (
|
||||
LaunchAudit,
|
||||
# CreateAuditTemplates,
|
||||
# DeleteAuditTemplates,
|
||||
)
|
33
watcher_dashboard/content/audit_templates/tabs.py
Normal file
33
watcher_dashboard/content/audit_templates/tabs.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 _
|
||||
from horizon import tabs
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "infra_optim/audit_templates/_detail_overview.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
return {"audit_template": self.tab_group.kwargs['audit_template']}
|
||||
|
||||
|
||||
class AuditTemplateDetailTabs(tabs.TabGroup):
|
||||
slug = "audit_template_details"
|
||||
tabs = (OverviewTab,)
|
||||
sticky = True
|
148
watcher_dashboard/content/audit_templates/tests.py
Normal file
148
watcher_dashboard/content/audit_templates/tests.py
Normal file
@ -0,0 +1,148 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from django.core import urlresolvers
|
||||
from django import http
|
||||
from mox3.mox import IsA # noqa
|
||||
|
||||
from watcher_dashboard import api
|
||||
from watcher_dashboard.test import helpers as test
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
INDEX_URL = urlresolvers.reverse(
|
||||
'horizon:admin:audit_templates:index')
|
||||
CREATE_URL = urlresolvers.reverse(
|
||||
'horizon:admin:audit_templates:create')
|
||||
DETAILS_VIEW = 'horizon:admin:audit_templates:detail'
|
||||
|
||||
|
||||
class AuditTemplatesTest(test.BaseAdminViewTests):
|
||||
|
||||
goal_list = [
|
||||
'BASIC_CONSOLIDATION',
|
||||
'MINIMIZE_ENERGY_CONSUMPTION',
|
||||
'BALANCE_LOAD',
|
||||
'MINIMIZE_LICENSING_COST',
|
||||
'PREPARED_PLAN_OPERATION',
|
||||
]
|
||||
|
||||
@test.create_stubs({api.watcher.AuditTemplate: ('list',)})
|
||||
def test_index(self):
|
||||
search_opts = None
|
||||
api.watcher.AuditTemplate.list(
|
||||
IsA(http.HttpRequest),
|
||||
filter=search_opts).MultipleTimes().AndReturn(
|
||||
self.audit_templates.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'infra_optim/audit_templates/index.html')
|
||||
audit_templates = res.context['audit_templates_table'].data
|
||||
self.assertItemsEqual(audit_templates, self.audit_templates.list())
|
||||
|
||||
@test.create_stubs({api.watcher.AuditTemplate: ('list',)})
|
||||
def test_audit_template_list_unavailable(self):
|
||||
search_opts = None
|
||||
api.watcher.AuditTemplate.list(
|
||||
IsA(http.HttpRequest),
|
||||
filter=search_opts).MultipleTimes().AndRaise(
|
||||
self.exceptions.watcher)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
resp = self.client.get(INDEX_URL)
|
||||
self.assertMessageCount(resp, error=1, warning=0)
|
||||
|
||||
@test.create_stubs({api.watcher.AuditTemplate: ('get_goals',)})
|
||||
def test_create_get(self):
|
||||
api.watcher.AuditTemplate.get_goals(
|
||||
IsA(http.HttpRequest)).AndReturn(self.goal_list)
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(CREATE_URL)
|
||||
self.assertTemplateUsed(res, 'infra_optim/audit_templates/create.html')
|
||||
|
||||
@test.create_stubs({api.watcher.AuditTemplate: ('create',
|
||||
'get_goals')})
|
||||
def test_create_post(self):
|
||||
at = self.audit_templates.first()
|
||||
api.watcher.AuditTemplate.get_goals(
|
||||
IsA(http.HttpRequest)).AndReturn(self.goal_list)
|
||||
params = {'name': at.name,
|
||||
'goal': at.goal,
|
||||
'description': at.description,
|
||||
'host_aggregate': at.host_aggregate,
|
||||
}
|
||||
api.watcher.AuditTemplate.create(
|
||||
IsA(http.HttpRequest), **params).AndReturn(at)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'name': at.name,
|
||||
'goal': at.goal,
|
||||
'description': at.description,
|
||||
'host_aggregate': at.host_aggregate,
|
||||
}
|
||||
res = self.client.post(CREATE_URL, form_data)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.watcher.AuditTemplate: ('get',)})
|
||||
def test_details(self):
|
||||
at = self.audit_templates.first()
|
||||
at_id = at.uuid
|
||||
api.watcher.AuditTemplate.get(
|
||||
IsA(http.HttpRequest), at_id).\
|
||||
MultipleTimes().AndReturn(at)
|
||||
self.mox.ReplayAll()
|
||||
DETAILS_URL = urlresolvers.reverse(DETAILS_VIEW, args=[at_id])
|
||||
res = self.client.get(DETAILS_URL)
|
||||
self.assertTemplateUsed(res,
|
||||
'infra_optim/audit_templates/details.html')
|
||||
audit_templates = res.context['audit_template']
|
||||
self.assertItemsEqual([audit_templates], [at])
|
||||
|
||||
@test.create_stubs({api.watcher.AuditTemplate: ('get',)})
|
||||
def test_details_exception(self):
|
||||
at = self.audit_templates.first()
|
||||
at_id = at.uuid
|
||||
api.watcher.AuditTemplate.get(IsA(http.HttpRequest), at_id) \
|
||||
.AndRaise(self.exceptions.watcher)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
DETAILS_URL = urlresolvers.reverse(DETAILS_VIEW, args=[at_id])
|
||||
res = self.client.get(DETAILS_URL)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.watcher.AuditTemplate: ('delete', 'list')})
|
||||
def test_delete(self):
|
||||
search_opts = None
|
||||
at_list = self.audit_templates.list()
|
||||
at = self.audit_templates.first()
|
||||
at_id = at.uuid
|
||||
api.watcher.AuditTemplate.list(
|
||||
IsA(http.HttpRequest),
|
||||
filter=search_opts).MultipleTimes().AndReturn(
|
||||
at_list)
|
||||
api.watcher.AuditTemplate.delete(IsA(http.HttpRequest), at_id)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'action': 'audit_templates__delete',
|
||||
'object_ids': at_id}
|
||||
|
||||
res = self.client.post(INDEX_URL, form_data)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
29
watcher_dashboard/content/audit_templates/urls.py
Normal file
29
watcher_dashboard/content/audit_templates/urls.py
Normal file
@ -0,0 +1,29 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 import urls
|
||||
|
||||
from watcher_dashboard.content.audit_templates import views
|
||||
|
||||
|
||||
urlpatterns = urls.patterns(
|
||||
'watcher_dashboard.content.audit_templates.views',
|
||||
urls.url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
urls.url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||
urls.url(r'^(?P<audit_template_id>[^/]+)/detail$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
)
|
113
watcher_dashboard/content/audit_templates/views.py
Normal file
113
watcher_dashboard/content/audit_templates/views.py
Normal file
@ -0,0 +1,113 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import horizon.exceptions
|
||||
from horizon import forms
|
||||
import horizon.tables
|
||||
import horizon.tabs
|
||||
import horizon.workflows
|
||||
import logging
|
||||
from watcher_dashboard.api import watcher
|
||||
from watcher_dashboard.content.audit_templates import forms as wforms
|
||||
from watcher_dashboard.content.audit_templates import tables
|
||||
from watcher_dashboard.content.audit_templates import tabs as wtabs
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(horizon.tables.DataTableView):
|
||||
table_class = tables.AuditTemplatesTable
|
||||
template_name = 'infra_optim/audit_templates/index.html'
|
||||
page_title = _("Audit Templates")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(IndexView, self).get_context_data(**kwargs)
|
||||
context['audit_templates_count'] = self.get_count()
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
audit_templates = []
|
||||
search_opts = self.get_filters()
|
||||
try:
|
||||
audit_templates = watcher.AuditTemplate.list(self.request,
|
||||
filter=search_opts)
|
||||
except Exception:
|
||||
horizon.exceptions.handle(
|
||||
self.request,
|
||||
_("Unable to retrieve audit template information."))
|
||||
return audit_templates
|
||||
|
||||
def get_count(self):
|
||||
return len(self.get_data())
|
||||
|
||||
def get_filters(self):
|
||||
filter = None
|
||||
filter_action = self.table._meta._filter_action
|
||||
if filter_action:
|
||||
filter_field = self.table.get_filter_field()
|
||||
if filter_action.is_api_filter(filter_field):
|
||||
filter_string = self.table.get_filter_string()
|
||||
if filter_field and filter_string:
|
||||
filter = filter_string
|
||||
return filter
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = wforms.CreateForm
|
||||
form_id = "create_audit_templates_form"
|
||||
modal_header = _("Create Audit Template")
|
||||
template_name = 'infra_optim/audit_templates/create.html'
|
||||
success_url = reverse_lazy("horizon:admin:audit_templates:index")
|
||||
page_title = _("Create an Audit Template")
|
||||
submit_label = _("Create Audit Template")
|
||||
submit_url = reverse_lazy("horizon:admin:audit_templates:create")
|
||||
|
||||
|
||||
class DetailView(horizon.tabs.TabbedTableView):
|
||||
tab_group_class = wtabs.AuditTemplateDetailTabs
|
||||
template_name = 'infra_optim/audit_templates/details.html'
|
||||
redirect_url = 'horizon:admin:audit_templates:index'
|
||||
page_title = _("Audit Template Details: {{ audit_template.name }}")
|
||||
|
||||
def _get_data(self):
|
||||
audit_template_id = None
|
||||
try:
|
||||
LOG.info(self.kwargs)
|
||||
audit_template_id = self.kwargs['audit_template_id']
|
||||
audit_template = watcher.AuditTemplate.get(self.request,
|
||||
audit_template_id)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve details for audit template "%s".') \
|
||||
% audit_template_id
|
||||
horizon.exceptions.handle(
|
||||
self.request, msg,
|
||||
redirect=self.redirect_url)
|
||||
return audit_template
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
audit_template = self._get_data()
|
||||
context["audit_template"] = audit_template
|
||||
return context
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
audit_template = self._get_data()
|
||||
# ports = self._get_ports()
|
||||
return self.tab_group_class(request, audit_template=audit_template,
|
||||
# ports=ports,
|
||||
**kwargs)
|
0
watcher_dashboard/content/audits/__init__.py
Normal file
0
watcher_dashboard/content/audits/__init__.py
Normal file
81
watcher_dashboard/content/audits/forms.py
Normal file
81
watcher_dashboard/content/audits/forms.py
Normal file
@ -0,0 +1,81 @@
|
||||
# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
|
||||
# All rights reserved.
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Forms for starting Watcher Audits.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from watcher_dashboard.api import watcher
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateForm(forms.SelfHandlingForm):
|
||||
audit_template = forms.ChoiceField(label=_("Audit Template"),
|
||||
required=True)
|
||||
failure_url = 'horizon:admin:audits:index'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CreateForm, self).__init__(request, *args, **kwargs)
|
||||
audit_templates = self._get_audit_template_list(request)
|
||||
if audit_templates:
|
||||
self.fields['audit_template'].choices = audit_templates
|
||||
else:
|
||||
del self.fields['audit_template']
|
||||
|
||||
def _get_audit_template_list(self, request):
|
||||
try:
|
||||
audit_templates = watcher.AuditTemplate.list(self.request, None)
|
||||
except Exception:
|
||||
msg = _('Failed to get audit template list.')
|
||||
LOG.info(msg)
|
||||
messages.warning(request, msg)
|
||||
audit_templates = []
|
||||
|
||||
choices = [
|
||||
(audit_template.uuid, audit_template.name or audit_template.uuid)
|
||||
for audit_template in audit_templates
|
||||
]
|
||||
|
||||
if choices:
|
||||
choices.insert(0, ("", _("Select Audit Template")))
|
||||
return choices
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
params = {'audit_template_uuid': data['audit_template']}
|
||||
params['type'] = 'ONE_SHOT'
|
||||
params['deadline'] = None
|
||||
audit = watcher.Audit.create(request, **params)
|
||||
message = _('Audit was successfully created.')
|
||||
messages.success(request, message)
|
||||
return audit
|
||||
except Exception as exc:
|
||||
if exc.status_code == 409:
|
||||
msg = _('Quota exceeded for resource audit.')
|
||||
else:
|
||||
msg = _('Failed to create audit "%s".') % data['name']
|
||||
LOG.info(exc)
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
return False
|
23
watcher_dashboard/content/audits/panel.py
Normal file
23
watcher_dashboard/content/audits/panel.py
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
class Audits(horizon.Panel):
|
||||
name = _("Audits")
|
||||
slug = "audits"
|
149
watcher_dashboard/content/audits/tables.py
Normal file
149
watcher_dashboard/content/audits/tables.py
Normal file
@ -0,0 +1,149 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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.core import urlresolvers
|
||||
from django import shortcuts
|
||||
from django.template.defaultfilters import title # noqa
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import horizon.exceptions
|
||||
import horizon.messages
|
||||
import horizon.tables
|
||||
from horizon.utils import filters
|
||||
from watcher_dashboard.api import watcher
|
||||
|
||||
import logging
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
AUDIT_STATE_DISPLAY_CHOICES = (
|
||||
("NO STATE", pgettext_lazy("State of an audit", u"No State")),
|
||||
("ONGOING", pgettext_lazy("State of an audit", u"On Going")),
|
||||
("SUCCESS", pgettext_lazy("State of an audit", u"Sucess")),
|
||||
("SUBMITTED", pgettext_lazy("State of an audit", u"Submitted")),
|
||||
("FAILED", pgettext_lazy("State of an audit", u"Failed")),
|
||||
("DELETED", pgettext_lazy("State of an audit", u"Deleted")),
|
||||
("PENDING", pgettext_lazy("State of an audit", u"Pending")),
|
||||
)
|
||||
|
||||
|
||||
class AuditsFilterAction(horizon.tables.FilterAction):
|
||||
# server = choices query = text
|
||||
filter_type = "server"
|
||||
filter_choices = (
|
||||
('audit_template_filter', _("Audit Template ="), True),
|
||||
)
|
||||
|
||||
|
||||
class CreateAudit(horizon.tables.LinkAction):
|
||||
name = "launch_audit"
|
||||
verbose_name = _("Launch Audit")
|
||||
url = "horizon:admin:audits:create"
|
||||
classes = ("ajax-modal", "btn-launch")
|
||||
# policy_rules = (("compute", "compute:create"),)
|
||||
|
||||
|
||||
# class ArchiveAudits(horizon.tables.LinkAction):
|
||||
# name = "archive_audits"
|
||||
# verbose_name = _("Archive Audits")
|
||||
# url = "horizon:project:instances:launch"
|
||||
# classes = ("ajax-modal", "btn-launch")
|
||||
# icon = "folder-open"
|
||||
|
||||
class GoToActionPlan(horizon.tables.Action):
|
||||
name = "go_to_action_plan"
|
||||
verbose_name = _("Go to Action Plan")
|
||||
url = "horizon:admin:action_plans:detail"
|
||||
# classes = ("ajax-modal", "btn-launch")
|
||||
# icon = "send"
|
||||
|
||||
def allowed(self, request, audit):
|
||||
return ((audit is None) or
|
||||
(audit.state in ("SUCCESS")))
|
||||
|
||||
def single(self, table, request, audit_id):
|
||||
try:
|
||||
action_plans = watcher.ActionPlan.list(
|
||||
request,
|
||||
audit_filter=audit_id)
|
||||
except Exception:
|
||||
horizon.exceptions.handle(
|
||||
request,
|
||||
_("Unable to retrieve action_plan information."))
|
||||
return "javascript:void(0);"
|
||||
|
||||
return shortcuts.redirect(urlresolvers.reverse(
|
||||
self.url,
|
||||
args=[action_plans[0].uuid]))
|
||||
|
||||
|
||||
class GoToAuditTemplate(horizon.tables.Action):
|
||||
name = "go_to_audit_template"
|
||||
verbose_name = _("Go to Audit Template")
|
||||
url = "horizon:admin:audit_templates:detail"
|
||||
# classes = ("ajax-modal", "btn-launch")
|
||||
# icon = "send"
|
||||
|
||||
def allowed(self, request, audit):
|
||||
return ((audit is None) or
|
||||
(audit.state in ("SUCCESS")))
|
||||
|
||||
def single(self, table, request, audit_id):
|
||||
try:
|
||||
audit = watcher.Audit.get(
|
||||
request, audit_id=audit_id)
|
||||
except Exception:
|
||||
horizon.exceptions.handle(
|
||||
request,
|
||||
_("Unable to retrieve action_plan information."))
|
||||
return "javascript:void(0);"
|
||||
|
||||
return shortcuts.redirect(urlresolvers.reverse(
|
||||
self.url,
|
||||
args=[audit.audit_template_uuid]))
|
||||
|
||||
|
||||
class AuditsTable(horizon.tables.DataTable):
|
||||
name = horizon.tables.Column(
|
||||
'id',
|
||||
verbose_name=_("ID"),
|
||||
link="horizon:admin:audits:detail")
|
||||
audit_template = horizon.tables.Column(
|
||||
'audit_template_name',
|
||||
verbose_name=_('Audit Template'),
|
||||
filters=(title, filters.replace_underscores))
|
||||
status = horizon.tables.Column(
|
||||
'state',
|
||||
verbose_name=_('State'),
|
||||
status=True,
|
||||
status_choices=AUDIT_STATE_DISPLAY_CHOICES)
|
||||
|
||||
class Meta(object):
|
||||
name = "audits"
|
||||
verbose_name = _("Audits")
|
||||
launch_actions = (CreateAudit,)
|
||||
table_actions = launch_actions + (
|
||||
# CancelAudit,
|
||||
AuditsFilterAction,
|
||||
# ArchiveAudits,
|
||||
)
|
||||
row_actions = (
|
||||
GoToActionPlan,
|
||||
GoToAuditTemplate,
|
||||
# CreateAudits,
|
||||
# ArchiveAudits,
|
||||
# CreateAudits,
|
||||
# DeleteAudits,
|
||||
)
|
33
watcher_dashboard/content/audits/tabs.py
Normal file
33
watcher_dashboard/content/audits/tabs.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 _
|
||||
from horizon import tabs
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "infra_optim/audits/_detail_overview.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
return {"audit": self.tab_group.kwargs['audit']}
|
||||
|
||||
|
||||
class AuditDetailTabs(tabs.TabGroup):
|
||||
slug = "audit_details"
|
||||
tabs = (OverviewTab,)
|
||||
sticky = True
|
30
watcher_dashboard/content/audits/urls.py
Normal file
30
watcher_dashboard/content/audits/urls.py
Normal file
@ -0,0 +1,30 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 import urls
|
||||
|
||||
from watcher_dashboard.content.audits import views
|
||||
|
||||
|
||||
urlpatterns = urls.patterns(
|
||||
'watcher_dashboard.audits.views',
|
||||
urls.url(r'^$',
|
||||
views.IndexView.as_view(), name='index'),
|
||||
urls.url(r'^create/$',
|
||||
views.CreateView.as_view(), name='create'),
|
||||
urls.url(r'^(?P<audit_id>[^/]+)/detail$',
|
||||
views.DetailView.as_view(), name='detail'),
|
||||
)
|
117
watcher_dashboard/content/audits/views.py
Normal file
117
watcher_dashboard/content/audits/views.py
Normal file
@ -0,0 +1,117 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import horizon.exceptions
|
||||
from horizon import forms
|
||||
import horizon.tables
|
||||
import horizon.tabs
|
||||
from horizon.utils import memoized
|
||||
import horizon.workflows
|
||||
from watcher_dashboard.api import watcher
|
||||
from watcher_dashboard.content.audits import forms as wforms
|
||||
from watcher_dashboard.content.audits import tables
|
||||
from watcher_dashboard.content.audits import tabs as wtabs
|
||||
|
||||
|
||||
class IndexView(horizon.tables.DataTableView):
|
||||
table_class = tables.AuditsTable
|
||||
template_name = 'infra_optim/audits/index.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(IndexView, self).get_context_data(**kwargs)
|
||||
create_action = {
|
||||
'name': _("New Audit"),
|
||||
'url': reverse('horizon:admin:audits:create'),
|
||||
'icon': 'fa-plus',
|
||||
'ajax_modal': True,
|
||||
}
|
||||
context['header_actions'] = [create_action]
|
||||
context['audits_count'] = self.get_audits_count()
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
audits = []
|
||||
search_opts = self.get_filters()
|
||||
try:
|
||||
audits = watcher.Audit.list(self.request,
|
||||
audit_template_filter=search_opts)
|
||||
except Exception:
|
||||
horizon.exceptions.handle(
|
||||
self.request,
|
||||
_("Unable to retrieve audit information."))
|
||||
return audits
|
||||
|
||||
def get_audits_count(self):
|
||||
return len(self.get_data())
|
||||
|
||||
def get_filters(self):
|
||||
filter = None
|
||||
filter_action = self.table._meta._filter_action
|
||||
if filter_action:
|
||||
filter_field = self.table.get_filter_field()
|
||||
if filter_action.is_api_filter(filter_field):
|
||||
filter_string = self.table.get_filter_string()
|
||||
if filter_field and filter_string:
|
||||
filter = filter_string
|
||||
return filter
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = wforms.CreateForm
|
||||
form_id = "create_audit_form"
|
||||
modal_header = _("Create Audit")
|
||||
template_name = 'infra_optim/audits/create.html'
|
||||
success_url = reverse_lazy("horizon:admin:audits:index")
|
||||
page_title = _("Create Audit")
|
||||
submit_label = _("Create Audit")
|
||||
submit_url = reverse_lazy("horizon:admin:audits:create")
|
||||
|
||||
|
||||
class DetailView(horizon.tabs.TabbedTableView):
|
||||
tab_group_class = wtabs.AuditDetailTabs
|
||||
template_name = 'infra_optim/audits/details.html'
|
||||
redirect_url = 'horizon:admin:audits:index'
|
||||
page_title = _("Audit Details: {{ audit.uuid }}")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
audit_id = None
|
||||
try:
|
||||
audit_id = self.kwargs['audit_id']
|
||||
audit = watcher.Audit.get(self.request, audit_id)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve details for audit "%s".') \
|
||||
% audit_id
|
||||
horizon.exceptions.handle(
|
||||
self.request, msg,
|
||||
redirect=self.redirect_url)
|
||||
return audit
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
audit = self._get_data()
|
||||
context["audit"] = audit
|
||||
return context
|
||||
|
||||
def get_tabs(self, request, *args, **kwargs):
|
||||
audit = self._get_data()
|
||||
# ports = self._get_ports()
|
||||
return self.tab_group_class(request, audit=audit,
|
||||
# ports=ports,
|
||||
**kwargs)
|
36
watcher_dashboard/enabled/_31000_watcher_panelgroup.py
Normal file
36
watcher_dashboard/enabled/_31000_watcher_panelgroup.py
Normal file
@ -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 = 'watcher'
|
||||
# The display name of the PANEL_GROUP. Required.
|
||||
PANEL_GROUP_NAME = _('Optimization')
|
||||
# The slug of the dashboard the PANEL_GROUP associated with. Required.
|
||||
PANEL_GROUP_DASHBOARD = 'admin'
|
||||
|
||||
ADD_INSTALLED_APPS = ['watcher_dashboard']
|
||||
|
||||
# ADD_ANGULAR_MODULES = [
|
||||
# 'horizon.dashboard.watcher'
|
||||
# ]
|
||||
|
||||
# ADD_JS_FILES = [
|
||||
# 'horizon/lib/angular/angular-route.js'
|
||||
# ]
|
||||
|
||||
# ADD_SCSS_FILES = [
|
||||
# 'dashboard/watcher/watcher.scss'
|
||||
# ]
|
||||
|
||||
AUTO_DISCOVER_STATIC_FILES = False
|
23
watcher_dashboard/enabled/_31010_audit_templates_panel.py
Normal file
23
watcher_dashboard/enabled/_31010_audit_templates_panel.py
Normal file
@ -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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'audit_templates'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'admin'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'watcher'
|
||||
# If set, it will update the default panel of the PANEL_DASHBOARD.
|
||||
DEFAULT_PANEL = 'audit_templates'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'watcher_dashboard.content.audit_templates.panel.AuditTemplates'
|
23
watcher_dashboard/enabled/_31020_audits_panel.py
Normal file
23
watcher_dashboard/enabled/_31020_audits_panel.py
Normal file
@ -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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'audits'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'admin'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'watcher'
|
||||
# If set, it will update the default panel of the PANEL_DASHBOARD.
|
||||
DEFAULT_PANEL = 'audits'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'watcher_dashboard.content.audits.panel.Audits'
|
23
watcher_dashboard/enabled/_31030_action_plans_panel.py
Normal file
23
watcher_dashboard/enabled/_31030_action_plans_panel.py
Normal file
@ -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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'action_plans'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'admin'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'watcher'
|
||||
# If set, it will update the default panel of the PANEL_DASHBOARD.
|
||||
DEFAULT_PANEL = 'action_plans'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'watcher_dashboard.content.action_plans.panel.ActionPlans'
|
23
watcher_dashboard/enabled/_31040_actions_panel.py
Normal file
23
watcher_dashboard/enabled/_31040_actions_panel.py
Normal file
@ -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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'actions'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'admin'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'watcher'
|
||||
# If set, it will update the default panel of the PANEL_DASHBOARD.
|
||||
DEFAULT_PANEL = 'actions'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'watcher_dashboard.content.actions.panel.Actions'
|
0
watcher_dashboard/enabled/__init__.py
Normal file
0
watcher_dashboard/enabled/__init__.py
Normal file
15
watcher_dashboard/models.py
Normal file
15
watcher_dashboard/models.py
Normal file
@ -0,0 +1,15 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
|
||||
"""
|
BIN
watcher_dashboard/static/infra_optim/images/chevron.png
Normal file
BIN
watcher_dashboard/static/infra_optim/images/chevron.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 348 B |
BIN
watcher_dashboard/static/infra_optim/images/power.png
Normal file
BIN
watcher_dashboard/static/infra_optim/images/power.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 606 B |
@ -0,0 +1,3 @@
|
||||
/* Additional CSS for infra_optim. */
|
||||
@import "/dashboard/scss/variables";
|
||||
@import "/bootstrap/scss/bootstrap/variables";
|
60
watcher_dashboard/static/infra_optim/tests/formset_table.js
Normal file
60
watcher_dashboard/static/infra_optim/tests/formset_table.js
Normal file
@ -0,0 +1,60 @@
|
||||
horizon.addInitFunction(function () {
|
||||
module("Formset table (watcher.formset_table.js)");
|
||||
|
||||
test("Reenumerate rows", function () {
|
||||
var html = $('#qunit-fixture');
|
||||
var table = html.find('table');
|
||||
var input = table.find('tbody tr#flavors__row__14 input').first();
|
||||
|
||||
input.attr('id', 'id_flavors-3-name');
|
||||
watcher.formset_table.reenumerate_rows(table, 'flavors');
|
||||
equal(input.attr('id'), 'id_flavors-0-name', "Enumerate old rows ids");
|
||||
input.attr('id', 'id_flavors-__prefix__-name');
|
||||
watcher.formset_table.reenumerate_rows(table, 'flavors');
|
||||
equal(input.attr('id'), 'id_flavors-0-name', "Enumerate new rows ids");
|
||||
});
|
||||
|
||||
test("Delete row", function () {
|
||||
var html = $('#qunit-fixture');
|
||||
var table = html.find('table');
|
||||
var row = table.find('tbody tr').first();
|
||||
var input = row.find('input#id_flavors-0-DELETE');
|
||||
|
||||
equal(row.css("display"), 'table-row');
|
||||
equal(input.attr('checked'), undefined);
|
||||
watcher.formset_table.replace_delete(row);
|
||||
var x = input.next('a');
|
||||
watcher.formset_table.delete_row.call(x);
|
||||
equal(row.css("display"), 'none');
|
||||
equal(input.attr('checked'), 'checked');
|
||||
});
|
||||
|
||||
test("Add row", function() {
|
||||
var html = $('#qunit-fixture');
|
||||
var table = html.find('table');
|
||||
var empty_row_html = '<tr><td><input id="id_flavors-__prefix__-name" name="flavors-__prefix__-name"></td></tr>';
|
||||
|
||||
equal(table.find('tbody tr').length, 3);
|
||||
equal(html.find('#id_flavors-TOTAL_FORMS').val(), 3);
|
||||
watcher.formset_table.add_row(table, 'flavors', empty_row_html);
|
||||
equal(table.find('tbody tr').length, 4);
|
||||
equal(table.find('tbody tr:last input').attr('id'), 'id_flavors-3-name');
|
||||
equal(html.find('#id_flavors-TOTAL_FORMS').val(), 4);
|
||||
});
|
||||
|
||||
test("Init formset table", function() {
|
||||
var html = $('#qunit-fixture');
|
||||
var table = html.find('table');
|
||||
|
||||
watcher.formset_table.init('flavors', '', 'Add row');
|
||||
equal(table.find('tfoot tr a').html(), 'Add row');
|
||||
});
|
||||
|
||||
test("Init formset table -- no add", function() {
|
||||
var html = $('#qunit-fixture');
|
||||
var table = html.find('table');
|
||||
|
||||
watcher.formset_table.init('flavors', '', '');
|
||||
equal(table.find('tfoot tr a').length, 0);
|
||||
});
|
||||
});
|
19
watcher_dashboard/templates/client_side/_modal_chart.html
Normal file
19
watcher_dashboard/templates/client_side/_modal_chart.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "horizon/client_side/template.html" %}
|
||||
{% load horizon %}
|
||||
|
||||
{% block id %}modal_chart_template{% endblock %}
|
||||
|
||||
{% block template %}
|
||||
{% jstemplate %}
|
||||
<div class="[[classes]]" style="top: 80px; display: block;">
|
||||
<ul id="interval_selector">
|
||||
<li><a href="#" data-interval="12h">12h</a></li>
|
||||
<li><a href="#" data-interval="24h">24h</a></li>
|
||||
<li class="active"><a href="#" data-interval="1w">1w</a></li>
|
||||
<li><a href="#" data-interval="1m">1m</a></li>
|
||||
<li><a href="#" data-interval="1y">1y</a></li>
|
||||
</ul>
|
||||
<div id="modal_chart"></div>
|
||||
</div>
|
||||
{% endjstemplate %}
|
||||
{% endblock %}
|
1
watcher_dashboard/templates/client_side/templates.html
Normal file
1
watcher_dashboard/templates/client_side/templates.html
Normal file
@ -0,0 +1 @@
|
||||
{% include "client_side/_modal_chart.html" %}
|
24
watcher_dashboard/templates/formset_table/_row.html
Normal file
24
watcher_dashboard/templates/formset_table/_row.html
Normal file
@ -0,0 +1,24 @@
|
||||
<tr{{ row.attr_string|safe }}>
|
||||
{% for cell in row %}
|
||||
<td{{ cell.attr_string|safe }}>
|
||||
{% if cell.field %}
|
||||
{{ cell.field }}
|
||||
{% else %}
|
||||
{%if cell.wrap_list %}<ul>{% endif %}{{ cell.value }}{%if cell.wrap_list %}</ul>{% endif %}
|
||||
{% endif %}
|
||||
{% if forloop.first %}
|
||||
{% for field in row.form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% for error in field.errors %}
|
||||
<span class="help-inline">{{ field.name }}: {{ error }}</span>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% if row.form.non_field_errors %}
|
||||
<div class="alert alert-error">
|
||||
{{ row.form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
43
watcher_dashboard/templates/formset_table/_table.html
Normal file
43
watcher_dashboard/templates/formset_table/_table.html
Normal file
@ -0,0 +1,43 @@
|
||||
{% extends 'horizon/common/_data_table.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block table_columns %}
|
||||
{% if not table.is_browser_table %}
|
||||
<tr>
|
||||
{% for column in columns %}
|
||||
<th {{ column.attr_string|safe }}><span
|
||||
{% if column.name in table.get_required_columns %}
|
||||
class="required"
|
||||
{% endif %}
|
||||
>{{ column }}</span></th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endblock table_columns %}
|
||||
|
||||
{% block table %}
|
||||
{% with table.get_formset as formset %}
|
||||
{{ formset.management_form }}
|
||||
{% if formset.non_field_errors %}
|
||||
<div class="alert alert-error">
|
||||
{{ formset.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{{ block.super }}
|
||||
|
||||
<script type="text/javascript">
|
||||
(window.$ || window.addHorizonLoadEvent)(function () {
|
||||
// prepare the js-enabled parts of the formset data table
|
||||
var prefix = '{{ table.name|escapejs }}';
|
||||
var empty_row_html = '{% filter escapejs %}{% include "formset_table/_row.html" with row=table.get_empty_row %}{% endfilter %}';
|
||||
{% if table.formset_class.extra %}
|
||||
var add_label = '{% filter escapejs %}{% trans "Add a row" %}{% endfilter %}';
|
||||
{% else %}
|
||||
var add_label = '';
|
||||
{% endif %}
|
||||
|
||||
watcher.formset_table.init(prefix, empty_row_html, add_label);
|
||||
});
|
||||
</script>
|
||||
{% endblock table %}
|
44
watcher_dashboard/templates/formset_table/menu_formset.html
Normal file
44
watcher_dashboard/templates/formset_table/menu_formset.html
Normal file
@ -0,0 +1,44 @@
|
||||
{% load i18n %}
|
||||
{{ formset.management_form }}
|
||||
{% for error in formset.non_form_errors %}
|
||||
<div class="alert alert-error error">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="row register-nodes-formset" id="formset-{{ formset.prefix }}">
|
||||
<div class="col-xs-5">
|
||||
<div class="clearfix register-nav-head">
|
||||
<a class="node-icon" data-toggle="collapse" href="#collapse-upload-form">
|
||||
<i class="fa fa-lg fa-upload"></i>
|
||||
</a>
|
||||
<a id="add-node-link" class="node-icon" href="#">
|
||||
<i class="fa fa-lg fa-plus"></i>
|
||||
</a>
|
||||
<h4>Nodes to register</h4>
|
||||
</div>
|
||||
<div id="collapse-upload-form" class="panel-collapse collapse">
|
||||
{% include 'infra_optim/nodes/_upload.html' with form=upload_form %}
|
||||
</div>
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
{% for form in formset %}
|
||||
<li {% if forloop.first %}class="active"{% endif %}>
|
||||
<a href="#tab-{{ form.prefix }}" data-toggle="tab">
|
||||
{{ form.get_name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-xs-7">
|
||||
<div class="tab-content">
|
||||
{% for form in formset %}
|
||||
{% include form_template with form=form active=forloop.first %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
(window.$ || window.addHorizonLoadEvent)(function () {
|
||||
var prefix = '{{ formset.prefix|escapejs }}';
|
||||
var empty_form_html = '{% filter escapejs %}{% include form_template with form=formset.empty_form %}{% endfilter %}';
|
||||
watcher.menu_formset.init(prefix, empty_form_html);
|
||||
});
|
||||
</script>
|
@ -0,0 +1,17 @@
|
||||
{% load i18n %}
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% include 'infra_optim/header_actions.html' %}
|
||||
<h1>
|
||||
{% if request.session.domain_context_name %}
|
||||
<em>{{ request.session.domain_context_name }}:</em>
|
||||
{% endif %}
|
||||
{{ title }}
|
||||
{% if items_count or items_count == 0%}
|
||||
<span class="badge">
|
||||
{{ items_count }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</h1>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,37 @@
|
||||
{% load i18n %}
|
||||
{% with workflow.get_entry_point as entry_point %}
|
||||
<div class="row">
|
||||
<form class="form form-horizontal col-xs-12" {{ workflow.attr_string|safe }} action="{{ workflow.get_absolute_url }}" {% if add_to_field %}data-add-to-field="{{ add_to_field }}"{% endif %} method="POST"{% if workflow.multipart %} enctype="multipart/form-data"{% endif %}>
|
||||
{% csrf_token %}
|
||||
{% if REDIRECT_URL %}<input type="hidden" name="{{ workflow.redirect_param_name }}" value="{{ REDIRECT_URL }}"/>{% endif %}
|
||||
<div class="fullscreen-workflow-body">
|
||||
<div class="actions pull-right">
|
||||
{% block workflow-buttons %}
|
||||
<input class="btn btn-primary pull-right"
|
||||
type="submit"
|
||||
value="{{ workflow.finalize_button_name }}">
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% block workflow-body %}
|
||||
<ul class="nav nav-tabs">
|
||||
{% for step in workflow.steps %}
|
||||
<li class="{% if entry_point == step.slug %}active{% endif %}{% if step.has_errors %} error{% endif %}{% if step.has_required_fields %} required{% endif %}">
|
||||
<a href="#{{ step.get_id }}" data-toggle="tab" data-target="#{{ step.get_id }}">{{ step }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
{% for step in workflow.steps %}
|
||||
<fieldset id="{{ step.get_id }}" class="js-tab-pane{% if entry_point == step.slug %} active{% endif %}">
|
||||
{{ step.render }}
|
||||
</fieldset>
|
||||
{% if not forloop.last %}
|
||||
<noscript><hr /></noscript>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endwith %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans workflow.name %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=workflow.name %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'infra_optim/_fullscreen_workflow.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,16 @@
|
||||
<h4>{{ label }}</h4>
|
||||
<div class="overview_chart">
|
||||
<div class="chart_container">
|
||||
<div class="chart"
|
||||
data-chart-type="line_chart"
|
||||
data-url="{{ url }}"
|
||||
data-form-selector='#linechart_general_form'
|
||||
data-settings='{ "auto_size": false, "axes_x" : false, "axes_y" : false {% if y_max %}, "yMax": {{ y_max }} {% endif %} }'>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bar_chart_container">
|
||||
<div class="chart"
|
||||
data-chart-type="overview_bar_chart">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,63 @@
|
||||
{% load i18n %}
|
||||
{% load url from future%}
|
||||
|
||||
{% if meter_conf %}
|
||||
<div class="nodes">
|
||||
<div id="ceilometer-stats">
|
||||
<form class="form-inline performance_charts" id="linechart_general_form">
|
||||
<div class="pull-right">
|
||||
<div class="form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-addon"><i class="fa fa-clock-o"></i></div>
|
||||
<select data-line-chart-command="select_box_change"
|
||||
id="date_options"
|
||||
name="date_options"
|
||||
class="form-control">
|
||||
<option value="0.041666">{% trans "Last hour" %}</option>
|
||||
<option value="0.25">{% trans "Last 6 hours" %}</option>
|
||||
<option value="0.5">{% trans "Last 12 hours" %}</option>
|
||||
<option value="1" selected="selected">{% trans "Last day" %}</option>
|
||||
<option value="7">{% trans "Last week" %}</option>
|
||||
<option value="{% now 'j' %}">{% trans "Month to date" %}</option>
|
||||
<option value="15">{% trans "Last 15 days" %}</option>
|
||||
<option value="30">{% trans "Last 30 days" %}</option>
|
||||
<option value="365">{% trans "Last year" %}</option>
|
||||
<option value="other">{% trans "Other" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="date_from_group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-addon">{% trans "From" %}</div>
|
||||
<input data-line-chart-command="date_picker_change"
|
||||
type="text"
|
||||
id="date_from"
|
||||
name="date_from"
|
||||
class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="date_to_group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-addon">{% trans "To" %}</div>
|
||||
<input data-line-chart-command="date_picker_change"
|
||||
type="text"
|
||||
id="date_to"
|
||||
name="date_to"
|
||||
class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="node-charts" class="nodes row">
|
||||
{% for meter_label, url_part, y_max in meter_conf %}
|
||||
<div class="col-xs-{{col_size}}">
|
||||
{% include "infra_optim/_performance_chart.html" with label=meter_label y_max=y_max url=node_perf_url|add:"?"|add:url_part only %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p>{% trans 'Metering service is not enabled.' %}</p>
|
||||
{% endif %}
|
8
watcher_dashboard/templates/infra_optim/_top_5_box.html
Normal file
8
watcher_dashboard/templates/infra_optim/_top_5_box.html
Normal file
@ -0,0 +1,8 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-2 col-xs-offset-3">{% include "infra_optim/_top_5_chart.html" with top_5=top_5.fan%}</div>
|
||||
<div class="col-xs-2">{% include "infra_optim/_top_5_chart.html" with top_5=top_5.voltage %}</div>
|
||||
<div class="col-xs-2">{% include "infra_optim/_top_5_chart.html" with top_5=top_5.temperature%}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-2 col-xs-offset-3">{% include "infra_optim/_top_5_chart.html" with top_5=top_5.current %}</div>
|
||||
</div>
|
29
watcher_dashboard/templates/infra_optim/_top_5_chart.html
Normal file
29
watcher_dashboard/templates/infra_optim/_top_5_chart.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% load i18n %}
|
||||
{% load chart_helpers %}
|
||||
|
||||
<h4>
|
||||
{% trans 'Top 5 Nodes' %} ({{ top_5.label }}):
|
||||
</h4>
|
||||
{% if top_5.data %}
|
||||
<table width="100%">
|
||||
{% for d in top_5.data %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'horizon:admin:nodes:detail' d.node_uuid %}">
|
||||
{{ d.node_uuid|truncatechars:6 }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ d.value}} {{ top_5.unit }}
|
||||
</td>
|
||||
<td>
|
||||
{%if d.direction %}
|
||||
<i class="fa fa-arrow-{{d.direction}}"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
{% trans 'No data available.' %}
|
||||
{% endif %}
|
11
watcher_dashboard/templates/infra_optim/_workflow_base.html
Normal file
11
watcher_dashboard/templates/infra_optim/_workflow_base.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans workflow.name %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=workflow.name %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,20 @@
|
||||
{% load i18n sizeformat %}
|
||||
{% load url from future %}
|
||||
|
||||
<h3>{% trans "Action Plan Overview" %}</h3>
|
||||
|
||||
<div class="info detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ action_plan.uuid|default:"—" }}</dd>
|
||||
{% url 'horizon:admin:audits:detail' action_plan.audit_uuid as audit_url %}
|
||||
<dt>{% trans "Audit ID" %}</dt>
|
||||
<dd><a href="{{ audit_url }}">{{ action_plan.audit_uuid|default:_("—") }}</a></dd>
|
||||
<dt>{% trans "State" %}</dt>
|
||||
<dd>{{ action_plan.state|default:"—" }}</dd>
|
||||
<dt>{% trans "Created At" %}</dt>
|
||||
<dd>{{ action_plan.created_at|default:"—" }}</dd>
|
||||
<dt>{% trans "Update At" %}</dt>
|
||||
<dd>{{ action_plan.updated_at|default:"—" }}</dd>
|
||||
</dl>
|
||||
</div>
|
@ -0,0 +1,11 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Flavor" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Action Plan") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Action Plan Details"%}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% include "infra_optim/action_plans/_details_overview.html" %}
|
||||
<hr>
|
||||
<div id="wactions">
|
||||
{{ wactions_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,14 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
{% block title %}{% trans 'Action Plans' %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Action Plans') items_count=action_plans_count %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div id="action_plans">
|
||||
{{ action_plans_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
38
watcher_dashboard/templates/infra_optim/actions/details.html
Normal file
38
watcher_dashboard/templates/infra_optim/actions/details.html
Normal file
@ -0,0 +1,38 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans 'Actions: ' %}{{ action.uuid }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_page_header.html' with title=_('Actions: ')|add:action.uuid %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="detail col-md-6">
|
||||
<h4>{% trans "Audit Info" %}</h4>
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ action.uuid|default:"—" }}</dd>
|
||||
<dt>{% trans "Type" %}</dt>
|
||||
<dd>{{ action.type|default:"—" }}</dd>
|
||||
{% url 'horizon:admin:action_templates:detail' action.action_template_uuid as action_template_url %}
|
||||
<dt>{% trans "Audit Template" %}</dt>
|
||||
<dd><a href="{{ action_template_url }}">{{ action.action_template_uuid|default:_("—") }}</a></dd>
|
||||
<dt>{% trans "State" %}</dt>
|
||||
<dd>{{ action.state|default:"—" }}</dd>
|
||||
<dt>{% trans "Deadline" %}</dt>
|
||||
<dd>{{ action.deadline|default:"—" }}</dd>
|
||||
<dt>{% trans "Created At" %}</dt>
|
||||
<dd>{{ action.created_at|default:"—" }}</dd>
|
||||
<dt>{% trans "Update At" %}</dt>
|
||||
<dd>{{ action.updated_at|default:"—" }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{{ table.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
14
watcher_dashboard/templates/infra_optim/actions/index.html
Normal file
14
watcher_dashboard/templates/infra_optim/actions/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
{% block title %}{% trans 'Actions' %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Actions') items_count=actions_count %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div id="wactions">
|
||||
{{ wactions_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,31 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans 'Audits: ' %}{{ audits.name }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_page_header.html' with title=_('Audits: ')|add:audits.name %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h4>{% trans "Hardware Info" %}</h4>
|
||||
<dl class="dl-horizontal dl-horizontal-left">
|
||||
<dt>{% trans "Severity" %}</dt>
|
||||
<dd>{{ audits.cpu_arch|default:"—" }}</dd>
|
||||
<dt>{% trans "CPUs" %}</dt>
|
||||
<dd>{{ audits.vcpus|default:"—" }}</dd>
|
||||
<dt>{% trans "Memory" %}</dt>
|
||||
<dd>{{ audits.ram_bytes|filesizeformat|default:"—" }}</dd>
|
||||
<dt>{% trans "Disk" %}</dt>
|
||||
<dd>{{ audits.disk_bytes|filesizeformat|default:"—" }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{{ table.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,35 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
{% block title %}{% trans 'Audits' %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Audits') items_count=flavors_count %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% if suggested_flavors_count %}
|
||||
<table id="suggested-flavors-header" class="table">
|
||||
<thead>
|
||||
<tr data-toggle="collapse" href="#suggested-flavors">
|
||||
<th class="col-xs-11">
|
||||
<a href="#suggested-flavors">{{ suggested_flavors_count }}× Suggested Flavor</a>
|
||||
</th>
|
||||
<th class="col-xs-1">
|
||||
<a class="pull-right" href="#suggested-flavors">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
<div id="suggested-flavors" class="panel-collapse collapse">
|
||||
{{ suggested_flavors_table.render }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="audits">
|
||||
{{ audits_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "Creates an audit template with specified parameters." %}</p>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Template" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'infra_optim/audit_templates/_create.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,35 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans 'Audit Templates: ' %}{{ audit_template.name }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_page_header.html' with title=_('Audit Templates: ')|add:audit_template.name %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="detail col-md-6">
|
||||
<h4>{% trans "Audit Template Info" %}</h4>
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ audit_template.name|default:"—" }}</dd>
|
||||
<dt>{% trans "Id" %}</dt>
|
||||
<dd>{{ audit_template.uuid|default:"—" }}</dd>
|
||||
<dt>{% trans "Goal" %}</dt>
|
||||
<dd>{{ audit_template.goal|default:"—" }}</dd>
|
||||
<dt>{% trans "Created At" %}</dt>
|
||||
<dd>{{ audit_template.created_at|default:"—" }}</dd>
|
||||
<dt>{% trans "Update At" %}</dt>
|
||||
<dd>{{ audit_template.updated_at|default:"—" }}</dd>
|
||||
<dt>{% trans "Deleted At" %}</dt>
|
||||
<dd>{{ audit_template.deleted_at|default:"—" }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{{ table.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,14 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
{% block title %}{% trans 'Audit Templates' %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_page_header.html' with title=_('Audit Templates') items_count=audit_templates_count %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div id="audit_templates">
|
||||
{{ audit_templates_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "Creates a audit with specified parameters." %}</p>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Audit" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'infra_optim/audits/_create.html' %}
|
||||
{% endblock %}
|
38
watcher_dashboard/templates/infra_optim/audits/details.html
Normal file
38
watcher_dashboard/templates/infra_optim/audits/details.html
Normal file
@ -0,0 +1,38 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans 'Audits: ' %}{{ audit.uuid }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_page_header.html' with title=_('Audits: ')|add:audit.uuid %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="detail col-md-6">
|
||||
<h4>{% trans "Audit Info" %}</h4>
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ audit.uuid|default:"—" }}</dd>
|
||||
<dt>{% trans "Type" %}</dt>
|
||||
<dd>{{ audit.type|default:"—" }}</dd>
|
||||
{% url 'horizon:admin:audit_templates:detail' audit.audit_template_uuid as audit_template_url %}
|
||||
<dt>{% trans "Audit Template" %}</dt>
|
||||
<dd><a href="{{ audit_template_url }}">{{ audit.audit_template_uuid|default:_("—") }}</a></dd>
|
||||
<dt>{% trans "State" %}</dt>
|
||||
<dd>{{ audit.state|default:"—" }}</dd>
|
||||
<dt>{% trans "Deadline" %}</dt>
|
||||
<dd>{{ audit.deadline|default:"—" }}</dd>
|
||||
<dt>{% trans "Created At" %}</dt>
|
||||
<dd>{{ audit.created_at|default:"—" }}</dd>
|
||||
<dt>{% trans "Update At" %}</dt>
|
||||
<dd>{{ audit.updated_at|default:"—" }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{{ table.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
14
watcher_dashboard/templates/infra_optim/audits/index.html
Normal file
14
watcher_dashboard/templates/infra_optim/audits/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
{% block title %}{% trans 'Audits' %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_page_header.html' with title=_('Audits') items_count=audits_count %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div id="audits">
|
||||
{{ audits_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
12
watcher_dashboard/templates/infra_optim/base.html
Normal file
12
watcher_dashboard/templates/infra_optim/base.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block css %}
|
||||
{{block.super}}
|
||||
|
||||
{% load compress %}
|
||||
{% compress css %}
|
||||
<link href='{{ STATIC_URL }}infra_optim/scss/infra_optim.scss' type='text/scss' media='screen' rel='stylesheet' />
|
||||
<link href="{{ STATIC_URL }}horizon/lib/font-awesome/css/font-awesome.css" type="text/css" media="screen" rel="stylesheet" />
|
||||
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
27
watcher_dashboard/templates/infra_optim/base_detail.html
Normal file
27
watcher_dashboard/templates/infra_optim/base_detail.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{% block breadcrumbs %}{% endblock %}
|
||||
|
||||
<div class="pull-right btn-toolbar">
|
||||
{% block actions %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<h3>{% block name %}{% endblock %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="pull-right">
|
||||
{% block overall_usage %}{% endblock %}
|
||||
</div>
|
||||
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
10
watcher_dashboard/templates/infra_optim/header_actions.html
Normal file
10
watcher_dashboard/templates/infra_optim/header_actions.html
Normal file
@ -0,0 +1,10 @@
|
||||
<div class="actions pull-right">
|
||||
{% for action in header_actions %}
|
||||
<a href="{{ action.url }}"
|
||||
title="{{ action.name }}"
|
||||
class="btn btn-default btn-lg btn-no-border {% if action.ajax_modal %}ajax-modal{% endif %}">
|
||||
{% if action.icon %}<span class="fa {{ action.icon }}"></span>{% endif %}
|
||||
{% if action.show_name %}{{ action.name }}{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
17
watcher_dashboard/templates/infra_optim/logs/index.html
Normal file
17
watcher_dashboard/templates/infra_optim/logs/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends 'infra_optim/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans 'Logs' %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Logs') items_count=flavors_count %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ table.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user