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