././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.864164 watcher_dashboard-13.1.0.dev5/0000775000175100017510000000000015033037770015217 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/.zuul.yaml0000664000175100017510000000034715033037765017170 0ustar00mylesmyles- project: templates: - check-requirements - horizon-non-primary-django-jobs - openstack-python3-jobs-horizon - openstack-cover-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924726.0 watcher_dashboard-13.1.0.dev5/AUTHORS0000664000175100017510000000450215033037766016275 0ustar00mylesmylesAkihiro Motoki Alexander Chadin Alfredo Moralejo Andreas Jaeger Anh Tran Antoine Cabot BubaVV Cao Xuan Hoang Corey Bryant Dantali0n David TARDIVEL Doug Hellmann Erik Olof Gunnar Andersson Flavio Percoco Ghanshyam Mann Ian Wienand Ivan Kolodyazhny Kim Bao Long Larry Rensing Li Wei M V P Nitesh Martin Kopec Michal Arbet Nguyen Hai OpenStack Release Bot Qian Min Chen Rajiv Kumar Sean McGinnis Sean Mooney ShangXiao Swapnil Kulkarni (coolsvap) Takashi Kajinami Thomas Goirand Tony Breeds Vincent Francoise Vladimir Ostroverkhov YuehuiLei YuehuiLei Yumeng Bao YumengBao Yumeng_Bao avnish caoyuan chenke gaofei gujin huang.zhiping jacky06 licanwei limin.lc lingyongxu malei manchandavishal melissaml pengyuesheng petergurinov qingszhao rajat29 ricolin zhangboye zhangguoqing zhulingjie zhurong zte-hanrong ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924726.0 watcher_dashboard-13.1.0.dev5/ChangeLog0000664000175100017510000002164315033037766017004 0ustar00mylesmylesCHANGES ======= * add pyproject.toml to support pip 23.1 * Update master for stable/2025.1 * tox: Drop envdir 13.0.0 ------ * Allow cancel audit when it is ongoing or pending * Support multiple global\_efficacy indicators in action plans table * Bump hacking * reno: Update master for unmaintained/2023.1 * Enable Watcher dashboard only when backend is on * Fix efficacy indicators in action plans * Update python versions, drop py3.8 * [devstack] use $PYTHON when configuring horizon * Drop unnecessary 'x' bit from doc config file * Update master for stable/2024.2 * reno: Update master for unmaintained/zed 12.0.0 ------ * Update master for stable/2024.1 * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria 11.0.0 ------ * reno: Update master for unmaintained/yoga * Update python classifier in setup.cfg * Drop use of features removed in Django 4.0 * Update master for stable/2023.2 10.0.0 ------ * Update master for stable/2023.1 9.0.0 ----- * Adjust tox.ini for tox4 * Replace deprecated inspect.getargspec * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed * Django 4.x: Replace removed features 8.0.0 ----- * Add Python3 zed unit tests * Update master for stable/yoga 7.0.0 ----- * Add Python3 yoga unit tests * Update master for stable/xena 6.0.0 ----- * Fix broken devstack install * Enable tox cover jobs on zuul * Repair coverage report tox job * Changed minversion in tox to 3.18.0 * Manage constraints with testenv install\_command * Add Python3 xena unit tests * doc: Update our IRC server to OFTC * Fix create audit fail from audit\_template * Use py3 as the default runtime for tox * setup.cfg: Replace dashes with underscores * Dropping lower constraints testing * Update master for stable/wallaby 5.0.0 ----- * Bump py37 to py38 in tox.ini * Add Python3 wallaby unit tests * Update master for stable/victoria 4.0.0 ----- * Cleanup for Refactor-error-messages * [goal] Migrate testing to ubuntu focal * Switch to newer openstackdocstheme and reno versions * Fix hacking min version to 3.0.1 * Remove the mock * Add py38 package metadata * Add Python3 victoria unit tests * Update master for stable/ussuri 3.0.0 ----- * Cleanup py27 support * Fix pyScss version in lower-constraints.txt * s/assertItemsEqual/assertCountEqual/g * Drop Django 1.11 support * translation: drop babel extractor definitions * Drop python 2.7 support and testing * tox: Keeping going with docs and cleanup setup.cfg * Switch to official Ussuri jobs * Use Horizon project template for django jobs * TypeError exception when tests with Django22 * Update master for stable/train 2.0.0 ----- * Build pdf docs * Add blueprints link for README * Refactor error messages * Remove the unused code * Blacklist sphinx 2.1.0 (autodoc bug) * Add py37 in tox.ini and setup.cfg file * Switch to the new canonical constraints URL on master * Sync Sphinx requirement * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch * Remove py35 * Update master for stable/stein * Add python 3.7 unit test job for watcher-dashboard * Replace openstack.org git:// URLs with https:// 1.12.0 ------ * Update hacking version * Use template for lower-constraints * Update devel info: mailing list * Change openstack-dev to openstack-discuss * Removed older version of python added 3.5 * Update min tox version to 2.0 * Fix Invalid filter: parse\_isotime * switch documentation job to new PTI * import zuul job settings from project-config * Add test 'node\_modules' in gitignore * Imported Translations from Zanata * Switch test runner to django default runner * Update reno for stable/rocky 1.11.0 ------ * fix audit delete failure: add allow func to filter audit * fix indicator exception * fix audit delete failure * Add name for audit: check if audit\_name already exists 1.10.0 ------ * Add name for audit * Set interval parameter to optional and add validation before audit create * Drop mox3 from test-requirements.txt * Add release note in README * Django 2.0 support and fix lower-constraints * fix tox python3 overrides * Explicitly use django\_nose.NoseTestSuiteRunner 1.9.0 ----- * Remove mox usage from api tests * Remove mox usage from audittemplate tests * Remove mox usage from strategy tests * Remove mox usage from goal tests * Updated from global requirements * add lower-constraints job * Delete the unnecessary '-' * Install horizon directly from pypi * Update links in README * Updated from global requirements * Imported Translations from Zanata * Fix url links and spelling error in docs * Imported Translations from Zanata * Update reno for stable/queens 1.8.0 ----- * Update unreachable link in installation doc 1.7.0 ----- * Updated from global requirements * Updated from global requirements * Cleanup test-requirements 1.6.0 ----- * Update audit\_template create help message * Remove setting of version/release from releasenotes * Updated from global requirements * Drop django\_openstack\_auth from requirements.txt * removed horizon from test\_requestent.txt * Remove the dulpicate description * Imported Translations from Zanata 1.5.0 ----- * Updated from global requirements * enable watcher-dashboard service in devstack * Add formatting marks in README.rst * Fix to use "." to source script files * Updated from global requirements * Add support for cron syntax * Update reno for stable/pike 1.4.0 ----- * Update url in README.rst * Update the documentation link for doc migration * Imported Translations from Zanata * Updated from global requirements 1.3.0 ----- * Updated from global requirements * Imported Translations from Zanata * Update docs to the new standards * Remove load url from future and fix gate * Add DS\_Store to gitignore * switch to openstackdocstheme * Add rm to whitelist\_externals in tox.ini 1.2.0 ----- * Updated from global requirements * Fix watcher-dashboard deployment error * Fixing Tox and optimize the link address * Updated from global requirements 1.1.0 ----- * Updated from global requirements * Add a button to create audit template in audit creat form * Fix exception error when creating audit without audit template * Adding horizon time filters to format the time in detail page * Replace default:"—" with default:\_("-") * Using enable\_plugin instend of enable\_service * Remove the extra required=True * Remove the empty file form.py * Updated from global requirements * [Fix gate]Update test requirement * Remove unused logging import * Updated from global requirements * Update the path of watcher dashboard enable file * Refactor the watcher dashboard enable file * Update reno for stable/ocata 1.0.0 ----- * Add [watcher] tag when using OpenStack ML 0.8.0 ----- * Updated from global requirements * Remove link to modindex * 'next\_uuid' Action field is replaced by 'parents' * Updated from global requirements * Remove support for py26 * Replaces yaml.load() with yaml.safe\_load() * Fix bad parsing of HTTP response * Removed unnecessary utf-8 encoding * Remove useless ddt requirements * Remove useless pytz requirements * Add page title for some panel table * Remove the pep8 ingore * Add reno for release notes management * Remove the 'MANIFEST.in' * Add Constraints support * Add auto\_trigger option in Audit creation form 0.7.0 ----- * Show team and repo badges on README * Moved README.rst content to installation.rst doc * Remove unnecessary watcher\_dashboard/models.py file * Updated from global requirements * ceilometer has been removed from openstack-dashboard 0.6.0 ----- * Updated from global requirements * Added the audit scope field in dashboard * Updated from global requirements 0.5.0 ----- * Added action details * Updated from global requirements * Clean the code by renaming 'type' parameters * Drop \*openstack/common\* in flake8 exclude * Add new audit fields in dashboard tables * Update table's actions * Fix py27 commands failed * Replace UUID reference by name * modify the home-page info with the developer documentation * interval param should be set to None by default * Replace audit\_filter by audit * Remove 'Go to Audit Template' from Audit view 0.4.0 ----- * Remove discover from test-requirements * Updated from global requirements * Updated from global requirements * Add policies for API access control to watcher-dashboard * Do not pass interval parameter for ONESHOT audit * Add support continuously-optimization * Revert "Initializes EfficacyIndicator attributes" * Initializes EfficacyIndicator attributes * Modify error message from 'action\_plan' to 'audit' * Rename type keyword across the project * Replaced UUID of goal with name * Added efficacy-related fields to the dashboard 0.3.0 ----- * Added Goals and Strategies to Dashboard * Add fix for ONESHOT type * Removed unused 'alarm' field * Fix api test about AuditTemplate * update test-requirements to get horizon faster 0.2.0 ----- * Renamed 'TRIGGERED' state to 'PENDING' 0.1.0 ----- * Watcher Dashboard - Initial commit * Added .gitreview ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/HACKING.rst0000664000175100017510000000445515033037765017031 0ustar00mylesmylesContributing ============ The code repository is located at `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: ``__ We use OpenStack's Gerrit for the code contributions: ``__ and we follow the `OpenStack Gerrit Workflow `__. If you're interested in the code, here are some key places to start: * `watcher_dashboard/api.py `_ - This file contains all the API calls made to the Watcher API (through python-watcherclient). * `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 available 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 https://www.python.org/dev/peps/pep-0008/ - Step 2: Read https://www.python.org/dev/peps/pep-0008/ again - Step 3: Read https://github.com/openstack/hacking/blob/master/HACKING.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/LICENSE0000664000175100017510000002363715033037765016243 0ustar00mylesmyles 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. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.863164 watcher_dashboard-13.1.0.dev5/PKG-INFO0000644000175100017510000000432315033037770016314 0ustar00mylesmylesMetadata-Version: 2.2 Name: watcher-dashboard Version: 13.1.0.dev5 Summary: Watcher Management Dashboard Home-page: https://docs.openstack.org/watcher-dashboard/latest Author: OpenStack Author-email: openstack-discuss@lists.openstack.org Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: OpenStack Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Internet :: WWW/HTTP Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: pbr!=2.1.0,>=2.0.0 Requires-Dist: horizon>=18.2.0 Requires-Dist: PyYAML>=3.12 Requires-Dist: python-watcherclient>=1.1.0 Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: requires-dist Dynamic: requires-python Dynamic: summary ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/watcher-dashboard.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on OpenStack Dashboard plugin for Watcher project ============================================== The Watcher dashboard is a Horizon plugin that will allow users to realize a wide range of cloud optimization goals. * Free software: Apache license * Documentation: https://docs.openstack.org/watcher-dashboard/latest * Source: https://opendev.org/openstack/watcher-dashboard * Bugs: https://bugs.launchpad.net/watcher-dashboard * Release Notes: https://docs.openstack.org/releasenotes/watcher-dashboard * Blueprints: https://blueprints.launchpad.net/watcher-dashboard ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/README.rst0000664000175100017510000000147615033037765016722 0ustar00mylesmyles======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/watcher-dashboard.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on OpenStack Dashboard plugin for Watcher project ============================================== The Watcher dashboard is a Horizon plugin that will allow users to realize a wide range of cloud optimization goals. * Free software: Apache license * Documentation: https://docs.openstack.org/watcher-dashboard/latest * Source: https://opendev.org/openstack/watcher-dashboard * Bugs: https://bugs.launchpad.net/watcher-dashboard * Release Notes: https://docs.openstack.org/releasenotes/watcher-dashboard * Blueprints: https://blueprints.launchpad.net/watcher-dashboard ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924727.0 watcher_dashboard-13.1.0.dev5/RELEASENOTES.rst0000664000175100017510000000072515033037767017654 0ustar00mylesmyles================= watcher-dashboard ================= .. _watcher-dashboard_3.0.0: 3.0.0 ===== .. _watcher-dashboard_3.0.0_Upgrade Notes: Upgrade Notes ------------- .. releasenotes/notes/drop-py-2-7-198cca7f72d16655.yaml @ b'9e9d46411c9b062d4eb1b3e25e1b864bcb40bf18' - Python 2.7 support has been dropped. Last release of watcher-dashboard to support py2.7 is OpenStack Train. The minimum version of Python now supported by watcher-dashboard is Python 3.6. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/babel-django.cfg0000664000175100017510000000012315033037765020205 0ustar00mylesmyles[python: watcher_dashboard/**.py] [django: watcher_dashboard/**/templates/**.html] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/babel-djangojs.cfg0000664000175100017510000000012515033037765020544 0ustar00mylesmyles[javascript: watcher_dashboard/**.js] [angular: watcher_dashboard/**/static/**.html] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.853164 watcher_dashboard-13.1.0.dev5/devstack/0000775000175100017510000000000015033037770017023 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/devstack/local.conf.example0000664000175100017510000000017515033037765022425 0ustar00mylesmyles# settings file for watcher-dashboard plugin enable_plugin watcher-dashboard https://opendev.org/openstack/watcher-dashboard ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/devstack/plugin.sh0000664000175100017510000000341315033037765020662 0ustar00mylesmyles# 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/local/enabled/* ${DEST}/horizon/openstack_dashboard/local/enabled/ cp -a ${WATCHER_DASHBOARD_DIR}/watcher_dashboard/conf/* ${DEST}/horizon/openstack_dashboard/conf/ } 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* rm -f ${DEST}/horizon/openstack_dashboard/conf/watcher* fi if [[ "$1" == "clean" ]]; then # Remove state and transient data # Remember clean.sh first calls unstack.sh # no-op : fi fi ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/devstack/settings0000664000175100017510000000013215033037765020606 0ustar00mylesmyles# DevStack settings # Enable Watcher dashboard services enable_service watcher-dashboard ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.853164 watcher_dashboard-13.1.0.dev5/doc/0000775000175100017510000000000015033037770015764 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/doc/requirements.txt0000664000175100017510000000010315033037765021246 0ustar00mylesmylesopenstackdocstheme>=2.2.1 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.853164 watcher_dashboard-13.1.0.dev5/doc/source/0000775000175100017510000000000015033037770017264 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/doc/source/conf.py0000664000175100017510000000762315033037765020577 0ustar00mylesmyles# 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', 'openstackdocstheme', ] 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 Dashboard' 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 = 'native' # -- 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} # html_theme_options = {"show_other_versions": "True"} html_theme = 'openstackdocs' # 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', 'doc-watcher-dashboard.tex', u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 latex_use_xindy = False latex_domain_indices = False latex_elements = { 'makeindex': '', 'printindex': '', 'preamble': r'\setcounter{tocdepth}{3}', } # Example configuration for intersphinx: refer to the Python standard library. # intersphinx_mapping = {'http://docs.python.org/': None} # openstackdocstheme options openstackdocs_repo_name = 'openstack/watcher-dashboard' openstackdocs_pdf_link = True openstackdocs_auto_name = False openstackdocs_bug_project = 'watcher-dashboard' openstackdocs_bug_tag = '' ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.853164 watcher_dashboard-13.1.0.dev5/doc/source/contributor/0000775000175100017510000000000015033037770021636 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/doc/source/contributor/contributing.rst0000664000175100017510000000406615033037765025111 0ustar00mylesmyles.. 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/ .. _contributing: ======================= Contributing to Watcher ======================= If you're interested in contributing to the Watcher project, the following will help get you started. Contributor License Agreement ----------------------------- .. index:: single: license; agreement In order to contribute to the Watcher project, you need to have signed OpenStack's contributor's agreement. .. seealso:: * https://docs.openstack.org/infra/manual/developers.html * https://wiki.openstack.org/wiki/CLA LaunchPad Project ----------------- Most of the tools used for OpenStack depend on a launchpad.net ID for authentication. After signing up for a launchpad account, join the "openstack" team to have access to the mailing list and receive notifications of important events. .. seealso:: * https://launchpad.net/ * https://launchpad.net/watcher * https://launchpad.net/watcher-dashboard * https://launchpad.net/~openstack Project Hosting Details ----------------------- Bug tracker https://launchpad.net/watcher-dashboard Blueprints https://blueprints.launchpad.net/watcher-dashboard Mailing list (prefix subjects with ``[watcher]`` for faster responses) http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss Wiki https://wiki.openstack.org/wiki/Watcher Code Hosting https://opendev.org/openstack/watcher-dashboard Code Review https://review.opendev.org/#/q/status:open+project:openstack/watcher-dashboard,n,z IRC Channel ``#openstack-watcher`` (changelog_) Weekly Meetings On Wednesdays at 14:00 UTC on even weeks in the ``#openstack-meeting-4`` IRC channel, 13:00 UTC on odd weeks in the ``#openstack-meeting-alt`` IRC channel (`meetings logs`_) .. _changelog: http://eavesdrop.openstack.org/irclogs/%23openstack-watcher/ .. _meetings logs: http://eavesdrop.openstack.org/meetings/watcher/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/doc/source/contributor/index.rst0000664000175100017510000000005615033037765023504 0ustar00mylesmyles.. toctree:: :maxdepth: 1 contributing ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/doc/source/index.rst0000664000175100017510000000342215033037765021132 0ustar00mylesmyles.. 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://opendev.org/openstack/watcher/ .. _python-watcherclient: https://opendev.org/openstack/python-watcherclient/ .. _watcher-dashboard: https://opendev.org/openstack/watcher-dashboard/ Install Guide ============= .. toctree:: :maxdepth: 1 install/index Developer Guide =============== .. toctree:: :maxdepth: 1 contributor/index Indices and tables ================== * :ref:`genindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.853164 watcher_dashboard-13.1.0.dev5/doc/source/install/0000775000175100017510000000000015033037770020732 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/doc/source/install/index.rst0000664000175100017510000000005615033037765022600 0ustar00mylesmyles.. toctree:: :maxdepth: 1 installation ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/doc/source/install/installation.rst0000664000175100017510000001062715033037765024177 0ustar00mylesmylesInstallation ------------ 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 ````. 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 https://opendev.org/openstack/watcher-dashboard $ cd watcher-dashboard $ pip install -e . We will refer to the folder you are now in as ````. 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 $ ./tools/register_plugin.sh . 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: https://docs.openstack.org/horizon/latest .. _quickstart tutorial: https://docs.openstack.org/horizon/latest/contributor/quickstart.html .. _installation guide: https://docs.openstack.org/horizon/latest/install/index.html DevStack setup -------------- Add the following to your DevStack ``local.conf`` file :: enable_plugin watcher-dashboard https://opendev.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. Policies -------- You can enable policies on Watcher ``Optimization`` panel, by updating in the ``/openstack_dashboard/settings.py`` configuration file the following parameters POLICY_FILES = { ... 'infra-optim': 'watcher_policy.json', } You can also update the file ``/openstack_dashboard/conf/watcher_policy.conf`` to customize your policies. Links ----- Watcher project: https://opendev.org/openstack/watcher/ Watcher at github: https://github.com/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: OFTC (https://www.oftc.net/) Channel: #openstack-watcher Or send an email to openstack-discuss@lists.openstack.org using [watcher] in object ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/manage.py0000775000175100017510000000150115033037765017025 0ustar00mylesmyles#!/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) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/pyproject.toml0000664000175100017510000000013315033037765020134 0ustar00mylesmyles[build-system] requires = ["pbr>=6.0.0", "setuptools>=64.0.0"] build-backend = "pbr.build" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.848164 watcher_dashboard-13.1.0.dev5/releasenotes/0000775000175100017510000000000015033037770017710 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.853164 watcher_dashboard-13.1.0.dev5/releasenotes/notes/0000775000175100017510000000000015033037770021040 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/notes/.placeholder0000664000175100017510000000000015033037765023315 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/notes/drop-py-2-7-198cca7f72d16655.yaml0000664000175100017510000000033315033037765025773 0ustar00mylesmyles--- upgrade: - | Python 2.7 support has been dropped. Last release of watcher-dashboard to support py2.7 is OpenStack Train. The minimum version of Python now supported by watcher-dashboard is Python 3.6. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924727.0 watcher_dashboard-13.1.0.dev5/releasenotes/notes/reno.cache0000664000175100017510000000674015033037767023005 0ustar00mylesmyles--- dates: - date: 1712142431 version: 11.0.0 - date: 1682693145 version: rocky-eol - date: 1486059805 version: 1.0.0 - date: 1532625661 version: 1.10.0 - date: 1602671726 version: 4.0.0 - date: 1587634969 version: 3.0.0.0rc1 - date: 1670246470 version: queens-eol - date: 1600783924 version: 4.0.0.0rc1 - date: 1616415191 version: 5.0.0.0rc1 - date: 1731429823 version: 2023.1-eom - date: 1472639936 version: 0.4.0 - date: 1492096674 version: 1.1.0 - date: 1555432378 version: ocata-em - date: 1459845438 version: 0.2.0 - date: 1648641394 version: 7.0.0 - date: 1569401229 version: 2.0.0.0rc1 - date: 1633519489 version: 6.0.0 - date: 1623940695 version: ocata-eol - date: 1618398868 version: 5.0.0 - date: 1512754513 version: 1.6.0 - date: 1589367576 version: 3.0.0 - date: 1518110759 version: 1.8.0 - date: 1457583463 version: 0.1.1 - date: 1485449679 version: 0.8.0 - date: 1646914395 version: 7.0.0.0rc1 - date: 1663072197 version: 8.0.0.0rc1 - date: 1745841093 version: 2023.2-eol - date: 1455811512 version: 0.0.1 - date: 1679488841 version: 9.0.0 - date: 1733853538 version: victoria-eol - date: 1709669559 version: victoria-eom - date: 1742205499 version: 13.0.0.0rc1 - date: 1707148826 version: yoga-eom - date: 1715166004 version: zed-eom - date: 1533737414 version: 1.11.0 - date: 1524240588 version: 1.9.0 - date: 1733853712 version: zed-eol - date: 1496921896 version: 1.2.0 - date: 1479389644 version: 0.6.0 - date: 1481832327 version: 0.7.0 - date: 1727866412 version: 12.0.0 - date: 1710512453 version: 11.0.0.0rc1 - date: 1643024637 version: 4.0.1 - date: 1708356063 version: ussuri-eol - date: 1621603520 version: train-em - date: 1703161839 version: train-eol - date: 1571229723 version: 2.0.0 - date: 1733853672 version: yoga-eol - date: 1677590872 version: 9.0.0.0rc1 - date: 1457110054 version: 0.1.0 - date: 1605782863 version: stein-em - date: 1696418086 version: 10.0.0 - date: 1709669586 version: wallaby-eom - date: 1667589661 version: wallaby-em - date: 1509109651 version: 1.5.0 - date: 1664972493 version: 8.0.0 - date: 1628602669 version: 3.0.1 - date: 1582819995 version: rocky-em - date: 1651147865 version: victoria-em - date: 1726482273 version: 12.0.0.0rc1 - date: 1681465923 version: xena-em - date: 1743591427 version: 13.0.0 - date: 1658393387 version: 5.0.1 - date: 1475098071 version: 0.5.0 - date: 1733853630 version: xena-eol - date: 1553093351 version: 1.12.0 - date: 1502413761 version: 1.4.0 - date: 1501162432 version: 1.3.0 - date: 1694654601 version: 10.0.0.0rc1 - date: 1572312564 version: queens-em - date: 1516816752 version: 1.7.0 - date: 1694093101 version: stein-eol - date: 1659345334 version: pike-eol - date: 1555537665 version: 1.4.1 - date: 1639144035 version: ussuri-em - date: 1709669610 version: xena-eom - date: 1555970566 version: pike-em - date: 1464625107 version: 0.3.0 - date: 1631724822 version: 6.0.0.0rc1 - date: 1733853578 version: wallaby-eol file-contents: releasenotes/notes/drop-py-2-7-198cca7f72d16655.yaml: upgrade: - 'Python 2.7 support has been dropped. Last release of watcher-dashboard to support py2.7 is OpenStack Train. The minimum version of Python now supported by watcher-dashboard is Python 3.6. ' notes: - files: - - releasenotes/notes/drop-py-2-7-198cca7f72d16655.yaml - !!binary | OWU5ZDQ2NDExYzliMDYyZDRlYjFiM2UyNWUxYjg2NGJjYjQwYmYxOA== version: 3.0.0 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.855164 watcher_dashboard-13.1.0.dev5/releasenotes/source/0000775000175100017510000000000015033037770021210 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/2023.1.rst0000664000175100017510000000021015033037765022464 0ustar00mylesmyles=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: unmaintained/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/2023.2.rst0000664000175100017510000000020215033037765022466 0ustar00mylesmyles=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/2024.1.rst0000664000175100017510000000020215033037765022466 0ustar00mylesmyles=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/2024.2.rst0000664000175100017510000000020215033037765022467 0ustar00mylesmyles=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/2025.1.rst0000664000175100017510000000020215033037765022467 0ustar00mylesmyles=========================== 2025.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2025.1 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.855164 watcher_dashboard-13.1.0.dev5/releasenotes/source/_static/0000775000175100017510000000000015033037770022636 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/_static/.placeholder0000664000175100017510000000000015033037765025113 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.855164 watcher_dashboard-13.1.0.dev5/releasenotes/source/_templates/0000775000175100017510000000000015033037770023345 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/_templates/.placeholder0000664000175100017510000000000015033037765025622 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/conf.py0000664000175100017510000002176615033037765022527 0ustar00mylesmyles# 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. # Watcher Release Notes documentation build configuration file, created by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Watcher Dashboard Release Notes' copyright = u'2017, Watcher Developers' # Release notes are version independent. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # openstackdocstheme options openstackdocs_repo_name = 'openstack/watcher-dashboard' openstackdocs_auto_name = False openstackdocs_bug_project = 'watcher-dashboard' openstackdocs_bug_tag = '' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {"show_other_versions": "True"} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'WatcherDashboardReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'WatcherDashboardReleaseNotes.tex', u'Watcher Dashboard Release Notes Documentation', u'Watcher Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'watcherdashboardreleasenotes', u'Watcher Dashboard Release Notes Documentation', [u'Watcher Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'WatcherDashboardReleaseNotes', u'Watcher Dashboard Release Notes Documentation', u'Watcher Developers', 'WatcherDashboardReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/index.rst0000664000175100017510000000045515033037765023061 0ustar00mylesmyles=============================== Watcher Dashboard Release Notes =============================== .. toctree:: :maxdepth: 1 unreleased 2025.1 2024.2 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ocata ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.848164 watcher_dashboard-13.1.0.dev5/releasenotes/source/locale/0000775000175100017510000000000015033037770022447 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.848164 watcher_dashboard-13.1.0.dev5/releasenotes/source/locale/en_GB/0000775000175100017510000000000015033037770023421 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.855164 watcher_dashboard-13.1.0.dev5/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175100017510000000000015033037770025206 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000664000175100017510000000177315033037765030253 0ustar00mylesmyles# Andi Chandler , 2017. #zanata # Andi Chandler , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: watcher-dashboard\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-08-15 08:11+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-08-17 09:34+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "Current Series Release Notes" msgstr "Current Series Release Notes" msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release Notes" msgid "Queens Series Release Notes" msgstr "Queens Series Release Notes" msgid "Rocky Series Release Notes" msgstr "Rocky Series Release Notes" msgid "Watcher Dashboard Release Notes" msgstr "Watcher Dashboard Release Notes" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.849164 watcher_dashboard-13.1.0.dev5/releasenotes/source/locale/ko_KR/0000775000175100017510000000000015033037770023454 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.855164 watcher_dashboard-13.1.0.dev5/releasenotes/source/locale/ko_KR/LC_MESSAGES/0000775000175100017510000000000015033037770025241 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po0000664000175100017510000000126515033037765030302 0ustar00mylesmyles# minwook-shin , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: Watcher Dashboard Release Notes\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-03-01 11:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-07-31 01:05+0000\n" "Last-Translator: minwook-shin \n" "Language-Team: Korean (South Korea)\n" "Language: ko_KR\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "Current Series Release Notes" msgstr "현재 시리즈 릴리즈 노트" msgid "Ocata Series Release Notes" msgstr "Ocata 시리즈 릴리즈 노트" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/ocata.rst0000664000175100017510000000023015033037765023030 0ustar00mylesmyles=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/pike.rst0000664000175100017510000000021715033037765022676 0ustar00mylesmyles=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/queens.rst0000664000175100017510000000022315033037765023243 0ustar00mylesmyles=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/rocky.rst0000664000175100017510000000022115033037765023070 0ustar00mylesmyles=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/stein.rst0000664000175100017510000000022115033037765023063 0ustar00mylesmyles=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/train.rst0000664000175100017510000000017615033037765023067 0ustar00mylesmyles========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/unreleased.rst0000664000175100017510000000016015033037765024072 0ustar00mylesmyles============================== Current Series Release Notes ============================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/ussuri.rst0000664000175100017510000000020215033037765023272 0ustar00mylesmyles=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/victoria.rst0000664000175100017510000000022015033037765023560 0ustar00mylesmyles============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/wallaby.rst0000664000175100017510000000021415033037765023376 0ustar00mylesmyles============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/xena.rst0000664000175100017510000000020015033037765022671 0ustar00mylesmyles========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/yoga.rst0000664000175100017510000000020015033037765022675 0ustar00mylesmyles========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/releasenotes/source/zed.rst0000664000175100017510000000017415033037765022532 0ustar00mylesmyles======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/requirements.txt0000664000175100017510000000053515033037765020512 0ustar00mylesmyles# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. pbr!=2.1.0,>=2.0.0 # Apache-2.0 horizon>=18.2.0 # Apache-2.0 PyYAML>=3.12 # MIT python-watcherclient>=1.1.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/run_tests.sh0000775000175100017510000004313615033037765017617 0ustar00mylesmyles#!/bin/bash set -o errexit function usage { echo "Usage: $0 [OPTION]..." echo "Run Horizon's test suite(s)" echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically" echo " if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local" echo " environment" echo " -c, --coverage Generate reports using Coverage" echo " -f, --force Force a clean re-build of the virtual" echo " environment. Useful when dependencies have" echo " been added." echo " -m, --manage Run a Django management command." echo " --makemessages Create/Update English translation files." echo " --compilemessages Compile all translation files." echo " --check-only Do not update translation files (--makemessages only)." echo " --pseudo Pseudo translate a language." echo " -p, --pep8 Just run pep8" echo " -8, --pep8-changed []" echo " Just run PEP8 and HACKING compliance check" echo " on files changed since HEAD~1 (or )" echo " -P, --no-pep8 Don't run pep8 by default" echo " -t, --tabs Check for tab characters in files." echo " -y, --pylint Just run pylint" echo " -e, --eslint Just run eslint" echo " -k, --karma Just run karma" echo " -q, --quiet Run non-interactively. (Relatively) quiet." echo " Implies -V if -N is not set." echo " --only-selenium Run only the Selenium unit tests" echo " --with-selenium Run unit tests including Selenium tests" echo " --selenium-headless Run Selenium tests headless" echo " --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 gettext_noop -k gettext_lazy -k ngettext_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 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.864164 watcher_dashboard-13.1.0.dev5/setup.cfg0000664000175100017510000000205115033037770017036 0ustar00mylesmyles[metadata] name = watcher-dashboard summary = Watcher Management Dashboard description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/watcher-dashboard/latest python_requires = >=3.9 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 :: Implementation :: CPython Programming Language :: Python :: 3 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Topic :: Internet :: WWW/HTTP [files] packages = watcher_dashboard [nosetests] verbosity = 2 detailed-errors = 1 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/setup.py0000664000175100017510000000127115033037765016736 0ustar00mylesmyles# 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. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/test-requirements.txt0000664000175100017510000000052015033037765021461 0ustar00mylesmyles# Hacking already pins down pep8, pyflakes and flake8 hacking>=7.0.0,<7.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD selenium>=2.50.1 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT # This also needs xvfb library installed on your OS xvfbwrapper>=0.1.3 #license: MIT ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.855164 watcher_dashboard-13.1.0.dev5/tools/0000775000175100017510000000000015033037770016357 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/tools/install_venv.py0000664000175100017510000000453415033037765021447 0ustar00mylesmyles# 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: $ . %s/bin/activate Or, if you prefer, you can run commands in the virtualenv on a case by case basis by running: $ %s/tools/with_venv.sh Also, make test will automatically use the virtualenv. """ print(help % (venv, root)) def main(argv): root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) if os.environ.get('tools_path'): root = os.environ['tools_path'] venv = os.path.join(root, '.venv') if os.environ.get('venv'): venv = os.environ['venv'] pip_requires = os.path.join(root, 'requirements.txt') test_requires = os.path.join(root, 'test-requirements.txt') py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) project = 'OpenStack' install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, py_version, project) options = install.parse_args(argv) install.check_python_version() install.check_dependencies() install.create_virtualenv(no_site_packages=options.no_site_packages) install.install_dependencies() print_help(venv, root) if __name__ == '__main__': main(sys.argv) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/tools/install_venv_common.py0000664000175100017510000001343715033037765023021 0ustar00mylesmyles# 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 """ 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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/tools/register_plugin.sh0000775000175100017510000000143315033037765022125 0ustar00mylesmyles#!/bin/bash src_path=`cd "$1"; pwd` dest_path=`cd "$2"; pwd` # echo "$src_path --> $dest_path" for filepath in $src_path/watcher_dashboard/local/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 policy_file_name='watcher_policy.json' src_policy_filepath=$src_path'/watcher_dashboard/conf/'$policy_file_name dest_policy_file=$dest_path'/openstack_dashboard/conf/'$policy_file_name echo "$src_policy_filepath --> $dest_policy_file" ln -s $src_policy_filepath $dest_policy_file ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/tools/with_venv.sh0000775000175100017510000000077215033037765020741 0ustar00mylesmyles#!/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 && "$@" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/tox.ini0000664000175100017510000000443415033037765016543 0ustar00mylesmyles[tox] minversion = 3.18.0 envlist = py3,pep8 # Automatic envs (pyXX) will only use the python version appropriate to that # env and ignore basepython inherited from [testenv] if we set # ignore_basepython_conflict. ignore_basepython_conflict = True [testenv] basepython = python3 usedevelop = True setenv = 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 allowlist_externals = /bin/bash rm find install_command = pip install -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = rm -f .testrepository/times.dbm find . -type f -name "*.pyc" -delete find . -type d -name "__pycache__" -delete python manage.py test --settings=watcher_dashboard.test.settings \ --exclude-tag integration \ watcher_dashboard [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html [testenv:cover] commands = coverage erase coverage run --source watcher_dashboard {toxinidir}/manage.py test \ --settings=watcher_dashboard.test.settings \ --exclude-tag integration watcher_dashboard {posargs} coverage xml coverage html -d ./cover --omit='*tests*' coverage report [testenv:docs] deps = -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:pdf-docs] deps = {[testenv:docs]deps} allowlist_externals = rm make commands = rm -rf doc/build/pdf sphinx-build -W --keep-going -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:debug] commands = oslo_debug_helper {posargs} [flake8] # F405 TEMPLATES may be undefined, or defined from star imports # (because it is not easy to avoid this in openstack_dashboard.test.settings) # W504 line break after binary operator ignore = F405,W504 show-source = True builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,.ropeproject,tools ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.855164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/0000775000175100017510000000000015033037770020663 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/__init__.py0000664000175100017510000000000015033037765022766 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.856164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/api/0000775000175100017510000000000015033037770021434 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/api/__init__.py0000664000175100017510000000000015033037765023537 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/api/watcher.py0000664000175100017510000004063315033037765023455 0ustar00mylesmyles# 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.conf import settings from django.utils.translation import gettext_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) insert_watcher_policy_file() 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 def insert_watcher_policy_file(): policy_files = getattr(settings, 'POLICY_FILES', {}) policy_files['infra-optim'] = 'watcher_policy.json' setattr(settings, 'POLICY_FILES', policy_files) class Audit(base.APIDictWrapper): _attrs = ('uuid', 'name', 'created_at', 'modified_at', 'deleted_at', 'state', 'audit_type', 'audit_template_uuid', 'audit_template_name', 'interval') def __init__(self, apiresource, request=None): super(Audit, self).__init__(apiresource) self._request = request @classmethod def create(cls, request, audit_template_uuid, audit_type, name=None, auto_trigger=False, interval=None): """Create an audit in Watcher :param request: request object :type request: django.http.HttpRequest :param audit_template_uuid: related audit template UUID :type audit_template_uuid: string :param audit_type: audit type :type audit_type: string :param interval: Audit interval (default: None) :type interval: int :param name: Name for this audit :type name: string :return: the created Audit object :rtype: :py:class:`~.Audit` """ if interval: return watcherclient(request).audit.create( audit_template_uuid=audit_template_uuid, audit_type=audit_type, auto_trigger=auto_trigger, interval=interval, name=name) else: return watcherclient(request).audit.create( audit_template_uuid=audit_template_uuid, audit_type=audit_type, auto_trigger=auto_trigger, name=name) @classmethod def list(cls, request, **filters): """Return a list of audits in Watcher :param request: request object :type request: django.http.HttpRequest :param filters: key/value kwargs used as filters :type filters: dict :return: list of audits, or an empty list if there are none :rtype: list of :py:class:`~.Audit` """ return watcherclient(request).audit.list(detail=True, **filters) @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: :py:class:`~.Audit` """ return watcherclient(request).audit.get(audit=audit_id) @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 """ return watcherclient(request).audit.delete(audit=audit_id) @classmethod def cancel(cls, request, audit_id): """Cancel an audit :param request: request object :type request: django.http.HttpRequest :param audit_id: audit id :type audit_id: int """ cancel_patch = [{'op': 'replace', 'path': '/state', 'value': 'CANCELLED'}] return watcherclient(request).audit.update(audit=audit_id, patch=cancel_patch) @property def id(self): return self.uuid class AuditTemplate(base.APIDictWrapper): _attrs = ('uuid', 'description', 'scope', 'name', 'goal_uuid', 'goal_name', 'strategy_uuid', 'strategy_name', 'created_at', 'updated_at', 'deleted_at') def __init__(self, apiresource, request=None): super(AuditTemplate, self).__init__(apiresource) self._request = request @classmethod def create(cls, request, name, goal, strategy, description, scope): """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 UUID or name associated to this audit template :type goal: string :param strategy: Strategy UUID or name associated to this audit template :type strategy: string :param description: Descrition of the audit template :type description: string :param scope: Audit scope :type scope: list of list of dict :param audit_template: audit template :type audit_template: string :return: the created Audit Template object :rtype: :py:class:`~.AuditTemplate` """ audit_template = watcherclient(request).audit_template.create( name=name, goal=goal, strategy=strategy, description=description, scope=scope, ) 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: :py:class:`~.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, **filters): """Return a list of audit templates in Watcher :param request: request object :type request: django.http.HttpRequest :param filters: key/value kwargs used as filters :type filters: dict :return: list of audit templates, or an empty list if there are none :rtype: list of :py:class:`~.AuditTemplate` """ return watcherclient(request).audit_template.list( detail=True, **filters) @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: :py:class:`~.AuditTemplate` """ return watcherclient(request).audit_template.get( audit_template_id=audit_template_id) @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.APIDictWrapper): _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, **filters): """Return a list of action plans in Watcher :param request: request object :type request: django.http.HttpRequest :param filters: key/value kwargs used as filters :type filters: dict :return: list of action plans, or an empty list if there are none :rtype: list of :py:class:`~.ActionPlan` """ return watcherclient(request).action_plan.list(detail=True, **filters) @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: :py:class:`~.ActionPlan` """ return watcherclient(request).action_plan.get( action_plan_id=action_plan_id) @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 """ watcherclient(request).action_plan.start(action_plan_id) @property def id(self): return self.uuid class Action(base.APIDictWrapper): _attrs = ('uuid', 'created_at', 'updated_at', 'deleted_at', 'next_uuid', 'description', '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, **filters): """Return a list of actions in Watcher :param request: request object :type request: django.http.HttpRequest :param filters: key/value kwargs used as filters :type filters: dict :return: list of actions, or an empty list if there are none :rtype: list of :py:class:`~.Action` """ return watcherclient(request).action.list(detail=True, **filters) @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: :py:class:`~.Action` """ return watcherclient(request).action.get(action_id=action_id) @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': 'PENDING'}) watcherclient(request).action.update(action_id, patch) @property def id(self): return self.uuid class Goal(base.APIDictWrapper): """Goal resource.""" _attrs = ('uuid', 'name', 'display_name', 'created_at', 'updated_at', 'deleted_at', 'efficacy_specifications') def __init__(self, apiresource, request=None): super(Goal, self).__init__(apiresource) self._request = request @classmethod def list(cls, request, **filters): """Return a list of goals in Watcher :param request: request object :type request: django.http.HttpRequest :param filters: key/value kwargs used as filters :type filters: dict :return: list of goals, or an empty list if there are none :rtype: list of :py:class:`~.Goal` instance """ return watcherclient(request).goal.list(detail=True, **filters) @classmethod @errors_utils.handle_errors(_("Unable to retrieve goal")) def get(cls, request, goal): """Return the goal that matches the ID :param request: request object :type request: django.http.HttpRequest :param goal: uuid of goal to be retrieved :type goal: int :return: matching goal, or None if no goal matches the UUID :rtype: :py:class:`~.Goal` instance """ return watcherclient(request).goal.get(goal) @property def id(self): return self.uuid class Strategy(base.APIDictWrapper): """Strategy resource.""" _attrs = ('uuid', 'name', 'display_name', 'goal_uuid', 'goal_name', 'created_at', 'updated_at', 'deleted_at') def __init__(self, apiresource, request=None): super(Strategy, self).__init__(apiresource) self._request = request @classmethod def list(cls, request, **filters): """Return a list of strategies in Watcher :param request: request object :type request: django.http.HttpRequest :param filters: key/value kwargs used as filters :type filters: dict :return: list of strategies, or an empty list if there are none :rtype: list of :py:class:`~.Strategy` instances """ return watcherclient(request).strategy.list(detail=True, **filters) @classmethod @errors_utils.handle_errors(_("Unable to retrieve strategy")) def get(cls, request, strategy): """Return the strategy that matches the UUID :param request: request object :type request: django.http.HttpRequest :param strategy: uuid of strategy to be retrieved :type strategy: str :return: matching strategy, or None if no strategy matches the UUID :rtype: :py:class:`~.Strategy` instance """ return watcherclient(request).strategy.get(strategy) @property def id(self): return self.uuid class EfficacyIndicatorSpec(base.APIDictWrapper): attrs = ('name', 'description', 'unit', 'schema') class EfficacyIndicator(base.APIDictWrapper): def __init__(self, indicator): super(EfficacyIndicator, self).__init__(indicator) self.value = indicator.get('value', None) self.name = indicator.get('name', None) self.description = indicator.get('description', None) self.unit = indicator.get('unit', None) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.856164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/common/0000775000175100017510000000000015033037770022153 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/common/__init__.py0000664000175100017510000000000015033037765024256 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/common/exceptions.py0000664000175100017510000000150615033037765024714 0ustar00mylesmyles# 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 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.856164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/conf/0000775000175100017510000000000015033037770021610 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/conf/watcher_policy.json0000664000175100017510000000216215033037765025524 0ustar00mylesmyles{ "admin_api": "role:admin or role:administrator", "show_password": "!", "default": "rule:admin_api", "action:detail": "rule:default", "action:get": "rule:default", "action:get_all": "rule:default", "action_plan:create": "rule:default", "action_plan:detail": "rule:default", "action_plan:get": "rule:default", "action_plan:get_all": "rule:default", "action_plan:update": "rule:default", "audit:create": "rule:default", "audit:delete": "rule:default", "audit:detail": "rule:default", "audit:get": "rule:default", "audit:get_all": "rule:default", "audit:update": "rule:default", "audit_template:create": "rule:default", "audit_template:delete": "rule:default", "audit_template:detail": "rule:default", "audit_template:get": "rule:default", "audit_template:get_all": "rule:default", "audit_template:update": "rule:default", "goal:detail": "rule:default", "goal:get": "rule:default", "goal:get_all": "rule:default", "strategy:detail": "rule:default", "strategy:get": "rule:default", "strategy:get_all": "rule:default" } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.856164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/0000775000175100017510000000000015033037770022335 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/__init__.py0000664000175100017510000000000015033037765024440 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.856164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/action_plans/0000775000175100017510000000000015033037770025007 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/action_plans/__init__.py0000664000175100017510000000000015033037765027112 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/action_plans/panel.py0000664000175100017510000000142415033037765026465 0ustar00mylesmyles# 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 gettext_lazy as _ import horizon class ActionPlans(horizon.Panel): name = _("Action Plans") slug = "action_plans" permissions = ("openstack.services.infra-optim",) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/action_plans/tables.py0000664000175100017510000001740715033037765026650 0ustar00mylesmyles# 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 import template from django.template.defaultfilters import title # noqa from django import urls from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from django.utils.translation import pgettext_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")), ("SUCCEEDED", pgettext_lazy("State of an action plan", u"Succeeded")), ("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', _("Audit ="), True), ) policy_rules = (("infra-optim", "action_plan:detail"),) class ArchiveActionPlan(horizon.tables.DeleteAction): verbose_name = _("Archive Action Plans") policy_rules = (("infra-optim", "action_plan:delete"),) @staticmethod def action_present(count): return ngettext_lazy( u"Archive Action Plan", u"Archive Action Plans", count ) @staticmethod def action_past(count): return ngettext_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 = (("infra-optim", "action_plan:update"),) help_text = _("Execute an action plan.") @staticmethod def action_present(count): return ngettext_lazy( u"Start Action Plan", u"Start Action Plans", count ) @staticmethod def action_past(count): return ngettext_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 def format_global_efficacy(action_plan): template_name = 'infra_optim/action_plans/_global_efficacy.html' global_efficacy_dict = {} for indicator in action_plan.global_efficacy: global_efficacy = watcher.EfficacyIndicator(indicator) if (global_efficacy.value is not None and global_efficacy.unit is not None): global_efficacy_dict[global_efficacy.name] = ( f"{global_efficacy.value} {global_efficacy.unit}") elif global_efficacy.value is not None: global_efficacy_dict[global_efficacy.name] = str( global_efficacy.value) context = { "global_indicators": global_efficacy_dict, } return template.loader.render_to_string(template_name, context) def get_audit_link(datum): try: return urls.reverse( "horizon:admin:audits:detail", kwargs={"audit_uuid": getattr(datum, "audit_uuid", None)}) except urls.NoReverseMatch: return None class ActionPlansTable(horizon.tables.DataTable): name = horizon.tables.Column( 'uuid', verbose_name=_("UUID"), link="horizon:admin:action_plans:detail") audit = horizon.tables.Column( 'audit_uuid', verbose_name=_('Audit'), link=get_audit_link) 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) efficacy = horizon.tables.Column( transform=format_global_efficacy, verbose_name=_('Efficacy')) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "action_plans" verbose_name = _("Action Plans") table_actions = ( # CancelActionPlan, ActionPlansFilterAction, StartActionPlan, ArchiveActionPlan, ) row_actions = ( StartActionPlan, # CreateActionPlans, ArchiveActionPlan, # CreateActionPlans, # DeleteActionPlans, ) row_class = UpdateRow class RelatedActionPlansTable(horizon.tables.DataTable): name = horizon.tables.Column( 'uuid', verbose_name=_("UUID"), link="horizon:admin:action_plans:detail") audit = horizon.tables.Column( 'audit_uuid', verbose_name=_('Audit'), link=get_audit_link) 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) efficacy = horizon.tables.Column( transform=format_global_efficacy, verbose_name=_('Efficacy')) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "related_action_plans" verbose_name = _("Related Action Plans") hidden_title = False row_actions = ( StartActionPlan, ArchiveActionPlan, ) row_class = UpdateRow class RelatedEfficacyIndicatorsTable(horizon.tables.DataTable): name = horizon.tables.Column( 'name', verbose_name=_("Name")) description = horizon.tables.Column( 'description', verbose_name=_("Description")) unit = horizon.tables.Column( 'unit', verbose_name=_("Unit")) value = horizon.tables.Column( 'value', verbose_name=_("Value")) def get_object_id(self, datum): return datum.name class Meta(object): name = "related_efficacy_indicators" verbose_name = _("Related Efficacy Indicators") hidden_title = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/action_plans/tabs.py0000664000175100017510000000201215033037765026311 0ustar00mylesmyles# 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 gettext_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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/action_plans/urls.py0000664000175100017510000000166615033037765026363 0ustar00mylesmyles# 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.urls import re_path from watcher_dashboard.content.action_plans import views urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(r'^(?P[^/]+)/detail$', views.DetailView.as_view(), name='detail'), re_path(r'^archive/$', views.ArchiveView.as_view(), name='archive'), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/action_plans/views.py0000664000175100017510000001167615033037765026535 0ustar00mylesmyles# 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 gettext_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' page_title = _("Action Plans") 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, **search_opts) except Exception as exc: LOG.exception(exc) 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): filters = {} 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: filters[filter_field] = filter_string return filters 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.RelatedActionsTable, tables.RelatedEfficacyIndicatorsTable) 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_uuid = None try: action_plan_uuid = self.kwargs['action_plan_uuid'] action_plan = watcher.ActionPlan.get( self.request, action_plan_uuid) except Exception as exc: LOG.exception(exc) msg = _('Unable to retrieve details for action_plan "%s".') \ % action_plan_uuid horizon.exceptions.handle( self.request, msg, redirect=self.redirect_url) return action_plan def get_related_wactions_data(self): try: action_plan = self._get_data() actions = watcher.Action.list(self.request, action_plan=action_plan.uuid) except Exception as exc: LOG.exception(exc) actions = [] msg = _('Action list can not be retrieved.') horizon.exceptions.handle(self.request, msg) return actions def get_related_efficacy_indicators_data(self): try: action_plan = self._get_data() efficacy_indicators = [ watcher.EfficacyIndicator(indicator) for indicator in action_plan.efficacy_indicators] except Exception as exc: LOG.exception(exc) msg = _('Failed to get the efficacy indicators: %s') % str(exc) LOG.info(msg) horizon.messages.warning(self.request, msg) return efficacy_indicators 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(action_plan) LOG.info('*********************************') return context def get_tabs(self, request, *args, **kwargs): action_plan = self._get_data() return self.tab_group_class( request, action_plan=action_plan, **kwargs) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1751924727.8571641 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/actions/0000775000175100017510000000000015033037770023775 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/actions/__init__.py0000664000175100017510000000000015033037765026100 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/actions/panel.py0000664000175100017510000000140715033037765025454 0ustar00mylesmyles# 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 gettext_lazy as _ import horizon class Actions(horizon.Panel): name = _("Actions ") slug = "actions" permissions = ("openstack.services.infra-optim",) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/actions/tables.py0000664000175100017510000001064615033037765025634 0ustar00mylesmyles# 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 import urls from django.utils.translation import gettext_lazy as _ from django.utils.translation import pgettext_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_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")), ("SUCCEEDED", pgettext_lazy("Power state of an Instance", u"Succeeded")), ("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),) policy_rules = (("infra-optim", "action:detail"),) def get_action_plan_link(datum): try: return urls.reverse( "horizon:admin:action_plans:detail", kwargs={"action_plan_uuid": getattr( datum, "action_plan_uuid", None)}) except urls.NoReverseMatch: return None class ActionsTable(horizon.tables.DataTable): name = horizon.tables.Column( 'uuid', verbose_name=_("UUID"), link="horizon:admin:actions:detail") action_type = horizon.tables.Column( 'action_type', verbose_name=_('Type'), filters=(title, filters.replace_underscores)) state = horizon.tables.Column( 'state', verbose_name=_('State'), status_choices=ACTION_STATE_DISPLAY_CHOICES) action_plan = horizon.tables.Column( 'action_plan_uuid', verbose_name=_('Action Plan'), link=get_action_plan_link) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "wactions" verbose_name = _("Actions") table_actions = (ActionsFilterAction, ) row_class = UpdateRow class RelatedActionsTable(horizon.tables.DataTable): """Identical to the index table but with different Meta""" name = horizon.tables.Column( 'uuid', verbose_name=_("UUID"), link="horizon:admin:actions:detail") action_type = horizon.tables.Column( 'action_type', verbose_name=_('Type'), filters=(title, filters.replace_underscores)) state = horizon.tables.Column( 'state', verbose_name=_('State'), status_choices=ACTION_STATE_DISPLAY_CHOICES) action_plan = horizon.tables.Column( 'action_plan_uuid', verbose_name=_('Action Plan'), link=get_action_plan_link) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "related_wactions" verbose_name = _("Related Actions") hidden_title = False class ActionParametersTable(horizon.tables.DataTable): name = horizon.tables.Column( 'name', verbose_name=_("Parameter name")) value = horizon.tables.Column( 'value', verbose_name=_('Parameter value')) def get_object_id(self, datum): return datum.name class Meta(object): name = "parameters" verbose_name = _("Related parameters") hidden_title = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/actions/tabs.py0000664000175100017510000000176115033037765025311 0ustar00mylesmyles# 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 gettext_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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/actions/urls.py0000664000175100017510000000152715033037765025345 0ustar00mylesmyles# 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.urls import re_path from watcher_dashboard.content.actions import views urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(r'^(?P[^/]+)/detail$', views.DetailView.as_view(), name='detail'), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/actions/views.py0000664000175100017510000000721315033037765025513 0ustar00mylesmyles# 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 collections from django.utils.translation import gettext_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' page_title = _("Actions") 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, **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): filters = {} 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: filters[filter_field] = filter_string return filters class DetailView(horizon.tables.MultiTableView): table_classes = [tables.ActionParametersTable] 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_uuid = None try: action_uuid = self.kwargs['action_uuid'] action = watcher.Action.get(self.request, action_uuid) except Exception: msg = _('Unable to retrieve details for action "%s".') \ % action_uuid horizon.exceptions.handle( self.request, msg, redirect=self.redirect_url) return action def get_parameters_data(self): action = self._get_data() parameter_cls = collections.namedtuple( 'Parameter', field_names=['name', 'value']) return [parameter_cls(name=name, value=value) for name, value in action.input_parameters.items()] 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1751924727.8571641 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audit_templates/0000775000175100017510000000000015033037770025521 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audit_templates/__init__.py0000664000175100017510000000000015033037765027624 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audit_templates/forms.py0000664000175100017510000001063315033037765027230 0ustar00mylesmyles# 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 import exceptions as core_exc from django.urls import reverse from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import messages import yaml from watcher_dashboard.api import watcher LOG = logging.getLogger(__name__) class YamlValidator(object): message = _('Enter a valid YAML or JSON value.') code = 'invalid' def __init__(self, message=None): if message: self.message = message def __call__(self, value): try: yaml.safe_load(value) except Exception: raise core_exc.ValidationError(self.message, code=self.code) 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')) strategy = forms.DynamicChoiceField(label=_('Strategy'), required=False) scope = forms.CharField( label=_('Scope'), required=False, widget=forms.widgets.Textarea, validators=[YamlValidator()]) 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) strategies = self._get_strategy_list(request, goals) if goals: self.fields['goal'].choices = goals else: del self.fields['goal'] if strategies: self.fields['strategy'].choices = strategies else: del self.fields['strategy'] def _get_goal_list(self, request): try: goals = watcher.Goal.list(self.request) except Exception as exc: msg = _('Failed to get goals list: %s') % str(exc) LOG.info(msg) messages.warning(request, msg) messages.warning(request, exc) goals = [] choices = [ (goal.uuid, goal.display_name) for goal in goals ] if choices: choices.insert(0, ("", _("Select Goal"))) return choices def _get_strategy_list(self, request, goals): try: strategies = watcher.Strategy.list(self.request) except Exception as exc: msg = _('Failed to get the list of available strategies.') LOG.info(msg) messages.warning(request, msg) messages.warning(request, exc) strategies = [] _goals = {} for goal in goals: _goals[goal[0]] = goal[1] choices = [ (strategy.uuid, strategy.display_name + ' (GOAL: ' + _goals[strategy.goal_uuid] + ')') for strategy in strategies ] if choices: choices.insert(0, ("", _("Select Strategy"))) return choices def handle(self, request, data): try: params = {'name': data['name']} params['description'] = data['description'] params['goal'] = data['goal'] params['strategy'] = data['strategy'] or None params['scope'] = [] if not data['scope'] else yaml.safe_load( data['scope']) audit_tpl = watcher.AuditTemplate.create(request, **params) message = _('Audit Template was successfully created.') messages.success(request, message) return audit_tpl except Exception as exc: msg = _('Failed to create audit template.') LOG.info(exc) redirect = reverse(self.failure_url) exceptions.handle(request, msg, redirect=redirect) return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audit_templates/panel.py0000664000175100017510000000143515033037765027201 0ustar00mylesmyles# 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 gettext_lazy as _ import horizon class AuditTemplates(horizon.Panel): name = _("Audit Templates") slug = "audit_templates" permissions = ("openstack.services.infra-optim",) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audit_templates/tables.py0000664000175100017510000000707115033037765027356 0ustar00mylesmyles# 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 gettext_lazy as _ from django.utils.translation import ngettext_lazy import horizon.exceptions import horizon.messages import horizon.tables from watcher_dashboard.api import watcher class CreateAuditTemplates(horizon.tables.LinkAction): name = "create" verbose_name = _("Create Template") url = "horizon:admin:audit_templates:create" classes = ("ajax-modal", "btn-launch") policy_rules = (("infra-optim", "audit_template:create"),) class AuditTemplatesFilterAction(horizon.tables.FilterAction): filter_type = "server" filter_choices = ( ('goal', _("Goal ="), True), ('strategy', _("Strategy ="), True), ) policy_rules = (("infra-optim", "audit_template:detail"),) 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" policy_rules = (("infra-optim", "audit:create"),) @staticmethod def action_present(count): return ngettext_lazy( "Launch Audit", "Launch Audits", count ) @staticmethod def action_past(count): return ngettext_lazy( "Launched Audit", "Launched Audits", count ) def action(self, request, obj_id): params = {'audit_template_uuid': obj_id} params['audit_type'] = 'ONESHOT' params['auto_trigger'] = False watcher.Audit.create(request, **params) class ArchiveAuditTemplates(horizon.tables.DeleteAction): verbose_name = _("Archive Templates") policy_rules = (("infra-optim", "audit_template:delete"),) @staticmethod def action_present(count): return ngettext_lazy( "Archive Template", "Archive Templates", count ) @staticmethod def action_past(count): return ngettext_lazy( "Archived Template", "Archived 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_name', verbose_name=_('Goal'), status=True, ) strategy = horizon.tables.Column( 'strategy_name', verbose_name=_('Strategy'), status=True, ) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "audit_templates" verbose_name = _("Audit Templates") table_actions = ( CreateAuditTemplates, AuditTemplatesFilterAction, LaunchAudit, ArchiveAuditTemplates, ) row_actions = ( LaunchAudit, ArchiveAuditTemplates, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audit_templates/tabs.py0000664000175100017510000000203015033037765027023 0ustar00mylesmyles# 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 gettext_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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audit_templates/tests.py0000664000175100017510000001051415033037765027242 0ustar00mylesmyles# 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 unittest import mock from django import urls from watcher_dashboard import api from watcher_dashboard.test import helpers as test INDEX_URL = urls.reverse( 'horizon:admin:audit_templates:index') CREATE_URL = urls.reverse( 'horizon:admin:audit_templates:create') DETAILS_VIEW = 'horizon:admin:audit_templates:detail' class AuditTemplatesTest(test.BaseAdminViewTests): def setUp(self): super(AuditTemplatesTest, self).setUp() self.goal_list = self.goals.list() self.strategy_list = self.strategies.list() @mock.patch.object(api.watcher.AuditTemplate, 'list') def test_index(self, mock_list): mock_list.return_value = self.audit_templates.list() res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'infra_optim/audit_templates/index.html') audit_templates = res.context['audit_templates_table'].data self.assertCountEqual(audit_templates, self.audit_templates.list()) @mock.patch.object(api.watcher.AuditTemplate, 'list') def test_audit_template_list_unavailable(self, mock_list): mock_list.side_effect = self.exceptions.watcher resp = self.client.get(INDEX_URL) self.assertMessageCount(resp, error=1, warning=0) @mock.patch.object(api.watcher.Strategy, 'list') @mock.patch.object(api.watcher.Goal, 'list') def test_create_get(self, m_goal_list, m_strategy_list): m_goal_list.return_value = self.goal_list m_strategy_list.return_value = self.strategy_list res = self.client.get(CREATE_URL) self.assertTemplateUsed(res, 'infra_optim/audit_templates/create.html') @mock.patch.object(api.watcher.Strategy, 'list') @mock.patch.object(api.watcher.Goal, 'list') @mock.patch.object(api.watcher.AuditTemplate, 'create') def test_create_post(self, m_audit_create, m_goal_list, m_strategy_list): at = self.audit_templates.first() form_data = { 'name': at.name, 'goal': at.goal_uuid, 'strategy': at.strategy_uuid, 'description': at.description, 'scope': at.scope, } m_goal_list.return_value = self.goal_list m_strategy_list.return_value = self.strategy_list m_audit_create.return_value = at res = self.client.post(CREATE_URL, form_data) self.assertNoFormErrors(res) self.assertRedirectsNoFollow(res, INDEX_URL) @mock.patch.object(api.watcher.AuditTemplate, 'get') def test_details(self, m_get): at = self.audit_templates.first() at_id = at.uuid m_get.return_value = at DETAILS_URL = urls.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.assertCountEqual([audit_templates], [at]) @mock.patch.object(api.watcher.AuditTemplate, 'get') def test_details_exception(self, m_get): at = self.audit_templates.first() at_id = at.uuid m_get.side_effect = self.exceptions.watcher DETAILS_URL = urls.reverse(DETAILS_VIEW, args=[at_id]) res = self.client.get(DETAILS_URL) self.assertRedirectsNoFollow(res, INDEX_URL) @mock.patch.object(api.watcher.AuditTemplate, 'delete') @mock.patch.object(api.watcher.AuditTemplate, 'list') def test_delete(self, m_list, m_del): at_list = self.audit_templates.list() at = self.audit_templates.first() at_id = at.uuid m_list.return_value = at_list form_data = {'action': 'audit_templates__delete', 'object_ids': at_id} res = self.client.post(INDEX_URL, form_data) self.assertRedirectsNoFollow(res, INDEX_URL) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audit_templates/urls.py0000664000175100017510000000165515033037765027073 0ustar00mylesmyles# 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.urls import re_path from watcher_dashboard.content.audit_templates import views urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(r'^create/$', views.CreateView.as_view(), name='create'), re_path(r'^(?P[^/]+)/detail$', views.DetailView.as_view(), name='detail'), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audit_templates/views.py0000664000175100017510000001135315033037765027237 0ustar00mylesmyles# 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 json import logging from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ import horizon.exceptions from horizon import forms import horizon.tables import horizon.tabs import horizon.workflows 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, **search_opts) except Exception as exc: LOG.exception(exc) 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): filters = {} 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: filters[filter_field] = filter_string return filters 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") def get_object_id(self, obj): return obj.uuid 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_uuid = None try: LOG.info(self.kwargs) audit_template_uuid = self.kwargs['audit_template_uuid'] audit_template = watcher.AuditTemplate.get( self.request, audit_template_uuid) if audit_template.scope: audit_template.scope = json.dumps(audit_template.scope) except Exception as exc: LOG.exception(exc) msg = _('Unable to retrieve details for audit template "%s".') \ % audit_template_uuid horizon.exceptions.handle( self.request, msg, redirect=self.redirect_url) return audit_template def get_related_audits_data(self): try: audit_template = self._get_data() audits = watcher.Audit.list( self.request, audit_template=audit_template.uuid) except Exception as exc: LOG.exception(exc) audits = [] msg = _('Audits list cannot be retrieved.') horizon.exceptions.handle(self.request, msg) return audits 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, **kwargs) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.858164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audits/0000775000175100017510000000000015033037770023626 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audits/__init__.py0000664000175100017510000000000015033037765025731 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audits/forms.py0000664000175100017510000001170315033037765025334 0ustar00mylesmyles# 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.urls import reverse from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import messages from watcher_dashboard.api import watcher LOG = logging.getLogger(__name__) ADD_AUDIT_TEMPLATES_URL = "horizon:admin:audit_templates:create" class CreateForm(forms.SelfHandlingForm): audit_template = forms.DynamicChoiceField( label=_("Audit Template"), add_item_link=ADD_AUDIT_TEMPLATES_URL) audit_name = forms.CharField(max_length=255, label=_("Name"), help_text=_("An audit name should not " "duplicate with existed audits' names."), required=False) audit_type = forms.ChoiceField(label=_("Audit Type"), choices=[(None, _("Select Audit Type")), ('oneshot', _('ONESHOT')), ('continuous', _('CONTINUOUS'))], widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'audit_type' })) interval = forms.CharField(label=_("Interval (in seconds or cron format)"), help_text=_("Interval in seconds or cron" "format for CONTINUOUS audit"), widget=forms.TextInput(attrs={ 'class': 'switched', 'data-switch-on': 'audit_type', 'data-audit_type-continuous': _("Interval (in seconds or cron" " format)")}), required=False) failure_url = 'horizon:admin:audits:index' auto_trigger = forms.BooleanField(label=_("Auto Trigger"), required=False) def __init__(self, request, *args, **kwargs): super(CreateForm, self).__init__(request, *args, **kwargs) audit_templates = self._get_audit_template_list(request) self.fields['audit_template'].choices = audit_templates def _get_audit_template_list(self, request): try: audit_templates = watcher.AuditTemplate.list(self.request) except Exception as e: msg = _('Failed to get audit template list: %s') % str(e) 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"))) else: choices.insert(0, ("", _("No Audit Template found"))) return choices def clean(self): cleaned_data = super(CreateForm, self).clean() audit_type = cleaned_data.get('audit_type') if audit_type == 'continuous' and not cleaned_data.get('interval'): msg = _('Please input an interval for continuous audit') raise forms.ValidationError(msg) return cleaned_data def handle(self, request, data): try: params = {'audit_template_uuid': data.get('audit_template')} params['audit_type'] = data['audit_type'].upper() params['auto_trigger'] = data['auto_trigger'] params['name'] = data['audit_name'] if data['audit_type'] == 'continuous': params['interval'] = data['interval'] else: params['interval'] = None audit = watcher.Audit.create(request, **params) message = _('Audit was successfully created.') messages.success(request, message) return audit except Exception as exc: if getattr(exc, 'http_status', None) == 409: msg = _('Error: Audit name already exists.') else: msg = _('Failed to create audit.') LOG.info(exc) redirect = reverse(self.failure_url) exceptions.handle(request, msg, redirect=redirect) return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audits/panel.py0000664000175100017510000000140315033037765025301 0ustar00mylesmyles# 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 gettext_lazy as _ import horizon class Audits(horizon.Panel): name = _("Audits") slug = "audits" permissions = ('openstack.services.infra-optim',) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audits/tables.py0000664000175100017510000001366415033037765025470 0ustar00mylesmyles# 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 import shortcuts from django.template.defaultfilters import title # noqa from django import urls from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from django.utils.translation import pgettext_lazy import horizon.exceptions import horizon.messages import horizon.tables from horizon.utils import filters from watcher_dashboard.api import watcher 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")), ("SUCCEEDED", pgettext_lazy("State of an audit", u"Succeeeded")), ("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', _("Audit Template ="), True), ) policy_rules = (("infra-optim", "audit:detail"),) class CreateAudit(horizon.tables.LinkAction): name = "create_audit" verbose_name = _("Create Audit") url = "horizon:admin:audits:create" classes = ("ajax-modal", "btn-launch") policy_rules = (("infra-optim", "audit:create"),) class GoToActionPlan(horizon.tables.Action): name = "go_to_action_plan" verbose_name = _("Go to Action Plan") url = "horizon:admin:action_plans:detail" policy_rules = (("infra-optim", "action_plan:detail"),) def allowed(self, request, audit): return audit or audit.state in ("SUCCEEDED", ) def single(self, table, request, audit_id): try: action_plans = watcher.ActionPlan.list( request, audit=audit_id) except Exception: horizon.exceptions.handle( request, _("Unable to retrieve action_plan information.")) return "javascript:void(0);" return shortcuts.redirect(urls.reverse( self.url, args=[action_plans[0].uuid])) class ArchiveAudits(horizon.tables.DeleteAction): verbose_name = _("Archive Audits") policy_rules = (("infra-optim", "audit:delete"),) def allowed(self, request, audit): return audit.state not in ("ONGOING", "PENDING") @staticmethod def action_present(count): return ngettext_lazy( "Archive Audit", "Archive Audits", count ) @staticmethod def action_past(count): return ngettext_lazy( "Archived Audit", "Archived Audits", count ) def delete(self, request, obj_id): watcher.Audit.delete(request, obj_id) class CancelAudits(horizon.tables.BatchAction): name = _("Cancel") verbose_name = _("Cancel Audits") policy_rules = (("infra-optim", "audit:update"),) def allowed(self, request, audit): return audit.state in ("ONGOING", "PENDING") @staticmethod def action_present(count): return ngettext_lazy( "Cancel Audit", "Cancel Audits", count ) @staticmethod def action_past(count): return ngettext_lazy( "Cancelled Audit", "Cancelled Audits", count ) def action(self, request, obj_id): watcher.Audit.cancel(request, obj_id) class AuditsTable(horizon.tables.DataTable): uuid = horizon.tables.Column( 'uuid', verbose_name=_("UUID"), link="horizon:admin:audits:detail") name = horizon.tables.Column( 'name', verbose_name=_("Name"), link="horizon:admin:audits:detail") goal = horizon.tables.Column( 'goal_name', verbose_name=_('Goal')) strategy = horizon.tables.Column( 'strategy_name', verbose_name=_('Strategy')) audit_type = horizon.tables.Column( 'audit_type', verbose_name=_('Audit Type')) auto_trigger = horizon.tables.Column( 'auto_trigger', verbose_name=_('Auto Trigger')) status = horizon.tables.Column( 'state', verbose_name=_('State'), status=True, status_choices=AUDIT_STATE_DISPLAY_CHOICES) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "audits" verbose_name = _("Audits") launch_actions = (CreateAudit,) table_actions = launch_actions + ( AuditsFilterAction, ) row_actions = ( GoToActionPlan, ArchiveAudits, CancelAudits, ) class RelatedAuditsTable(horizon.tables.DataTable): name = horizon.tables.Column( 'uuid', verbose_name=_("UUID"), 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) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "audits" verbose_name = _("Related audits") hidden_title = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audits/tabs.py0000664000175100017510000000175415033037765025144 0ustar00mylesmyles# 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 gettext_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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audits/urls.py0000664000175100017510000000164715033037765025201 0ustar00mylesmyles# 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.urls import re_path from watcher_dashboard.content.audits import views urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(r'^create/$', views.CreateView.as_view(), name='create'), re_path(r'^(?P[^/]+)/detail$', views.DetailView.as_view(), name='detail'), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/audits/views.py0000664000175100017510000001111715033037765025342 0ustar00mylesmyles# 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.urls import reverse from django.urls import reverse_lazy from django.utils.translation import gettext_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 as action_plan_tables 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 LOG = logging.getLogger(__name__) class IndexView(horizon.tables.DataTableView): table_class = tables.AuditsTable template_name = 'infra_optim/audits/index.html' page_title = _("Audits") 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, **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): filters = {} 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: filters[filter_field] = filter_string return filters 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.tables.MultiTableView): table_classes = (action_plan_tables.RelatedActionPlansTable,) 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_uuid = None try: audit_uuid = self.kwargs['audit_uuid'] audit = watcher.Audit.get(self.request, audit_uuid) except Exception: msg = _('Unable to retrieve details for audit "%s".') \ % audit_uuid horizon.exceptions.handle( self.request, msg, redirect=self.redirect_url) return audit def get_related_action_plans_data(self): try: action_plan = self._get_data() audits = watcher.ActionPlan.list(self.request, audit=action_plan.uuid) except Exception as exc: LOG.exception(exc) audits = [] msg = _('Action plan list cannot be retrieved.') horizon.exceptions.handle(self.request, msg) return audits 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, **kwargs) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.858164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/goals/0000775000175100017510000000000015033037770023442 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/goals/__init__.py0000664000175100017510000000000015033037765025545 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/goals/panel.py0000664000175100017510000000140015033037765025112 0ustar00mylesmyles# 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 gettext_lazy as _ import horizon class Goals(horizon.Panel): name = _("Goals") slug = "goals" permissions = ("openstack.services.infra-optim",) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/goals/tables.py0000664000175100017510000000354415033037765025300 0ustar00mylesmyles# 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.template.defaultfilters import title # noqa from django.utils.translation import gettext_lazy as _ import horizon.exceptions import horizon.messages import horizon.tables class GoalsTable(horizon.tables.DataTable): uuid = horizon.tables.Column( 'uuid', verbose_name=_("UUID"), link="horizon:admin:goals:detail") name = horizon.tables.Column( 'name', verbose_name=_('Name')) display_name = horizon.tables.Column( 'display_name', verbose_name=_('Verbose Name')) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "goals" verbose_name = _("Goals") class EfficacySpecificationTable(horizon.tables.DataTable): name = horizon.tables.Column( 'name', verbose_name=_("Name")) description = horizon.tables.Column( 'description', verbose_name=_("Description")) unit = horizon.tables.Column( 'unit', verbose_name=_("Unit")) schema = horizon.tables.Column( 'schema', verbose_name=_("Schema")) def get_object_id(self, datum): return datum.name class Meta(object): name = "efficacy_specification" verbose_name = _("Efficacy specification") hidden_title = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/goals/tabs.py0000664000175100017510000000174715033037765024762 0ustar00mylesmyles# 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 gettext_lazy as _ from horizon import tabs class OverviewTab(tabs.Tab): name = _("Overview") slug = "overview" template_name = "infra_optim/goals/_detail_overview.html" def get_context_data(self, request): return {"goal": self.tab_group.kwargs['goal']} class GoalDetailTabs(tabs.TabGroup): slug = "goal_details" tabs = (OverviewTab,) sticky = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/goals/tests.py0000664000175100017510000000445315033037765025170 0ustar00mylesmyles# 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 unittest import mock from django import urls from watcher_dashboard import api from watcher_dashboard.test import helpers as test INDEX_URL = urls.reverse('horizon:admin:goals:index') DETAILS_VIEW = 'horizon:admin:goals:detail' class GoalsTest(test.BaseAdminViewTests): @mock.patch.object(api.watcher.Goal, 'list') def test_index(self, mock_list): mock_list.return_value = self.goals.list() res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'infra_optim/goals/index.html') goals = res.context['goals_table'].data self.assertCountEqual(goals, self.goals.list()) @mock.patch.object(api.watcher.Goal, 'list') def test_goal_list_unavailable(self, mock_list): mock_list.side_effect = self.exceptions.watcher resp = self.client.get(INDEX_URL) self.assertMessageCount(resp, error=1, warning=0) @mock.patch.object(api.watcher.Strategy, 'list') @mock.patch.object(api.watcher.Goal, 'get') def test_details(self, mock_get, mock_list): goal = self.goals.first() goal_id = goal.uuid mock_get.return_value = goal DETAILS_URL = urls.reverse(DETAILS_VIEW, args=[goal_id]) res = self.client.get(DETAILS_URL) self.assertTemplateUsed(res, 'infra_optim/goals/details.html') goals = res.context['goal'] self.assertCountEqual([goals], [goal]) @mock.patch.object(api.watcher.Goal, 'get') def test_details_exception(self, mock_get): at = self.goals.first() at_id = at.uuid mock_get.side_effect = self.exceptions.watcher DETAILS_URL = urls.reverse(DETAILS_VIEW, args=[at_id]) res = self.client.get(DETAILS_URL) self.assertRedirectsNoFollow(res, INDEX_URL) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/goals/urls.py0000664000175100017510000000152315033037765025006 0ustar00mylesmyles# 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.urls import re_path from watcher_dashboard.content.goals import views urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(r'^(?P[^/]+)/detail$', views.DetailView.as_view(), name='detail'), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/goals/views.py0000664000175100017510000001056515033037765025164 0ustar00mylesmyles# 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 gettext_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.goals import tables from watcher_dashboard.content.goals import tabs as wtabs from watcher_dashboard.content.strategies import tables as strategies_tables LOG = logging.getLogger(__name__) class IndexView(horizon.tables.DataTableView): table_class = tables.GoalsTable template_name = 'infra_optim/goals/index.html' page_title = _("Goals") def get_context_data(self, **kwargs): context = super(IndexView, self).get_context_data(**kwargs) context['goals_count'] = self.get_goals_count() return context def get_data(self): goals = [] search_opts = self.get_filters() try: goals = watcher.Goal.list(self.request, **search_opts) except Exception: horizon.exceptions.handle( self.request, _("Unable to retrieve goal information.")) return goals def get_goals_count(self): return len(self.get_data()) def get_filters(self): filters = {} 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: filters[filter_field] = filter_string return filters class DetailView(horizon.tables.MultiTableView): table_classes = (tables.EfficacySpecificationTable, strategies_tables.RelatedStrategiesTable) tab_group_class = wtabs.GoalDetailTabs template_name = 'infra_optim/goals/details.html' redirect_url = 'horizon:admin:goals:index' page_title = _("Goal Details: {{ goal.name }}") @memoized.memoized_method def _get_data(self): goal_uuid = None try: goal_uuid = self.kwargs['goal_uuid'] goal = watcher.Goal.get(self.request, goal_uuid) except Exception as exc: LOG.exception(exc) msg = _('Unable to retrieve details for goal "%s".') \ % goal_uuid horizon.exceptions.handle( self.request, msg, redirect=self.redirect_url) return goal def get_related_strategies_data(self): try: goal = self._get_data() strategies = watcher.Strategy.list(self.request, goal=goal.uuid) except Exception as exc: LOG.exception(exc) strategies = [] msg = _('Strategy list cannot be retrieved.') horizon.exceptions.handle(self.request, msg) return strategies def get_efficacy_specification_data(self): try: goal = self._get_data() indicators_spec = [watcher.EfficacyIndicatorSpec(spec) for spec in goal.efficacy_specification] except Exception as exc: LOG.exception(exc) indicators_spec = [] msg = _('Efficacy specification cannot be retrieved.') horizon.exceptions.handle(self.request, msg) return indicators_spec def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) goal = self._get_data() context["goal"] = goal return context def get_tabs(self, request, *args, **kwargs): goal = self._get_data() # ports = self._get_ports() return self.tab_group_class(request, goal=goal, # ports=ports, **kwargs) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.858164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/strategies/0000775000175100017510000000000015033037770024507 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/strategies/__init__.py0000664000175100017510000000000015033037765026612 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/strategies/forms.py0000664000175100017510000000000015033037765026201 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/strategies/panel.py0000664000175100017510000000141715033037765026167 0ustar00mylesmyles# 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 gettext_lazy as _ import horizon class Strategies(horizon.Panel): name = _("Strategies") slug = "strategies" permissions = ("openstack.services.infra-optim",) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/strategies/tables.py0000664000175100017510000000441115033037765026337 0ustar00mylesmyles# 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 gettext_lazy as _ import horizon.exceptions import horizon.messages import horizon.tables class StrategiesFilterAction(horizon.tables.FilterAction): # server = choices query = text filter_type = "server" filter_choices = ( ('goal', _("Goal ="), True), ) policy_rules = (("infra-optim", "strategy:detail"),) class StrategiesTable(horizon.tables.DataTable): uuid = horizon.tables.Column( 'uuid', verbose_name=_("UUID"), link="horizon:admin:strategies:detail") name = horizon.tables.Column( 'name', verbose_name=_('Name')) display_name = horizon.tables.Column( 'display_name', verbose_name=_('Verbose Name')) goal = horizon.tables.Column( 'goal_name', verbose_name=_("Goal"), ) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "strategies" verbose_name = _("Strategies") table_actions = ( StrategiesFilterAction, ) class RelatedStrategiesTable(horizon.tables.DataTable): uuid = horizon.tables.Column( 'uuid', verbose_name=_("UUID"), link="horizon:admin:strategies:detail") name = horizon.tables.Column( 'name', verbose_name=_('Name')) display_name = horizon.tables.Column( 'display_name', verbose_name=_('Verbose Name')) goal = horizon.tables.Column( 'goal_name', verbose_name=_("Goal"), ) def get_object_id(self, datum): return datum.uuid class Meta(object): name = "related_strategies" verbose_name = _("Related strategies") hidden_title = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/strategies/tabs.py0000664000175100017510000000177415033037765026027 0ustar00mylesmyles# 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 gettext_lazy as _ from horizon import tabs class OverviewTab(tabs.Tab): name = _("Overview") slug = "overview" template_name = "infra_optim/strategies/_detail_overview.html" def get_context_data(self, request): return {"strategy": self.tab_group.kwargs['strategy']} class StrategyDetailTabs(tabs.TabGroup): slug = "strategy_details" tabs = (OverviewTab,) sticky = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/strategies/tests.py0000664000175100017510000000503715033037765026234 0ustar00mylesmyles# 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 unittest import mock from django import urls from watcher_dashboard import api from watcher_dashboard.test import helpers as test INDEX_URL = urls.reverse( 'horizon:admin:strategies:index') DETAILS_VIEW = 'horizon:admin:strategies:detail' class StrategiesTest(test.BaseAdminViewTests): goal_list = [ 'BASIC_CONSOLIDATION', 'MINIMIZE_ENERGY_CONSUMPTION', 'BALANCE_LOAD', 'MINIMIZE_LICENSING_COST', 'PREPARED_PLAN_OPERATION', ] @mock.patch.object(api.watcher.Strategy, 'list') def test_index(self, mock_list): mock_list.return_value = self.strategies.list() res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'infra_optim/strategies/index.html') strategies = res.context['strategies_table'].data self.assertCountEqual(strategies, self.strategies.list()) @mock.patch.object(api.watcher.Strategy, 'list') def test_strategy_list_unavailable(self, mock_list): mock_list.side_effect = self.exceptions.watcher resp = self.client.get(INDEX_URL) self.assertMessageCount(resp, error=1, warning=0) @mock.patch.object(api.watcher.Strategy, 'get') def test_details(self, mock_get): at = self.strategies.first() at_id = at.uuid mock_get.return_value = at DETAILS_URL = urls.reverse(DETAILS_VIEW, args=[at_id]) res = self.client.get(DETAILS_URL) self.assertTemplateUsed(res, 'infra_optim/strategies/details.html') strategies = res.context['strategy'] self.assertCountEqual([strategies], [at]) @mock.patch.object(api.watcher.Strategy, 'get') def test_details_exception(self, mock_get): at = self.strategies.first() at_id = at.uuid mock_get.side_effect = self.exceptions.watcher DETAILS_URL = urls.reverse(DETAILS_VIEW, args=[at_id]) res = self.client.get(DETAILS_URL) self.assertRedirectsNoFollow(res, INDEX_URL) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/strategies/urls.py0000664000175100017510000000153415033037765026055 0ustar00mylesmyles# 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.urls import re_path from watcher_dashboard.content.strategies import views urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(r'^(?P[^/]+)/detail$', views.DetailView.as_view(), name='detail'), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/content/strategies/views.py0000664000175100017510000000655615033037765026236 0ustar00mylesmyles# 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 gettext_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.strategies import tables from watcher_dashboard.content.strategies import tabs as wtabs LOG = logging.getLogger(__name__) class IndexView(horizon.tables.DataTableView): table_class = tables.StrategiesTable template_name = 'infra_optim/strategies/index.html' page_title = _("Strategies") def get_context_data(self, **kwargs): context = super(IndexView, self).get_context_data(**kwargs) context['strategies_count'] = self.get_strategies_count() return context def get_data(self): strategies = [] search_opts = self.get_filters() try: strategies = watcher.Strategy.list(self.request, **search_opts) except Exception as exc: LOG.exception(exc) horizon.exceptions.handle( self.request, _("Unable to retrieve strategy information.")) return strategies def get_strategies_count(self): return len(self.get_data()) def get_filters(self): filters = {} 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: filters[filter_field] = filter_string return filters class DetailView(horizon.tabs.TabbedTableView): tab_group_class = wtabs.StrategyDetailTabs template_name = 'infra_optim/strategies/details.html' redirect_url = 'horizon:admin:strategies:index' page_title = _("Strategy Details: {{ strategy.name }}") @memoized.memoized_method def _get_data(self): strategy_uuid = None try: strategy_uuid = self.kwargs['strategy_uuid'] strategy = watcher.Strategy.get(self.request, strategy_uuid) except Exception as exc: LOG.exception(exc) msg = _('Unable to retrieve details for strategy "%s".') \ % strategy_uuid horizon.exceptions.handle( self.request, msg, redirect=self.redirect_url) return strategy def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) strategy = self._get_data() context["strategy"] = strategy return context def get_tabs(self, request, *args, **kwargs): strategy = self._get_data() return self.tab_group_class(request, strategy=strategy, **kwargs) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.858164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/0000775000175100017510000000000015033037770021755 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/__init__.py0000664000175100017510000000000015033037765024060 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.859164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/enabled/0000775000175100017510000000000015033037770023347 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/enabled/_31000_goals_panel.py0000664000175100017510000000162515033037765027077 0ustar00mylesmyles# 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 = 'goals' # 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' # Python panel class of the PANEL to be added. ADD_PANEL = 'watcher_dashboard.content.goals.panel.Goals' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/enabled/_31010_strategies_panel.py0000664000175100017510000000164415033037765030146 0ustar00mylesmyles# 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 = 'strategies' # 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' # Python panel class of the PANEL to be added. ADD_PANEL = 'watcher_dashboard.content.strategies.panel.Strategies' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/enabled/_31020_watcher_panelgroup.py0000664000175100017510000000222615033037765030504 0ustar00mylesmyles# 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 gettext_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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/enabled/_31030_audit_templates_panel.py0000664000175100017510000000166215033037765031162 0ustar00mylesmyles# 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' # Python panel class of the PANEL to be added. ADD_PANEL = 'watcher_dashboard.content.audit_templates.panel.AuditTemplates' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/enabled/_31040_audits_panel.py0000664000175100017510000000163015033037765027263 0ustar00mylesmyles# 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' # Python panel class of the PANEL to be added. ADD_PANEL = 'watcher_dashboard.content.audits.panel.Audits' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/enabled/_31050_action_plans_panel.py0000664000175100017510000000165115033037765030450 0ustar00mylesmyles# 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' # Python panel class of the PANEL to be added. ADD_PANEL = 'watcher_dashboard.content.action_plans.panel.ActionPlans' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/enabled/_31060_actions_panel.py0000664000175100017510000000163315033037765027437 0ustar00mylesmyles# 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' # Python panel class of the PANEL to be added. ADD_PANEL = 'watcher_dashboard.content.actions.panel.Actions' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/local/enabled/__init__.py0000664000175100017510000000000015033037765025452 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1751924727.8501642 watcher_dashboard-13.1.0.dev5/watcher_dashboard/static/0000775000175100017510000000000015033037770022152 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1751924727.8501642 watcher_dashboard-13.1.0.dev5/watcher_dashboard/static/infra_optim/0000775000175100017510000000000015033037770024461 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.859164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/static/infra_optim/images/0000775000175100017510000000000015033037770025726 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/static/infra_optim/images/chevron.png0000664000175100017510000000053415033037765030106 0ustar00mylesmyles‰PNG  IHDRH-ÑbKGDÿÿÿ ½§“ pHYs  šœtIMEÞ(vÿ+MiTXtCommentCreated with GIMPd.eÀIDAT(ÏÕÒ;‚@à9»£ Ñ­ÕÞ•1þoˆ6ˆ ±Q üD"caŒ$ž qÚݯ˜ì ’D…ÔP1?„á1Â-˾.’Äþ"Ïó7LÓÁf Çj1Ixó6Û¢Óù MÓ„=èá~Ïá¸3d%Lž¿„Š´šº6@”Ï¡T Ï_BJ‰ÉxÃO¤b´šìaBˆOXƆ”¨7êH’ëÒÂ2&©E¯Ú\TÌÕ:`QÚ¹øŸ—{Yv' èHïIEND®B`‚././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/static/infra_optim/images/power.png0000664000175100017510000000113615033037765027575 0ustar00mylesmyles‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIMEÞ 30cUiTXtCommentCreated with GIMPd.eÂIDATHǵ–ÛjQ†¿½g5™LHÈ•¶`L,H/Š1Ö¤ÚñAEÖZ”RéX V×h=~B·ÛEDÒ6°„aÈŸm®ß¸É‹—ëôú}´ž¼£2‚42™ Ÿ>oóm÷;§Ë§Xn6‘±©+»RŠ0 Ùþò•]šÍ‹T+2™™‘ôéIÙ¶XÖ_mpçÞ}Þøˆã8éüÈ÷}6^¿áÖí»´ÛŒ1±Ä%šÇqø½·ÇZë«[t:¿™$4¥ÖZ‚ Äø‡ $‰¸17—ce¹ÁìÜ31tÉ´{Æu³ÌÏŸa©^?HG”L*EõZ……³xž‡1濨ʸÂ"Âìɬ\¾„çyDQ4Ö°4ÃjµÂ…ó5J¥ÒAŠD«B)…ÖšB¡@c©N¹\&‚©nÆ‘ ®]½B>ŸGD‚`jÒŽ4(‹ÿ\¦T×uš%û=Oz¹âÞ@Ã!›[[Çòm ‡üܽ²‰}ƦIEND®B`‚././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.859164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/static/infra_optim/scss/0000775000175100017510000000000015033037770025434 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/static/infra_optim/scss/infra_optim.scss0000664000175100017510000000020315033037765030637 0ustar00mylesmyles// /* Additional CSS for infra_optim. */ // @import "/dashboard/scss/variables"; // @import "/bootstrap/scss/bootstrap/variables"; ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.859164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/static/infra_optim/tests/0000775000175100017510000000000015033037770025623 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/static/infra_optim/tests/formset_table.js0000664000175100017510000000452215033037765031016 0ustar00mylesmyleshorizon.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 = ''; 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); }); }); ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1751924727.8501642 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/0000775000175100017510000000000015033037770022661 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.859164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/client_side/0000775000175100017510000000000015033037770025143 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/client_side/_modal_chart.html0000664000175100017510000000116615033037765030455 0ustar00mylesmyles{% extends "horizon/client_side/template.html" %} {% load horizon %} {% block id %}modal_chart_template{% endblock %} {% block template %} {% jstemplate %} {% endjstemplate %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/client_side/templates.html0000664000175100017510000000005615033037765030034 0ustar00mylesmyles{% include "client_side/_modal_chart.html" %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.859164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/formset_table/0000775000175100017510000000000015033037770025507 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/formset_table/_row.html0000664000175100017510000000131215033037765027344 0ustar00mylesmyles {% for cell in row %} {% if cell.field %} {{ cell.field }} {% else %} {%if cell.wrap_list %}
    {% endif %}{{ cell.value }}{%if cell.wrap_list %}
{% endif %} {% endif %} {% if forloop.first %} {% for field in row.form.hidden_fields %} {{ field }} {% for error in field.errors %} {{ field.name }}: {{ error }} {% endfor %} {% endfor %} {% if row.form.non_field_errors %}
{{ row.form.non_field_errors }}
{% endif %} {% endif %} {% endfor %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/formset_table/_table.html0000664000175100017510000000257415033037765027637 0ustar00mylesmyles{% extends 'horizon/common/_data_table.html' %} {% load i18n %} {% block table_columns %} {% if not table.is_browser_table %} {% for column in columns %} {{ column }} {% endfor %} {% endif %} {% endblock table_columns %} {% block table %} {% with table.get_formset as formset %} {{ formset.management_form }} {% if formset.non_field_errors %}
{{ formset.non_field_errors }}
{% endif %} {% endwith %} {{ block.super }} {% endblock table %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/formset_table/menu_formset.html0000664000175100017510000000302715033037765031106 0ustar00mylesmyles{% load i18n %} {{ formset.management_form }} {% for error in formset.non_form_errors %}
{{ error }}
{% endfor %}

Nodes to register

{% include 'infra_optim/nodes/_upload.html' with form=upload_form %}
{% for form in formset %} {% include form_template with form=form active=forloop.first %} {% endfor %}
././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1751924727.8501642 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/horizon/0000775000175100017510000000000015033037770024351 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.860164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/horizon/common/0000775000175100017510000000000015033037770025641 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000021500000000000010213 xustar00119 path=watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/horizon/common/_items_count_domain_page_header.html 22 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/horizon/common/_items_count_domain_page_he0000664000175100017510000000066115033037765033262 0ustar00mylesmyles{% load i18n %} {% block page_header %} {% endblock %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.860164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/0000775000175100017510000000000015033037770025170 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/_fullscreen_workflow.html0000664000175100017510000000317015033037765032316 0ustar00mylesmyles{% load i18n %} {% with workflow.get_entry_point as entry_point %}
{% csrf_token %} {% if REDIRECT_URL %}{% endif %}
{% block workflow-buttons %} {% endblock %}
{% block workflow-body %}
{% for step in workflow.steps %}
{{ step.render }}
{% if not forloop.last %} {% endif %} {% endfor %}
{% endblock %}
{% endwith %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/_fullscreen_workflow_base.html0000664000175100017510000000051015033037765033303 0ustar00mylesmyles{% 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 %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/_performance_chart.html0000664000175100017510000000077615033037765031715 0ustar00mylesmyles

{{ label }}

././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/_performance_chart_box.html0000664000175100017510000000514415033037765032557 0ustar00mylesmyles{% load i18n %} {% if meter_conf %}
{% trans "From" %}
{% trans "To" %}
{% for meter_label, url_part, y_max in meter_conf %}
{% include "infra_optim/_performance_chart.html" with label=meter_label y_max=y_max url=node_perf_url|add:"?"|add:url_part only %}
{% endfor %}
{% else %}

{% trans 'Metering service is not enabled.' %}

{% endif %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/_top_5_box.html0000664000175100017510000000074415033037765030124 0ustar00mylesmyles
{% include "infra_optim/_top_5_chart.html" with top_5=top_5.fan%}
{% include "infra_optim/_top_5_chart.html" with top_5=top_5.voltage %}
{% include "infra_optim/_top_5_chart.html" with top_5=top_5.temperature%}
{% include "infra_optim/_top_5_chart.html" with top_5=top_5.current %}
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/_top_5_chart.html0000664000175100017510000000107615033037765030434 0ustar00mylesmyles{% load i18n %} {% load chart_helpers %}

{% trans 'Top 5 Nodes' %} ({{ top_5.label }}):

{% if top_5.data %} {% for d in top_5.data %} {% endfor %}
{{ d.node_uuid|truncatechars:6 }} {{ d.value}} {{ top_5.unit }} {%if d.direction %} {% endif %}
{% else %} {% trans 'No data available.' %} {% endif %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/_workflow_base.html0000664000175100017510000000050015033037765031060 0ustar00mylesmyles{% 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 %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.861164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/action_plans/0000775000175100017510000000000015033037770027642 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000021100000000000010207 xustar00115 path=watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/action_plans/_details_overview.html 22 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/action_plans/_details_overview0000664000175100017510000000141115033037765033300 0ustar00mylesmyles{% load i18n sizeformat %}

{% trans "Action Plan Overview" %}

{% trans "ID" %}
{{ action_plan.uuid|default:_("-") }}
{% url 'horizon:admin:audits:detail' action_plan.audit_uuid as audit_url %}
{% trans "Audit ID" %}
{{ action_plan.audit_uuid|default:_("-") }}
{% trans "State" %}
{{ action_plan.state|default:_("-") }}
{% trans "Created At" %}
{{ action_plan.created_at|parse_isotime|default:_("-") }}
{% trans "Update At" %}
{{ action_plan.updated_at|parse_isotime|default:_("-") }}
././@PaxHeader0000000000000000000000000000021000000000000010206 xustar00114 path=watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/action_plans/_global_efficacy.html 22 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/action_plans/_global_efficacy.0000664000175100017510000000023715033037765033101 0ustar00mylesmyles{% load i18n %}
    {% for name, value in global_indicators.items %}
  • {{ name }} {{ value }}
  • {% endfor %}
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/action_plans/create.html0000664000175100017510000000051615033037765032001 0ustar00mylesmyles{% 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 %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/action_plans/details.html0000664000175100017510000000070415033037765032162 0ustar00mylesmyles{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Action Plan Details"%}{% endblock %} {% block main %}
{% include "infra_optim/action_plans/_details_overview.html" %}
{{ related_efficacy_indicators_table.render }}
{{ related_wactions_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/action_plans/index.html0000664000175100017510000000061215033037765031642 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% load i18n %} {% 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 %}
{{ action_plans_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.861164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/actions/0000775000175100017510000000000015033037770026630 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/actions/details.html0000664000175100017510000000241515033037765031151 0ustar00mylesmyles{% 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 %}

{% trans "Action Info" %}

{% trans "UUID" %}
{{ action.uuid|default:_("-") }}
{% trans "Type" %}
{{ action.action_type|default:_("-") }}
{% url 'horizon:admin:action_plans:detail' action.action_plan_uuid as action_plan_url %}
{% trans "Action Plan" %}
{{ action.action_plan_uuid|default:_("-") }}
{% trans "State" %}
{{ action.state|default:_("-") }}
{% trans "Created At" %}
{{ action.created_at|parse_isotime|default:_("-") }}
{% trans "Update At" %}
{{ action.updated_at|parse_isotime|default:_("-") }}
{{ parameters_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/actions/index.html0000664000175100017510000000056115033037765030633 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% load i18n %} {% 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 %}
{{ wactions_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.861164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/actions_history/0000775000175100017510000000000015033037770030411 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/actions_history/details.html0000664000175100017510000000166115033037765032734 0ustar00mylesmyles{% 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 %}

{% trans "Hardware Info" %}

{% trans "Severity" %}
{{ audits.cpu_arch|default:_("-") }}
{% trans "CPUs" %}
{{ audits.vcpus|default:_("-") }}
{% trans "Memory" %}
{{ audits.ram_bytes|filesizeformat|default:_("-") }}
{% trans "Disk" %}
{{ audits.disk_bytes|filesizeformat|default:_("-") }}
{{ table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/actions_history/index.html0000664000175100017510000000164715033037765032422 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% load i18n %} {% 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 %}
{{ suggested_flavors_count }}× Suggested Flavor
{{ suggested_flavors_table.render }}
{% endif %}
{{ audits_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.861164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audit_templates/0000775000175100017510000000000015033037770030354 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audit_templates/_create.html0000664000175100017510000000301015033037765032642 0ustar00mylesmyles{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Creates an audit template with specified parameters." %}

{% trans "Define the optimization goal to achieve, among those which are available." %} {% trans "Optionally, you can select the strategy used to achieve your goal. If not set, a strategy will be automatically selected among those which can be used for your goal" %}

{% trans "Audit scope:" %}

{% trans "Creates an audit template with specified parameters." %}

{% trans "YAML example:" %} --- - compute: - host_aggregates: - id: 1 - id: 2 - id: 3 - availability_zones: - name: AZ1 - name: AZ2 - exclude: - instances: - uuid: UUID1 - uuid: UUID2 - compute_nodes: - name: compute1

{% trans "JSON example:" %} [{'compute': [{'host_aggregates': [ {'id': 1}, {'id': 2}, {'id': 3}]}, {'availability_zones': [ {'name': 'AZ1'}, {'name': 'AZ2'}]}, {'exclude': [ {'instances': [ {'uuid': 'UUID1'}, {'uuid': 'UUID2'} ]}, {'compute_nodes': [ {'name': 'compute1'} ]} ]}] }]

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audit_templates/create.html0000664000175100017510000000030515033037765032507 0ustar00mylesmyles{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create Template" %}{% endblock %} {% block main %} {% include 'infra_optim/audit_templates/_create.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audit_templates/details.html0000664000175100017510000000263615033037765032702 0ustar00mylesmyles{% 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 %}

{% trans "Audit Template Info" %}

{% trans "Name" %}
{{ audit_template.name|default:_("-") }}
{% trans "UUID" %}
{{ audit_template.uuid|default:_("-") }}
{% trans "Goal" %}
{{ audit_template.goal_name|default:_("-") }}
{% trans "Strategy" %}
{{ audit_template.strategy_name|default:_("-") }}
{% trans "Audit Scope" %}
{{ audit_template.scope|linebreaks }}
{% trans "Created At" %}
{{ audit_template.created_at|parse_isotime|default:_("-") }}
{% trans "Update At" %}
{{ audit_template.updated_at|parse_isotime|default:_("-") }}
{% trans "Deleted At" %}
{{ audit_template.deleted_at|parse_isotime|default:_("-") }}
{{ table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audit_templates/index.html0000664000175100017510000000060415033037765032355 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% load i18n %} {% 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 %}
{{ audit_templates_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.861164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audits/0000775000175100017510000000000015033037770026461 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audits/_create.html0000664000175100017510000000053215033037765030755 0ustar00mylesmyles{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Creates a audit with specified parameters." %}

{% trans "If you check 'Auto Trigger' option, the action plan, recommended by the audit, will be automatically started." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audits/create.html0000664000175100017510000000027015033037765030615 0ustar00mylesmyles{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create Audit" %}{% endblock %} {% block main %} {% include 'infra_optim/audits/_create.html' %} {% endblock %}././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audits/details.html0000664000175100017510000000260515033037765031003 0ustar00mylesmyles{% 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 %}

{% trans "Audit Info" %}

{% trans "UUID" %}
{{ audit.uuid|default:_("-") }}
{% trans "Goal" %}
{{ audit.goal_name|default:_("-") }}
{% trans "Strategy" %}
{{ audit.strategy_name|default:_("-") }}
{% trans "Type" %}
{{ audit.audit_type|default:_("-") }}
{% trans "Auto Trigger" %}
{{ audit.auto_trigger }}
{% url 'horizon:admin:audit_templates:detail' audit.audit_template_uuid as audit_template_url %}
{% trans "State" %}
{{ audit.state|default:_("-") }}
{% trans "Created At" %}
{{ audit.created_at|parse_isotime|default:_("-") }}
{% trans "Update At" %}
{{ audit.updated_at|parse_isotime|default:_("-") }}
{{ related_action_plans_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/audits/index.html0000664000175100017510000000052715033037765030466 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% load i18n %} {% 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 %}
{{ audits_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/base.html0000664000175100017510000000060215033037765026772 0ustar00mylesmyles{% extends 'base.html' %} {% block css %} {{block.super}} {% load compress %} {% compress css %} {% endcompress %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/base_detail.html0000664000175100017510000000075015033037765030320 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% block main %}
{% block breadcrumbs %}{% endblock %}
{% block actions %}{% endblock %}

{% block name %}{% endblock %}

{% block overall_usage %}{% endblock %}
{{ tab_group.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.861164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/goals/0000775000175100017510000000000015033037770026275 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/goals/details.html0000664000175100017510000000204415033037765030614 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% load i18n %} {% block title %}{% trans 'Goals: ' %}{{ goal.display_name }}{% endblock %} {% block page_header %} {% include 'horizon/common/_page_header.html' with title=_('Goals: ')|add:goal.display_name %} {% endblock page_header %} {% block main %}

{% trans "Goal Info" %}

{% trans "UUID" %}
{{ goal.uuid|default:_("-") }}
{% trans "Name" %}
{{ goal.display_name|default:_("-") }}
{% trans "Created At" %}
{{ goal.created_at|parse_isotime|default:_("-") }}
{% trans "Update At" %}
{{ goal.updated_at|parse_isotime|default:_("-") }}
{{ efficacy_specification_table.render }}
{{ related_strategies_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/goals/index.html0000664000175100017510000000052115033037765030274 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% load i18n %} {% block title %}{% trans 'Goals' %}{% endblock %} {% block page_header %} {% include 'horizon/common/_page_header.html' with title=_('Goals') items_count=goal_count %} {% endblock page_header %} {% block main %}
{{ goals_table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/header_actions.html0000664000175100017510000000061215033037765031031 0ustar00mylesmyles ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.861164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/logs/0000775000175100017510000000000015033037770026134 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/logs/index.html0000664000175100017510000000060715033037765030140 0ustar00mylesmyles{% 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 %}
{{ table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/qunit.html0000664000175100017510000001572215033037765027231 0ustar00mylesmyles Watcher QUnit Test Suite {% include "horizon/_conf.html" %} {% comment %}Load test modules here.{% endcomment %} {% comment %}End test modules.{% endcomment %} {% include "horizon/_scripts.html" %} {% include "infra_optim/_scripts.html" %}

Watcher JavaScript Tests

    Flavors

    Flavor Name VCPU RAM (MB) Root Disk (GB) Ephemeral Disk (GB) Swap Disk (MB) Max. VMs Delete
    -
    -
    -
    Displaying 3 items
    ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.862164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/strategies/0000775000175100017510000000000015033037770027342 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/strategies/details.html0000664000175100017510000000222515033037765031662 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% load i18n %} {% block title %}{% trans 'Strategies: ' %}{{ strategy.display_name }}{% endblock %} {% block page_header %} {% include 'horizon/common/_page_header.html' with title=_('Strategies: ')|add:strategy.display_name %} {% endblock page_header %} {% block main %}

    {% trans "Strategy Info" %}

    {% trans "UUID" %}
    {{ strategy.uuid|default:_("-") }}
    {% trans "Name" %}
    {{ strategy.display_name|default:_("-") }}
    {% trans "Goal" %}
    {% url 'horizon:admin:goals:detail' strategy.goal_name as goal_url %}
    {{ strategy.goal_name|default:_("-") }}
    {% trans "Created At" %}
    {{ strategy.created_at|parse_isotime|default:_("-") }}
    {% trans "Update At" %}
    {{ strategy.updated_at|parse_isotime|default:_("-") }}
    {{ table.render }}
    {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templates/infra_optim/strategies/index.html0000664000175100017510000000054415033037765031346 0ustar00mylesmyles{% extends 'infra_optim/base.html' %} {% load i18n %} {% block title %}{% trans 'Strategies' %}{% endblock %} {% block page_header %} {% include 'horizon/common/_page_header.html' with title=_('Strategies') items_count=strategy_count %} {% endblock page_header %} {% block main %}
    {{ strategies_table.render }}
    {% endblock %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.862164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templatetags/0000775000175100017510000000000015033037770023355 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/templatetags/__init__.py0000664000175100017510000000117515033037765025476 0ustar00mylesmyles# Copyright (c) 2018 Ultimum Technologies s.r.o # # 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 horizon.utils import filters # noqa ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.862164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/0000775000175100017510000000000015033037770021642 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/__init__.py0000664000175100017510000000000015033037765023745 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.862164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/api_tests/0000775000175100017510000000000015033037770023635 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/api_tests/__init__.py0000664000175100017510000000000015033037765025740 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/api_tests/test_watcher.py0000664000175100017510000003170315033037765026713 0ustar00mylesmyles# 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 unittest import mock from watcher_dashboard import api from watcher_dashboard.test import helpers as test class WatcherAPITests(test.APITestCase): def test_goal_list(self): goals = {'goals': self.api_goals.list()} watcherclient = self.stub_watcherclient() watcherclient.goal.list = mock.Mock( return_value=goals) ret_val = api.watcher.Goal.list(self.request) self.assertIsInstance(ret_val, dict) self.assertIn('goals', ret_val) for n in ret_val['goals']: self.assertIsInstance(n, dict) watcherclient.goal.list.assert_called_with( detail=True) def test_goal_get(self): goal = self.api_goals.first() goal_id = self.api_goals.first()['uuid'] watcherclient = self.stub_watcherclient() watcherclient.goal.get = mock.Mock( return_value=goal) ret_val = api.watcher.Goal.get(self.request, goal_id) self.assertIsInstance(ret_val, dict) watcherclient.goal.get.assert_called_with( goal_id) def test_strategy_list(self): strategies = {'strategies': self.api_strategies.list()} watcherclient = self.stub_watcherclient() watcherclient.strategy.list = mock.Mock( return_value=strategies) ret_val = api.watcher.Strategy.list(self.request) self.assertIn('strategies', ret_val) for n in ret_val['strategies']: self.assertIsInstance(n, dict) watcherclient.strategy.list.assert_called_with( detail=True) def test_strategy_get(self): strategy = self.api_strategies.first() strategy_id = self.api_strategies.first()['uuid'] watcherclient = self.stub_watcherclient() watcherclient.strategy.get = mock.Mock( return_value=strategy) ret_val = api.watcher.Strategy.get(self.request, strategy_id) self.assertIsInstance(ret_val, dict) watcherclient.strategy.get.assert_called_with( strategy_id) def test_audit_template_list(self): audit_templates = { 'audit_templates': self.api_audit_templates.list()} watcherclient = self.stub_watcherclient() watcherclient.audit_template.list = mock.Mock( return_value=audit_templates) ret_val = api.watcher.AuditTemplate.list(self.request) self.assertIn('audit_templates', ret_val) for n in ret_val['audit_templates']: self.assertIsInstance(n, dict) watcherclient.audit_template.list.assert_called_with( detail=True) def test_audit_template_list_with_filters(self): search_opts = {'name': 'Audit Template 1'} audit_templates = { 'audit_templates': self.api_audit_templates.filter(**search_opts)} watcherclient = self.stub_watcherclient() watcherclient.audit_template.list = mock.Mock( return_value=audit_templates) ret_val = api.watcher.AuditTemplate.list( self.request, **search_opts) self.assertIn('audit_templates', ret_val) for n in ret_val['audit_templates']: self.assertIsInstance(n, dict) self.assertEqual(ret_val, audit_templates) watcherclient.audit_template.list.assert_called_with( detail=True, **search_opts) def test_audit_template_get(self): audit_template = self.api_audit_templates.first() audit_template_id = self.api_audit_templates.first()['uuid'] watcherclient = self.stub_watcherclient() watcherclient.audit_template.get = mock.Mock( return_value=audit_template) ret_val = api.watcher.AuditTemplate.get(self.request, audit_template_id) self.assertIsInstance(ret_val, dict) watcherclient.audit_template.get.assert_called_with( audit_template_id=audit_template_id) def test_audit_template_create(self): audit_template = self.api_audit_templates.first() name = audit_template['name'] goal = audit_template['goal_uuid'] strategy = audit_template['strategy_uuid'] description = audit_template['description'] scope = audit_template['scope'] watcherclient = self.stub_watcherclient() watcherclient.audit_template.create = mock.Mock( return_value=audit_template) ret_val = api.watcher.AuditTemplate.create( self.request, name, goal, strategy, description, scope) self.assertIsInstance(ret_val, dict) watcherclient.audit_template.create.assert_called_with( name=name, goal=goal, strategy=strategy, description=description, scope=scope) def test_audit_template_patch(self): audit_template = self.api_audit_templates.first() audit_template_id = self.api_audit_templates.first()['uuid'] form_data = {'name': 'new Audit Template 1'} watcherclient = self.stub_watcherclient() watcherclient.audit_template.patch = mock.Mock( return_value=audit_template) ret_val = api.watcher.AuditTemplate.patch( self.request, audit_template_id, form_data) self.assertIsInstance(ret_val, dict) watcherclient.audit_template.patch.assert_called_with( audit_template_id, [{'name': 'name', 'value': 'new Audit Template 1'}] ) def test_audit_template_delete(self): audit_template_list = self.api_audit_templates.list() audit_template_id = self.api_audit_templates.first()['uuid'] deleted_at_list = self.api_audit_templates.delete() watcherclient = self.stub_watcherclient() watcherclient.audit_template.delete = mock.Mock() api.watcher.AuditTemplate.delete(self.request, audit_template_id) self.assertEqual(audit_template_list, deleted_at_list) self.assertEqual(len(audit_template_list), len(deleted_at_list)) watcherclient.audit_template.delete.assert_called_with( audit_template_id=audit_template_id) def test_audit_list(self): audits = {'audits': self.api_audits.list()} watcherclient = self.stub_watcherclient() watcherclient.audit.list = mock.Mock( return_value=audits) ret_val = api.watcher.Audit.list(self.request) self.assertIn('audits', ret_val) for n in ret_val['audits']: self.assertIsInstance(n, dict) watcherclient.audit.list.assert_called_with( detail=True) def test_audit_get(self): audit = self.api_audits.first() audit_id = self.api_audits.first()['uuid'] watcherclient = self.stub_watcherclient() watcherclient.audit.get = mock.Mock( return_value=audit) ret_val = api.watcher.Audit.get(self.request, audit_id) self.assertIsInstance(ret_val, dict) watcherclient.audit.get.assert_called_with( audit=audit_id) def test_audit_create(self): audit = self.api_audits.first() audit_template_id = self.api_audit_templates.first()['uuid'] audit_type = self.api_audits.first()['audit_type'] audit_name = self.api_audits.first()['name'] audit_template_uuid = audit_template_id watcherclient = self.stub_watcherclient() watcherclient.audit.create = mock.Mock( return_value=audit) ret_val = api.watcher.Audit.create( self.request, audit_template_uuid, audit_type, audit_name) self.assertIsInstance(ret_val, dict) watcherclient.audit.create.assert_called_with( audit_template_uuid=audit_template_uuid, audit_type=audit_type, auto_trigger=False, name=audit_name) def test_audit_create_with_interval(self): audit = self.api_audits.list()[1] audit_template_id = self.api_audit_templates.first()['uuid'] audit_type = self.api_audits.first()['audit_type'] audit_name = self.api_audits.first()['name'] interval = audit['interval'] audit_template_uuid = audit_template_id watcherclient = self.stub_watcherclient() watcherclient.audit.create = mock.Mock( return_value=audit) ret_val = api.watcher.Audit.create( self.request, audit_template_uuid, audit_type, audit_name, False, interval) self.assertIsInstance(ret_val, dict) watcherclient.audit.create.assert_called_with( audit_template_uuid=audit_template_uuid, audit_type=audit_type, auto_trigger=False, interval=interval, name=audit_name) def test_audit_create_with_auto_trigger(self): audit = self.api_audits.list()[1] audit_template_id = self.api_audit_templates.first()['uuid'] audit_type = self.api_audits.first()['audit_type'] audit_name = self.api_audits.first()['name'] audit_template_uuid = audit_template_id watcherclient = self.stub_watcherclient() watcherclient.audit.create = mock.Mock( return_value=audit) ret_val = api.watcher.Audit.create( self.request, audit_template_uuid, audit_type, audit_name, True) self.assertIsInstance(ret_val, dict) watcherclient.audit.create.assert_called_with( audit_template_uuid=audit_template_uuid, audit_type=audit_type, auto_trigger=True, name=audit_name) def test_audit_delete(self): audit_id = self.api_audits.first()['uuid'] watcherclient = self.stub_watcherclient() watcherclient.audit.delete = mock.Mock() api.watcher.Audit.delete(self.request, audit_id) watcherclient.audit.delete.assert_called_with( audit=audit_id) def test_audit_cancel(self): audit_id = self.api_audits.first()['uuid'] watcherclient = self.stub_watcherclient() watcherclient.audit.update = mock.Mock() api.watcher.Audit.cancel(self.request, audit_id) watcherclient.audit.update.assert_called_with( audit=audit_id, patch=[{'op': 'replace', 'path': '/state', 'value': 'CANCELLED'}]) def test_action_plan_list(self): action_plans = {'action_plans': self.api_action_plans.list()} watcherclient = self.stub_watcherclient() watcherclient.action_plan.list = mock.Mock( return_value=action_plans) ret_val = api.watcher.ActionPlan.list(self.request) self.assertIn('action_plans', ret_val) for n in ret_val['action_plans']: self.assertIsInstance(n, dict) watcherclient.action_plan.list.assert_called_with( detail=True) def test_action_plan_get(self): action_plan = self.api_action_plans.first() action_plan_id = self.api_action_plans.first()['uuid'] watcherclient = self.stub_watcherclient() watcherclient.action_plan.get = mock.Mock( return_value=action_plan) ret_val = api.watcher.ActionPlan.get(self.request, action_plan_id) self.assertIsInstance(ret_val, dict) watcherclient.action_plan.get.assert_called_with( action_plan_id=action_plan_id) def test_action_plan_start(self): action_plan_id = self.api_action_plans.first()['uuid'] watcherclient = self.stub_watcherclient() watcherclient.action_plan.start = mock.Mock() api.watcher.ActionPlan.start(self.request, action_plan_id) watcherclient.action_plan.start.assert_called_with( action_plan_id) def test_action_plan_delete(self): action_plan_id = self.api_action_plans.first()['uuid'] watcherclient = self.stub_watcherclient() watcherclient.action_plan.delete = mock.Mock() api.watcher.ActionPlan.delete(self.request, action_plan_id) watcherclient.action_plan.delete.assert_called_with( action_plan_id=action_plan_id) def test_action_list(self): actions = {'actions': self.api_actions.list()} watcherclient = self.stub_watcherclient() watcherclient.action.list = mock.Mock( return_value=actions) ret_val = api.watcher.Action.list(self.request) self.assertIn('actions', ret_val) for n in ret_val['actions']: self.assertIsInstance(n, dict) watcherclient.action.list.assert_called_with( detail=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/helpers.py0000664000175100017510000000340115033037765023660 0ustar00mylesmyles# Copyright 2012 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2012 Nebula, Inc. # # 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 unittest import mock from openstack_dashboard.test import helpers from watcher_dashboard import api from watcher_dashboard.test.test_data import utils class WatcherTestsMixin(object): def _setup_test_data(self): super(WatcherTestsMixin, self)._setup_test_data() utils.load_test_data(self) class TestCase(WatcherTestsMixin, helpers.TestCase): pass class APITestCase(WatcherTestsMixin, helpers.APITestCase): def setUp(self): super(APITestCase, self).setUp() self._original_watcherclient = api.watcher.watcherclient api.watcher.watcherclient = lambda request: self.stub_watcherclient() def tearDown(self): super(APITestCase, self).tearDown() api.watcher.watcherclient = self._original_watcherclient def stub_watcherclient(self): if not hasattr(self, "watcherclient"): self.watcherclient = mock.Mock() return self.watcherclient class BaseAdminViewTests(WatcherTestsMixin, helpers.BaseAdminViewTests): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.862164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/0000775000175100017510000000000015033037770025407 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/__init__.py0000664000175100017510000000000015033037765027512 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/horizon.conf0000664000175100017510000000341515033037765027755 0ustar00mylesmyles# # Configuration filed based on Tempest's tempest.conf.sample # [dashboard] # Where the dashboard can be found (string value) dashboard_url=http://localhost:8000/ # Dashboard help page url (string value) help_url=https://docs.openstack.org/ [selenium] # Timeout in seconds to wait for a page to become available # (integer value) page_timeout=30 # Output directory for screenshots. # (string value) screenshots_directory=integration_tests_screenshots # Implicit timeout to wait until element become available, # this timeout is used for every find_element, find_elements call. # (integer value) implicit_wait=10 # Explicit timeout is used for long lasting operations, # methods using explicit timeout are usually prefixed with 'wait', # those methods ignore implicit_wait when looking up web elements. # (integer value) explicit_wait=300 [image] # http accessible image (string value) http_image=http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz [identity] # Username to use for non-admin API requests. (string value) username=demo # API key to use when authenticating. (string value) password=secretadmin # Administrative Username to use for admin API requests. # (string value) admin_username=admin # API key to use when authenticating as admin. (string value) admin_password=secretadmin [scenario] # ssh username for image file (string value) ssh_user=cirros [launch_instances] #available zone to launch instances available_zone=nova #image_name to launch instances image_name=cirros-0.3.4-x86_64-uec (24.0 MB) [volume] volume_type=lvmdriver-1 volume_size=1 [plugin] is_plugin=true plugin_page_path=watcher_dashboard.test.integration_tests.pages plugin_page_structure={"Admin": {"Optimization": {"_": ["Audit Templates", "Audits", "Action Plans", "Actions"] } } } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.862164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/0000775000175100017510000000000015033037770026506 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/__init__.py0000664000175100017510000000000015033037765030611 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.862164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/admin/0000775000175100017510000000000015033037770027576 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/admin/__init__.py0000664000175100017510000000000015033037765031701 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.863164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/admin/optimization/0000775000175100017510000000000015033037770032324 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000021300000000000010211 xustar00117 path=watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/admin/optimization/__init__.py 22 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/admin/optimization/__in0000664000175100017510000000000015033037765033145 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000021500000000000010213 xustar00119 path=watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/admin/optimization/auditspage.py 22 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/admin/optimization/audi0000664000175100017510000000550615033037765033203 0ustar00mylesmyles# 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.test.integration_tests.regions import forms from openstack_dashboard.test.integration_tests.regions import tables from openstack_dashboard.test.integration_tests.pages import basepage class AuditsTable(tables.TableRegion): name = "audits" @tables.bind_table_action('launch_audit') def launch_audit(self, launch_button): launch_button.click() return forms.BaseFormRegion(self.driver, self.conf) @tables.bind_row_action('go_to_action_plan') def go_to_action_plan(self, goto_button): goto_button.click() return forms.BaseFormRegion(self.driver, self.conf) @tables.bind_row_action('go_to_audit_template') def go_to_audit_template(self, goto_button): goto_button.click() return forms.BaseFormRegion(self.driver, self.conf) class AuditsPage(basepage.BaseNavigationPage): DEFAULT_ID = "auto" AUDIT_TABLE_NAME_COLUMN = 'name' AUDIT_TABLE_TEMPLATE_COLUMN_INDEX = 1 def __init__(self, driver, conf): super(AuditsPage, self).__init__(driver, conf) self._page_title = "Audits" @property def audits_table(self): return AuditsTable(self.driver, self.conf) def _get_audit_row(self, name): return self.audits_table.get_row(self.AUDIT_TABLE_NAME_COLUMN, name) def create_audit(self, name, id_=DEFAULT_ID, vcpus=None, ram=None, root_disk=None, ephemeral_disk=None, swap_disk=None): create_audit_form = self.audits_table.create_audit() create_audit_form.name.text = name if id_ is not None: create_audit_form.audit_id.text = id_ create_audit_form.vcpus.value = vcpus create_audit_form.memory_mb.value = ram create_audit_form.disk_gb.value = root_disk create_audit_form.eph_gb.value = ephemeral_disk create_audit_form.swap_mb.value = swap_disk create_audit_form.submit() self.wait_till_popups_disappear() def delete_audit(self, name): row = self._get_audit_row(name) row.mark() confirm_delete_audits_form = self.audits_table.delete_audit() confirm_delete_audits_form.submit() self.wait_till_popups_disappear() def is_audit_present(self, name): return bool(self._get_audit_row(name)) ././@PaxHeader0000000000000000000000000000022500000000000010214 xustar00127 path=watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/admin/optimization/audittemplatespage.py 22 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/pages/admin/optimization/audi0000664000175100017510000001037615033037765033204 0ustar00mylesmyles# 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 selenium.webdriver.common import by from openstack_dashboard.test.integration_tests.pages import basepage from openstack_dashboard.test.integration_tests.regions import forms from openstack_dashboard.test.integration_tests.regions import tables class AuditTemplatesTable(tables.TableRegion): name = 'audit_templates' CREATE_AUDIT_TEMPLATE_FORM_FIELDS = ("name", "description", "goal_id", "strategy_id") @tables.bind_table_action('create') def create_audit_template(self, create_button): create_button.click() return forms.FormRegion( self.driver, self.conf, field_mappings=self.CREATE_AUDIT_TEMPLATE_FORM_FIELDS) @tables.bind_table_action('delete') def delete_audit_template(self, delete_button): delete_button.click() return forms.BaseFormRegion(self.driver, self.conf, None) @tables.bind_row_action('launch_audit') def launch_audit(self, launch_button, row): launch_button.click() return forms.BaseFormRegion(self.driver, self.conf) class AudittemplatesPage(basepage.BaseNavigationPage): DEFAULT_DESCRIPTION = "Fake description from integration tests" DEFAULT_GOAL = "SERVER_CONSOLIDATION" AUDITS_PAGE_TITLE = "Audits - OpenStack Dashboard" AUDIT_TEMPLATE_INFO_SUB_TITLE = "Audit Template Info" # Set fields name attribute CREATE_AUDIT_TEMPLATE_FORM_FIELDS = ( "name", "description", "goal" ) _audittemplates_info_title_locator = (by.By.CSS_SELECTOR, 'div.detail>h4') def __init__(self, driver, conf): super(AudittemplatesPage, self).__init__(driver, conf) self._page_title = "Audit Templates" @property def audittemplates_table(self): return AuditTemplatesTable(self.driver, self.conf) @property def audit_templates__action_create_form(self): return forms.FormRegion(self.driver, self.conf, None, self.CREATE_AUDIT_TEMPLATE_FORM_FIELDS) def _get_row_with_audit_template_name(self, name): self._turn_off_implicit_wait() row = self.audittemplates_table.get_row("name", name) self._turn_on_implicit_wait() return row def delete_audit_template(self, name): row = self._get_row_with_audit_template_name(name) row.mark() confirm_delete_audit_template_form = ( self.audittemplates_table.delete_audit_template()) confirm_delete_audit_template_form.submit() def create_audit_template(self, name, description=DEFAULT_DESCRIPTION, goal_id=DEFAULT_GOAL): self.audittemplates_table.create_audit_template() self.audit_templates__action_create_form.name.text = name self.audit_templates__action_create_form.description.text = description self.audit_templates__action_create_form.goal_id.value = goal_id self.audit_templates__action_create_form.submit() def is_audit_template_present(self, name): return bool( self._get_row_with_audit_template_name(name)) def launch_audit(self, name): row = self._get_row_with_audit_template_name(name) self.audittemplates_table.launch_audit(row) # Check that the name appears in Audits page return (self.driver.title == self.AUDITS_PAGE_TITLE) \ and (name in self.driver.page_source) def show_audit_template_info(self, name): self.driver.find_element_by_link_text(name).click() info_line = self._get_element(*self._audittemplates_info_title_locator) return self._is_text_visible( info_line, self.AUDIT_TEMPLATE_INFO_SUB_TITLE) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.863164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/tests/0000775000175100017510000000000015033037770026551 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/tests/__init__.py0000664000175100017510000000000015033037765030654 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000021100000000000010207 xustar00115 path=watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/tests/test_audit_template_panel.py 22 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/integration_tests/tests/test_audit_template_pan0000664000175100017510000000617215033037765033404 0ustar00mylesmyles# 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 uuid from openstack_dashboard.test.integration_tests import helpers class AuditTemplateCreatePanelTests(helpers.AdminTestCase): def test_create_audit_template(self): """Test the audit template panel: * Loads the audit template panel * Creates a new audit template with random-generated name * Checks that this audit template is in list * Deletes this audit template (in tearDown) * Checks that the audit template is removed """ audit_template_name = "audit_template_%s" % uuid.uuid1() audit_template_page = \ self.home_pg.go_to_optimization_audittemplatespage() audit_template_page.create_audit_template(audit_template_name) self.assertTrue(audit_template_page.is_audit_template_present( audit_template_name)) class AuditTemplatePanelTests(helpers.AdminTestCase): def setUp(self): super(AuditTemplatePanelTests, self).setUp() self.audit_template_name = "audit_template_%s" % uuid.uuid1() audit_template_page = \ self.home_pg.go_to_optimization_audittemplatespage() audit_template_page.create_audit_template(self.audit_template_name) def tearDown(self): audit_template_page = \ self.home_pg.go_to_optimization_audittemplatespage() audit_template_page.delete_audit_template(self.audit_template_name) # Uncomment this line when button will be implemented self.assertFalse(audit_template_page.is_audit_template_present( self.audit_template_name)) super(AuditTemplatePanelTests, self).tearDown() def test_show_audit_template_info(self): """Test the audit template panel information page * Loads the audit template panel * Click on link behind the audit template name * Checks the info page (only the "Audit Template Info" title for now) """ audit_template_page = \ self.home_pg.go_to_optimization_audittemplatespage() self.assertTrue( audit_template_page.show_audit_template_info( self.audit_template_name)) def test_launch_audit(self): """Test the audit template panel "Launch Audit" row button * Loads the audit template panel * Click on the button "Launch Audit" * Checks the audits page for audit template name in page """ audit_template_page = \ self.home_pg.go_to_optimization_audittemplatespage() self.assertTrue( audit_template_page.launch_audit(self.audit_template_name)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/selenium.py0000664000175100017510000000367315033037765024052 0ustar00mylesmyles# 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 horizon.test import helpers as test from selenium.common import exceptions as selenium_exceptions class BrowserTests(test.SeleniumTestCase): def test_jasmine(self): url = "%s%s" % (self.live_server_url, "/jasmine/") self.selenium.get(url) wait = self.ui.WebDriverWait(self.selenium, 10) def jasmine_done(driver): text = driver.find_element_by_id("jasmine-testresult").text return "Tests completed" in text wait.until(jasmine_done) failed_elem = self.selenium.find_element_by_class_name("failed") failed = int(failed_elem.text) if failed: self.log_failure_messages() self.assertEqual(failed, 0) def log_failure_messages(self): logger = logging.getLogger('selenium') logger.error("Errors found during jasmine test:") fail_elems = self.selenium.find_elements_by_class_name("fail") for elem in fail_elems: try: module = elem.find_element_by_class_name("module-name").text except selenium_exceptions.NoSuchElementException: continue message = elem.find_element_by_class_name("test-message").text source = elem.find_element_by_tag_name("pre").text logger.error("Module: %s, message: %s, source: %s" % ( module, message, source)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/settings.py0000664000175100017510000000244315033037765024063 0ustar00mylesmyles# # 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 horizon.test.settings import * # noqa from openstack_dashboard.test.settings import * # noqa # Load the pluggable dashboard settings import openstack_dashboard.enabled import watcher_dashboard.local.enabled INSTALLED_APPS = ( 'django.contrib.contenttypes', 'django.contrib.auth', 'django.contrib.sessions', 'django.contrib.staticfiles', 'django.contrib.messages', 'django.contrib.humanize', 'openstack_auth', 'compressor', 'horizon', 'openstack_dashboard', ) INSTALLED_APPS = list(INSTALLED_APPS) # Make sure it's mutable settings_utils.update_dashboards( [ watcher_dashboard.local.enabled, openstack_dashboard.enabled, ], HORIZON_CONFIG, INSTALLED_APPS, ) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.863164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/test_data/0000775000175100017510000000000015033037770023612 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/test_data/__init__.py0000664000175100017510000000000015033037765025715 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/test_data/exceptions.py0000664000175100017510000000160715033037765026355 0ustar00mylesmyles# 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.test.test_data import exceptions from watcherclient.common.apiclient import exceptions as wexceptions def data(TEST): TEST.exceptions = exceptions.data watcher_exception = wexceptions.ClientException TEST.exceptions.watcher = exceptions.create_stubbed_exception( watcher_exception) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/test_data/utils.py0000664000175100017510000000363615033037765025340 0ustar00mylesmyles# 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.test.test_data import keystone_data from openstack_dashboard.test.test_data import utils def load_test_data(load_onto=None): from watcher_dashboard.test.test_data import exceptions from watcher_dashboard.test.test_data import watcher_data # The order of these loaders matters, some depend on others. loaders = (exceptions.data, keystone_data.data, watcher_data.data) if load_onto: for data_func in loaders: data_func(load_onto) return load_onto else: return utils.TestData(*loaders) class TestDataContainer(utils.TestDataContainer): def filter(self, filtered=None, **kwargs): """Returns objects in this container """ """whose attributes match the given keyword arguments. """ if filtered is None: filtered = self._objects try: key, value = kwargs.popitem() except KeyError: # We're out of filters, return return filtered def get_match(obj): return key in obj and obj.get(key) == value return self.filter(filtered=filter(get_match, filtered), **kwargs) def delete(self): """Delete the first object from this container and return a list""" self._objects.remove(self._objects[0]) return self._objects ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/test_data/watcher_data.py0000664000175100017510000001710415033037765026621 0ustar00mylesmyles# 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 copy from watcher_dashboard.api import watcher from watcher_dashboard.test.test_data import utils def data(TEST): TEST.service_catalog.append( {"type": "infra-optim", "name": "watcher", "endpoints_links": [], "endpoints": [ {"region": "RegionOne", "adminURL": "http://admin.watcher.example.com:9322", "internalURL": "http://int.watcher.example.com:9322", "publicURL": "http://public.watcher.example.com:9322"}, {"region": "RegionTwo", "adminURL": "http://admin.watcher2.example.com:9322", "internalURL": "http://int.watcher2.example.com:9322", "publicURL": "http://public.watcher2.example.com:9322"}]}, ) TEST.goals = utils.TestDataContainer() TEST.efficacy_specifications = utils.TestDataContainer() TEST.api_goals = utils.TestDataContainer() efficacy_specifications_dict1 = {'name': 'spec1'} efficacy_specifications_dict2 = {'name': 'spec2'} spec1 = watcher.EfficacyIndicatorSpec(efficacy_specifications_dict1) spec2 = watcher.EfficacyIndicatorSpec(efficacy_specifications_dict2) goal_dict1 = { 'uuid': 'gggggggg-1111-1111-1111-gggggggggggg', 'name': 'MINIMIZE_LICENSING_COST', 'display_name': 'Dummy', 'efficacy_specifications': spec1 } goal_dict2 = { 'uuid': 'gggggggg-2222-2222-2222-gggggggggggg', 'name': 'SERVER_CONSOLIDATION', 'display_name': 'Server consolidation', 'efficacy_specifications': spec2 } TEST.api_goals.add(goal_dict1) TEST.api_goals.add(goal_dict2) _goal_dict1 = copy.deepcopy(goal_dict1) _goal_dict2 = copy.deepcopy(goal_dict2) TEST.strategies = utils.TestDataContainer() TEST.api_strategies = utils.TestDataContainer() strategy_dict1 = { 'uuid': 'ssssssss-1111-1111-1111-ssssssssssss', 'name': 'minimize_licensing_cost1', 'goal_uuid': 'gggggggg-1111-1111-1111-gggggggggggg', 'display_name': 'Fake licensing cost strategy1', } strategy_dict2 = { 'uuid': 'ssssssss-2222-2222-2222-ssssssssssss', 'name': 'minimize_licensing_cost2', 'goal_uuid': 'gggggggg-1111-1111-1111-gggggggggggg', 'display_name': 'Fake licensing cost strategy2', } strategy_dict3 = { 'uuid': 'ssssssss-3333-3333-3333-ssssssssssss', 'name': 'sercon', 'goal_uuid': 'gggggggg-2222-2222-2222-gggggggggggg', 'display_name': 'Fake Sercon', } TEST.api_strategies.add(strategy_dict1) TEST.api_strategies.add(strategy_dict2) TEST.api_strategies.add(strategy_dict3) _strategy_dict1 = copy.deepcopy(strategy_dict1) _strategy_dict2 = copy.deepcopy(strategy_dict2) _strategy_dict3 = copy.deepcopy(strategy_dict3) TEST.audit_templates = utils.TestDataContainer() TEST.api_audit_templates = utils.TestDataContainer() audit_template_dict = { 'uuid': '11111111-1111-1111-1111-111111111111', 'name': 'Audit Template 1', 'description': 'Audit Template 1 description', 'scope': '', 'goal_uuid': 'gggggggg-1111-1111-1111-gggggggggggg', 'strategy_uuid': 'ssssssss-1111-1111-1111-ssssssssssss', } audit_template_dict2 = { 'uuid': '11111111-2222-2222-2222-111111111111', 'name': 'Audit Template 2', 'description': 'Audit Template 2 description', 'scope': '', 'goal_uuid': 'gggggggg-1111-1111-1111-gggggggggggg', 'strategy_uuid': 'ssssssss-2222-2222-2222-ssssssssssss', } audit_template_dict3 = { 'uuid': '11111111-3333-3333-3333-111111111111', 'name': 'Audit Template 1', 'description': 'Audit Template 3 description', 'scope': '', 'goal_uuid': 'gggggggg-2222-2222-2222-gggggggggggg', 'strategy_uuid': None, } TEST.api_audit_templates.add(audit_template_dict) TEST.api_audit_templates.add(audit_template_dict2) TEST.api_audit_templates.add(audit_template_dict3) _audit_template_dict = copy.deepcopy(audit_template_dict) _audit_template_dict2 = copy.deepcopy(audit_template_dict2) _audit_template_dict3 = copy.deepcopy(audit_template_dict3) TEST.audits = utils.TestDataContainer() TEST.api_audits = utils.TestDataContainer() audit_dict = { 'uuid': '22222222-2222-2222-2222-222222222222', 'audit_type': 'ONESHOT', 'name': 'Audit 1', 'audit_template_uuid': '11111111-1111-1111-1111-111111111111', 'interval': None, } audit_dict2 = { 'uuid': '33333333-3333-3333-3333-333333333333', 'audit_type': 'CONTINUOUS', 'name': 'Audit 2', 'audit_template_uuid': '11111111-1111-1111-1111-111111111111', 'interval': 60, } TEST.api_audits.add(audit_dict) TEST.api_audits.add(audit_dict2) _audit_dict = copy.deepcopy(audit_dict) TEST.action_plans = utils.TestDataContainer() TEST.api_action_plans = utils.TestDataContainer() action_plan_dict = { 'uuid': '33333333-3333-3333-3333-333333333333', 'state': 'RECOMMENDED', 'first_action_uuid': '44444444-4444-4444-4444-111111111111', 'audit_uuid': '33333333-3333-3333-3333-333333333333' } TEST.api_action_plans.add(action_plan_dict) _action_plan_dict = copy.deepcopy(action_plan_dict) TEST.actions = utils.TestDataContainer() TEST.api_actions = utils.TestDataContainer() action_dict1 = { 'uuid': '44444444-4444-4444-4444-111111111111', 'state': 'PENDING', 'next_uuid': '44444444-4444-4444-4444-222222222222', 'action_plan_uuid': '33333333-3333-3333-3333-333333333333' } TEST.api_actions.add(action_dict1) action_dict2 = { 'uuid': '44444444-4444-4444-4444-222222222222', 'state': 'PENDING', 'next_uuid': None, 'action_plan_uuid': '33333333-3333-3333-3333-333333333333' } TEST.api_actions.add(action_dict2) action2 = watcher.Action(action_dict2) action1 = watcher.Action(action_dict1) TEST.actions.add(action1) TEST.actions.add(action2) _action_plan_dict['actions'] = [action1, action2] action_plan = watcher.ActionPlan(_action_plan_dict) _audit_dict['action_plans'] = [action_plan] audit = watcher.Audit(_audit_dict) goal1 = watcher.Goal(_goal_dict1) goal2 = watcher.Goal(_goal_dict2) strategy1 = watcher.Strategy(_strategy_dict1) strategy2 = watcher.Strategy(_strategy_dict2) strategy3 = watcher.Strategy(_strategy_dict3) audit_template1 = watcher.AuditTemplate(_audit_template_dict) audit_template2 = watcher.AuditTemplate(_audit_template_dict2) audit_template3 = watcher.AuditTemplate(_audit_template_dict3) TEST.goals.add(goal1) TEST.goals.add(goal2) TEST.strategies.add(strategy1) TEST.strategies.add(strategy2) TEST.strategies.add(strategy3) TEST.audit_templates.add(audit_template1) TEST.audit_templates.add(audit_template2) TEST.audit_templates.add(audit_template3) TEST.audits.add(audit) TEST.action_plans.add(watcher.ActionPlan(action_plan)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/test_formset_table.py0000664000175100017510000000345715033037765026116 0ustar00mylesmyles# 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 django.forms from horizon import tables from horizon.tables import formset as hformset from watcher_dashboard.test import helpers as test class FormsetTableTests(test.TestCase): def test_populate(self): """Create a FormsetDataTable and populate it with data.""" class TableObj(object): pass obj = TableObj() obj.name = 'test object' obj.value = 42 obj.id = 4 class TableForm(django.forms.Form): name = django.forms.CharField() value = django.forms.IntegerField() TableFormset = django.forms.formsets.formset_factory(TableForm, extra=0) class Table(hformset.FormsetDataTable): formset_class = TableFormset name = tables.Column('name') value = tables.Column('value') class Meta(object): name = 'table' table = Table(self.request) table.data = [obj] formset = table.get_formset() self.assertEqual(len(formset), 1) form = formset[0] form_data = form.initial self.assertEqual(form_data['name'], 'test object') self.assertEqual(form_data['value'], 42) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/test/urls.py0000664000175100017510000000135015033037765023204 0ustar00mylesmyles# # 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 django.urls import re_path import openstack_dashboard.urls urlpatterns = [ re_path(r'', urls.include(openstack_dashboard.urls)) ] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.863164 watcher_dashboard-13.1.0.dev5/watcher_dashboard/utils/0000775000175100017510000000000015033037770022023 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/utils/__init__.py0000664000175100017510000000000015033037765024126 0ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/utils/errors.py0000664000175100017510000000567015033037765023725 0ustar00mylesmyles# -*- coding: utf8 -*- # # 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 functools import inspect import horizon.exceptions def handle_errors(error_message, error_default=None, request_arg=None): """A decorator for adding default error handling to API calls. It wraps the original method in a try-except block, with horizon's error handling added. Note: it should only be used on functions or methods that take request as their argument (it has to be named "request", or ``request_arg`` has to be provided, indicating which argument is the request). The decorated method accepts a number of additional parameters: :param _error_handle: whether to handle the errors in this call :param _error_message: override the error message :param _error_default: override the default value returned on error :param _error_redirect: specify a redirect url for errors :param _error_ignore: ignore known errors """ def decorator(func): # XXX This is an ugly hack for finding the 'request' argument. if request_arg is None: for _request_arg, name in enumerate( inspect.getfullargspec(func).args): if name == 'request': break else: raise RuntimeError( "The handle_errors decorator requires 'request' as " "an argument of the function or method being decorated") else: _request_arg = request_arg @functools.wraps(func) def wrapper(*args, **kwargs): _error_handle = kwargs.pop('_error_handle', True) _error_message = kwargs.pop('_error_message', error_message) _error_default = kwargs.pop('_error_default', error_default) _error_redirect = kwargs.pop('_error_redirect', None) _error_ignore = kwargs.pop('_error_ignore', False) if not _error_handle: return func(*args, **kwargs) try: return func(*args, **kwargs) except Exception: request = args[_request_arg] horizon.exceptions.handle(request, _error_message, ignore=_error_ignore, redirect=_error_redirect) return _error_default wrapper.wrapped = func return wrapper return decorator ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/utils/tests.py0000664000175100017510000000561315033037765023550 0ustar00mylesmyles# -*- coding: utf8 -*- # # 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 collections from watcher_dashboard.test import helpers from watcher_dashboard.utils import utils class UtilsTests(helpers.TestCase): def test_de_camel_case(self): ret = utils.de_camel_case('CamelCaseString') self.assertEqual(ret, 'Camel Case String') ret = utils.de_camel_case('SecureSSLConnection') self.assertEqual(ret, 'Secure SSL Connection') ret = utils.de_camel_case('xxXXxx') self.assertEqual(ret, 'xx X Xxx') ret = utils.de_camel_case('XXX') self.assertEqual(ret, 'XXX') ret = utils.de_camel_case('NON Camel Case') self.assertEqual(ret, 'NON Camel Case') def test_list_to_dict(self): Item = collections.namedtuple('Item', 'id') ret = utils.list_to_dict([Item('foo'), Item('bar'), Item('bar')]) self.assertEqual(ret, {'foo': Item('foo'), 'bar': Item('bar')}) def test_length(self): ret = utils.length(iter([])) self.assertEqual(ret, 0) ret = utils.length(iter([1, 2, 3])) self.assertEqual(ret, 3) def test_check_image_type(self): Image = collections.namedtuple('Image', 'properties') ret = utils.check_image_type(Image({'type': 'Picasso'}), 'Picasso') self.assertTrue(ret) ret = utils.check_image_type(Image({'type': 'Picasso'}), 'Van Gogh') self.assertFalse(ret) ret = utils.check_image_type(Image({}), 'Van Gogh') self.assertTrue(ret) def test_filter_items(self): Item = collections.namedtuple('Item', 'index') items = [Item(i) for i in range(7)] ret = list(utils.filter_items(items, index=1)) self.assertEqual(ret, [Item(1)]) ret = list(utils.filter_items(items, index__in=(1, 2, 3))) self.assertEqual(ret, [Item(1), Item(2), Item(3)]) ret = list(utils.filter_items(items, index__not_in=(1, 2, 3))) self.assertEqual(ret, [Item(0), Item(4), Item(5), Item(6)]) def test_safe_int_cast(self): ret = utils.safe_int_cast(1) self.assertEqual(ret, 1) ret = utils.safe_int_cast('1') self.assertEqual(ret, 1) ret = utils.safe_int_cast('') self.assertEqual(ret, 0) ret = utils.safe_int_cast(None) self.assertEqual(ret, 0) ret = utils.safe_int_cast(object()) self.assertEqual(ret, 0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/utils/utils.py0000664000175100017510000000572515033037765023552 0ustar00mylesmyles# -*- coding: utf8 -*- # # 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 re CAMEL_RE = re.compile(r'([A-Z][a-z]+|[A-Z]+(?=[A-Z\s]|$))') def de_camel_case(text): """Convert CamelCase names to human-readable format.""" return ' '.join(w.strip() for w in CAMEL_RE.split(text) if w.strip()) def list_to_dict(object_list, key_attribute='id'): """Converts an object list to a dict :param object_list: list of objects to be put into a dict :type object_list: list :param key_attribute: object attribute used as index by dict :type key_attribute: str :return: dict containing the objects in the list :rtype: dict """ return dict((getattr(o, key_attribute), o) for o in object_list) def length(iterator): """A length function for iterators Returns the number of items in the specified iterator. Note that this function consumes the iterator in the process. """ return sum(1 for _item in iterator) def check_image_type(image, image_type): """Check if image 'type' property matches passed-in image_type. If image has no 'type' property' return True, as we cannot be sure what type of image it is. """ return (image.properties.get('type', image_type) == image_type) def filter_items(items, **kwargs): """Filters the list of items and returns the filtered list. Example usage: >>> class Item(object): ... def __init__(self, index): ... self.index = index ... def __repr__(self): ... return '' % self.index >>> items = [Item(i) for i in range(7)] >>> list(filter_items(items, index=1)) [] >>> list(filter_items(items, index__in=(1, 2, 3))) [, , ] >>> list(filter_items(items, index__not_in=(1, 2, 3))) [, , , ] """ for item in items: for name, value in kwargs.items(): if name.endswith('__in'): if getattr(item, name[:-len('__in')]) not in value: break elif name.endswith('__not_in'): if getattr(item, name[:-len('__not_in')]) in value: break else: if getattr(item, name) != value: break else: yield item def safe_int_cast(value): try: return int(value) except (TypeError, ValueError): return 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924725.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard/version.py0000664000175100017510000000135515033037765022732 0ustar00mylesmyles# Copyright 2012 OpenStack Foundation # # 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 pbr import version version_info = version.VersionInfo('watcher_dashboard') __version__ = version_info.cached_version_string() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1751924727.856164 watcher_dashboard-13.1.0.dev5/watcher_dashboard.egg-info/0000775000175100017510000000000015033037770022355 5ustar00mylesmyles././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924726.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard.egg-info/PKG-INFO0000644000175100017510000000432315033037766023457 0ustar00mylesmylesMetadata-Version: 2.2 Name: watcher-dashboard Version: 13.1.0.dev5 Summary: Watcher Management Dashboard Home-page: https://docs.openstack.org/watcher-dashboard/latest Author: OpenStack Author-email: openstack-discuss@lists.openstack.org Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: OpenStack Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Internet :: WWW/HTTP Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: pbr!=2.1.0,>=2.0.0 Requires-Dist: horizon>=18.2.0 Requires-Dist: PyYAML>=3.12 Requires-Dist: python-watcherclient>=1.1.0 Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: requires-dist Dynamic: requires-python Dynamic: summary ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/watcher-dashboard.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on OpenStack Dashboard plugin for Watcher project ============================================== The Watcher dashboard is a Horizon plugin that will allow users to realize a wide range of cloud optimization goals. * Free software: Apache license * Documentation: https://docs.openstack.org/watcher-dashboard/latest * Source: https://opendev.org/openstack/watcher-dashboard * Bugs: https://bugs.launchpad.net/watcher-dashboard * Release Notes: https://docs.openstack.org/releasenotes/watcher-dashboard * Blueprints: https://blueprints.launchpad.net/watcher-dashboard ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924726.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard.egg-info/SOURCES.txt0000664000175100017510000002026215033037766024250 0ustar00mylesmyles.zuul.yaml AUTHORS ChangeLog HACKING.rst LICENSE README.rst babel-django.cfg babel-djangojs.cfg manage.py pyproject.toml requirements.txt run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini devstack/local.conf.example devstack/plugin.sh devstack/settings doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/contributor/contributing.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/install/installation.rst releasenotes/notes/.placeholder releasenotes/notes/drop-py-2-7-198cca7f72d16655.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/2024.2.rst releasenotes/source/2025.1.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst releasenotes/source/zed.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po tools/install_venv.py tools/install_venv_common.py tools/register_plugin.sh tools/with_venv.sh watcher_dashboard/__init__.py watcher_dashboard/version.py watcher_dashboard.egg-info/PKG-INFO watcher_dashboard.egg-info/SOURCES.txt watcher_dashboard.egg-info/dependency_links.txt watcher_dashboard.egg-info/not-zip-safe watcher_dashboard.egg-info/pbr.json watcher_dashboard.egg-info/requires.txt watcher_dashboard.egg-info/top_level.txt watcher_dashboard/api/__init__.py watcher_dashboard/api/watcher.py watcher_dashboard/common/__init__.py watcher_dashboard/common/exceptions.py watcher_dashboard/conf/watcher_policy.json watcher_dashboard/content/__init__.py watcher_dashboard/content/action_plans/__init__.py watcher_dashboard/content/action_plans/panel.py watcher_dashboard/content/action_plans/tables.py watcher_dashboard/content/action_plans/tabs.py watcher_dashboard/content/action_plans/urls.py watcher_dashboard/content/action_plans/views.py watcher_dashboard/content/actions/__init__.py watcher_dashboard/content/actions/panel.py watcher_dashboard/content/actions/tables.py watcher_dashboard/content/actions/tabs.py watcher_dashboard/content/actions/urls.py watcher_dashboard/content/actions/views.py watcher_dashboard/content/audit_templates/__init__.py watcher_dashboard/content/audit_templates/forms.py watcher_dashboard/content/audit_templates/panel.py watcher_dashboard/content/audit_templates/tables.py watcher_dashboard/content/audit_templates/tabs.py watcher_dashboard/content/audit_templates/tests.py watcher_dashboard/content/audit_templates/urls.py watcher_dashboard/content/audit_templates/views.py watcher_dashboard/content/audits/__init__.py watcher_dashboard/content/audits/forms.py watcher_dashboard/content/audits/panel.py watcher_dashboard/content/audits/tables.py watcher_dashboard/content/audits/tabs.py watcher_dashboard/content/audits/urls.py watcher_dashboard/content/audits/views.py watcher_dashboard/content/goals/__init__.py watcher_dashboard/content/goals/panel.py watcher_dashboard/content/goals/tables.py watcher_dashboard/content/goals/tabs.py watcher_dashboard/content/goals/tests.py watcher_dashboard/content/goals/urls.py watcher_dashboard/content/goals/views.py watcher_dashboard/content/strategies/__init__.py watcher_dashboard/content/strategies/forms.py watcher_dashboard/content/strategies/panel.py watcher_dashboard/content/strategies/tables.py watcher_dashboard/content/strategies/tabs.py watcher_dashboard/content/strategies/tests.py watcher_dashboard/content/strategies/urls.py watcher_dashboard/content/strategies/views.py watcher_dashboard/local/__init__.py watcher_dashboard/local/enabled/_31000_goals_panel.py watcher_dashboard/local/enabled/_31010_strategies_panel.py watcher_dashboard/local/enabled/_31020_watcher_panelgroup.py watcher_dashboard/local/enabled/_31030_audit_templates_panel.py watcher_dashboard/local/enabled/_31040_audits_panel.py watcher_dashboard/local/enabled/_31050_action_plans_panel.py watcher_dashboard/local/enabled/_31060_actions_panel.py watcher_dashboard/local/enabled/__init__.py watcher_dashboard/static/infra_optim/images/chevron.png watcher_dashboard/static/infra_optim/images/power.png watcher_dashboard/static/infra_optim/scss/infra_optim.scss watcher_dashboard/static/infra_optim/tests/formset_table.js watcher_dashboard/templates/client_side/_modal_chart.html watcher_dashboard/templates/client_side/templates.html watcher_dashboard/templates/formset_table/_row.html watcher_dashboard/templates/formset_table/_table.html watcher_dashboard/templates/formset_table/menu_formset.html watcher_dashboard/templates/horizon/common/_items_count_domain_page_header.html watcher_dashboard/templates/infra_optim/_fullscreen_workflow.html watcher_dashboard/templates/infra_optim/_fullscreen_workflow_base.html watcher_dashboard/templates/infra_optim/_performance_chart.html watcher_dashboard/templates/infra_optim/_performance_chart_box.html watcher_dashboard/templates/infra_optim/_top_5_box.html watcher_dashboard/templates/infra_optim/_top_5_chart.html watcher_dashboard/templates/infra_optim/_workflow_base.html watcher_dashboard/templates/infra_optim/base.html watcher_dashboard/templates/infra_optim/base_detail.html watcher_dashboard/templates/infra_optim/header_actions.html watcher_dashboard/templates/infra_optim/qunit.html watcher_dashboard/templates/infra_optim/action_plans/_details_overview.html watcher_dashboard/templates/infra_optim/action_plans/_global_efficacy.html watcher_dashboard/templates/infra_optim/action_plans/create.html watcher_dashboard/templates/infra_optim/action_plans/details.html watcher_dashboard/templates/infra_optim/action_plans/index.html watcher_dashboard/templates/infra_optim/actions/details.html watcher_dashboard/templates/infra_optim/actions/index.html watcher_dashboard/templates/infra_optim/actions_history/details.html watcher_dashboard/templates/infra_optim/actions_history/index.html watcher_dashboard/templates/infra_optim/audit_templates/_create.html watcher_dashboard/templates/infra_optim/audit_templates/create.html watcher_dashboard/templates/infra_optim/audit_templates/details.html watcher_dashboard/templates/infra_optim/audit_templates/index.html watcher_dashboard/templates/infra_optim/audits/_create.html watcher_dashboard/templates/infra_optim/audits/create.html watcher_dashboard/templates/infra_optim/audits/details.html watcher_dashboard/templates/infra_optim/audits/index.html watcher_dashboard/templates/infra_optim/goals/details.html watcher_dashboard/templates/infra_optim/goals/index.html watcher_dashboard/templates/infra_optim/logs/index.html watcher_dashboard/templates/infra_optim/strategies/details.html watcher_dashboard/templates/infra_optim/strategies/index.html watcher_dashboard/templatetags/__init__.py watcher_dashboard/test/__init__.py watcher_dashboard/test/helpers.py watcher_dashboard/test/selenium.py watcher_dashboard/test/settings.py watcher_dashboard/test/test_formset_table.py watcher_dashboard/test/urls.py watcher_dashboard/test/api_tests/__init__.py watcher_dashboard/test/api_tests/test_watcher.py watcher_dashboard/test/integration_tests/__init__.py watcher_dashboard/test/integration_tests/horizon.conf watcher_dashboard/test/integration_tests/pages/__init__.py watcher_dashboard/test/integration_tests/pages/admin/__init__.py watcher_dashboard/test/integration_tests/pages/admin/optimization/__init__.py watcher_dashboard/test/integration_tests/pages/admin/optimization/auditspage.py watcher_dashboard/test/integration_tests/pages/admin/optimization/audittemplatespage.py watcher_dashboard/test/integration_tests/tests/__init__.py watcher_dashboard/test/integration_tests/tests/test_audit_template_panel.py watcher_dashboard/test/test_data/__init__.py watcher_dashboard/test/test_data/exceptions.py watcher_dashboard/test/test_data/utils.py watcher_dashboard/test/test_data/watcher_data.py watcher_dashboard/utils/__init__.py watcher_dashboard/utils/errors.py watcher_dashboard/utils/tests.py watcher_dashboard/utils/utils.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924726.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard.egg-info/dependency_links.txt0000664000175100017510000000000115033037766026430 0ustar00mylesmyles ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924726.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard.egg-info/not-zip-safe0000664000175100017510000000000115033037766024610 0ustar00mylesmyles ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924726.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard.egg-info/pbr.json0000664000175100017510000000005715033037766024042 0ustar00mylesmyles{"git_version": "29be9da", "is_release": false}././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924726.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard.egg-info/requires.txt0000664000175100017510000000011415033037766024756 0ustar00mylesmylespbr!=2.1.0,>=2.0.0 horizon>=18.2.0 PyYAML>=3.12 python-watcherclient>=1.1.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1751924726.0 watcher_dashboard-13.1.0.dev5/watcher_dashboard.egg-info/top_level.txt0000664000175100017510000000002215033037766025106 0ustar00mylesmyleswatcher_dashboard