././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7801814 mistral_dashboard-20.0.0/0000775000175000017500000000000000000000000015276 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/.stestr.conf0000664000175000017500000000007100000000000017545 0ustar00zuulzuul00000000000000[DEFAULT] test_path=./mistraldashboard/tests top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/.zuul.yaml0000664000175000017500000000031200000000000017233 0ustar00zuulzuul00000000000000- project: templates: - check-requirements - horizon-non-primary-django-jobs - openstack-python3-jobs-horizon - publish-openstack-docs-pti - release-notes-jobs-python3 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591352.0 mistral_dashboard-20.0.0/AUTHORS0000664000175000017500000000532200000000000016350 0ustar00zuulzuul00000000000000Adriano Petrich Akihiro Motoki Anastasia Kuznetsova Andreas Jaeger Andreas Jaeger Arnaud M Bo Tran Brad P. Crochet Cao Xuan Hoang Charles Short Christian Berendt Chuck Short Corey Bryant Doug Hellmann Dougal Matthews Eyal Flavio Percoco Gal Margalit Gal Margalit Ghanshyam Mann Hanxi Liu Hardik Parekh Hervé Beraud Ivan Kolodyazhny Jeffrey Zhang Jeremy Liu Jeremy Stanley Kirill Izotov Liat Fried Lingxian Kong Lucky samadhiya Mateusz Kowalski Matthias Runge Nguyen Hai Nguyen Hai Truong Nguyen Hung Phuong Nikolay Mahotkin OpenStack Release Bot Pedro Henrique Rajiv Kumar Renat Akhmerov Rinat Sabitov Sean McGinnis Sharat Sharat Sharma Sharat Sharma Takashi Kajinami Takashi Kajinami Tobias Urdin Tobias Urdin Tony Breeds Vieri <15050873171@163.com> Vu Cong Tuan Zhenguo Niu Zhenguo Niu hardik hparekh jiaqi07 lawrancejing manchandavishal melissaml pengyuesheng privaterookie <996514515@qq.com> ricolin sanu madhavan shubham songwenping sunjia venkatamahesh wu.shiming zhangguoqing zhouyunfeng ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/CONTRIBUTING.rst0000664000175000017500000000117100000000000017737 0ustar00zuulzuul00000000000000The source repository for this project can be found at: https://opendev.org/openstack/mistral-dashboard Pull requests submitted through GitHub are not monitored. To start contributing to OpenStack, follow the steps in the contribution guide to set up and use Gerrit: https://docs.openstack.org/contributors/code-and-documentation/quick-start.html Bugs should be filed on Launchpad: https://bugs.launchpad.net/mistral For more specific information about contributing to this repository, see the Mistral Dashboard contributor guide: https://docs.openstack.org/mistral/latest/developer/contributor/contributing.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591352.0 mistral_dashboard-20.0.0/ChangeLog0000664000175000017500000003276500000000000017065 0ustar00zuulzuul00000000000000CHANGES ======= 20.0.0 ------ * Skip installation to speed up pep8 * reno: Update master for unmaintained/2023.1 * Changed minversion in tox to 3.18.0 * docs: Fix inconsistent format of commands * Add missing core job templates * Bump hacking * Update master for stable/2023.2 * Update master for stable/2023.1 * Update master for stable/2024.2 * reno: Update master for unmaintained/zed * Replace retired oslosphinx * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria 19.0.0 ------ * Update master for stable/2024.1 * reno: Update master for unmaintained/yoga 18.0.0 ------ * Update python classifier in setup.cfg * Update the workflow language syntax documentation url 17.0.0 ------ * Use wf\_identifier when creating a execution * Revert "Use workflow\_id when creating a execution" * Use workflow\_id when creating a execution 16.0.0.0b1 ---------- * Fix tox4 error * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed 15.0.0 ------ * remove unicode from code * Use TOX\_CONSTRAINTS\_FILE * remove unicode from code * Replace deprecated ugettext\_lazy and ungettext\_lazy * Update python testing as per zed cycle testing runtime * Update master for stable/yoga * Address RemovedInDjango40Warning * Update TOX\_CONSTRAINTS\_FILE * Update master for stable/xena * Enforce usage of raw definitions * Replace deprecated inspect.getargspec 14.0.0 ------ * [community goal] Update contributor documentation * setup.cfg: Replace dashes with underscores * Use py3 as the default runtime for tox * Add Python3 xena unit tests * Update master for stable/wallaby * Setting DEFAULT to False * Fix gate jobs 12.0.0 ------ * Add Python3 wallaby unit tests * Update master for stable/victoria 11.0.0 ------ * [goal] Migrate testing to ubuntu focal * Cleanup for Refactor-error-messages * Use unittest.mock instead of mock * Switch to newer openstackdocstheme and reno versions * Bump default tox env from py37 to py38 * Add py38 package metadata * Add Python3 victoria unit tests * Update master for stable/ussuri 10.0.0 ------ * s/assertItemsEqual/assertCountEqual/g * Remove six usage * Drop Django 1.11 support 10.0.0.0b1 ---------- * [ussuri][goal] Drop python 2.7 support and testing * Switch to Ussuri jobs * Use Horizon project template for django jobs * Update master for stable/train 9.0.0.0rc1 ---------- * Fix error when use keystone federation 9.0.0.0b1 --------- * Refactor error messages * Add Python 3 Train unit tests * Remove redundant exception handling * Remove the ErrorHandleTests class * Modify the name of action\_executions panel * Follow the new PTI for document build * Add the unit test for executions * Add the unit test for cron\_triggers * Add the unit test for update\_action\_execution * Add the unit test for create and update action * Add the unit test for executions detail * Add the unit test for workflows detail * Add the unit test for workbooks detail * Add the unit test for tasks detail * Add the unit test for cron\_triggers detail * Add the unit test for action\_executions detail * Add the unit test for actions detail * Modify the url of upper\_constraints\_file * fix the Bug of test in workflows panel * fix the Bug of test in workbooks panel * fix the Bug of test in tasks panel * fix the Bug of test in executions panel * fix the Bug of test in action\_executions panel * fix the Bug of test in cron\_triggers panel * fix the Bug of test in actions panel * Use openstack-python3-train-jobs for python3 test runtime * Add python 3.7 classifier to setup.cfg * Blacklist sphinx 2.1.0 (autodoc bug) * Correct error messages on update action execution form * The columns out of page range * Unified button style on Execute form in workflow panel * Change action\_execution to action\_executions in url * Indent four spaces * Update sphinx dependency * Remove try: ... finally: pass * Add asterisk mark for the required field * Add python37 job * Translate executions state * Add asterisk mark for the required field * Switch to stestr * Dropping the py35 testing * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch * Fix the bug of the py27 test * Update master for stable/stein 8.0.0 ----- * Change openstack-dev to openstack-discuss * Update min tox version to 2.0 * Remove setup.py check from pep8 job * Increment versioning with pbr instruction * Update links in README * add python 3.6 unit test job * import zuul job settings from project-config * Drop nose dependencies * fix mistral dashbaord workflow execution form bug * Update reno for stable/rocky 7.0.0 ----- * Drop mox, no longer needed * Add the restructuredtext check to the flake8 job * fix tox python3 overrides * Ignore the .eggs directory 7.0.0.0b2 --------- * Fix the README formatting * Django 2.0 support * support py3 7.0.0.0b1 --------- * Fix workflow language link * Add py35 to tox.ini * Updated from global requirements * Remove mox3 from test-requiremens.txt * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Update reno for stable/queens 6.0.0 ----- * Updated from global requirements 6.0.0.0b2 --------- * Drop django\_openstack\_auth from requirements.txt 6.0.0.0b1 --------- * Revert "Migrate mistral-dashboard to zuul v3" * Migrate mistral-dashboard to zuul v3 * Updated from global requirements * Fix CSS for "Cancel" button in workflow execution * Updated from global requirements * Expand parameter list for workflow execution * Updated from global requirements * Updated from global requirements * Update reno for stable/pike * Update the URLs 5.0.0 ----- * Updated from global requirements * Updated from global requirements * Change author * Updated from global requirements * Remove newlines from test data 5.0.0.0b2 --------- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements 5.0.0.0b1 --------- * Updated from global requirements * Optimize the link address * Make the definition section of the wb/wf create section editable * Added link to Create Action form Edit * Fix "Workflow" section of "Action Execution Details" view * Change the Next button to Validate * Added test for mistral-dashboard cron\_triggers * Fix oslo\_debug\_helper not running * Updated from global requirements * Add test for mistral-dashboard action\_executions * Update test requirement * Update .gitignore * Reorganize docs * Fix 'tox -ereleasenotes' Error * Replace github with git.openstack.org * Updated from global requirements * Update reno for stable/ocata 4.0.0.0rc1 ---------- * Updated from global requirements * Updated from global requirements * Removed unnecessary utf-8 encoding * Task table - type column would refer to workflow executions accordingly * Remove django<1.8 from tests * Move \_50\_mistral.py file into enabled folder * Remove the dependency on horizon repo * Fixed: Dashboard: "Run action" functionality doesn't work * add CONTRIBUTING.rst * Add Constraints support * Updated from global requirements * Workflow list - added missing fields * Added links to DSL docs in create/update views * Fix docs-gate for mistral-dashboard * Changed "Task Details" to "Task Execution Details" 4.0.0.0b2 --------- * mistral-dashboard:fixed boolean field design bug * mistral-dashboard: added action executions screens * Updated from global requirements * Show team and repo badges on README 4.0.0.0b1 --------- * Fixed action screen "run" button CSS issue * Remove mox in test-requirement.txt * Added reno for stable/mitaka, stable/liberty and stable/newton * Adding files to .gitignore * Cleanup unused files * mistral-dashboard: entities name change for system consistency * Updated from global requirements * Delete \*openstack/common\* in flake8 exclude list * Updated from global requirements * Updated from global requirements * Enable release notes translation * Maintain releasenotes for mistral dashboard * Updated from global requirements * Updated from global requirements * Updated from global requirements * Added sphinix config to setup.cfg 3.0.0.0rc1 ---------- * Clean imports in code * Remove .mailmap and AUTHORS file since it's no longer needed * Clean imports in code 3.0.0.0b3 --------- * Updated from global requirements * Remove requirements satisfied by horizon * Removed 'pull\_right' option * Fixed unit tests issue * Updated from global requirements 3.0.0.0b2 --------- * Updated from global requirements * Updated from global requirements 2.0.0.0rc1 ---------- * Updated from global requirements 2.0.0.0b3 --------- * Fix [ui] Actions screen pagination supports only descending order * UI: Actions screen improvements * Fix gate-mistral-dashboard-python34 * UI: fixed workflow execution don't work * UI: table pagination logic encapsulation * Updated from global requirements * Update URLs to Django 1.8 style * UI: Cron trigger create modal * Updated from global requirements * Updated from global requirements * Updated from global requirements * Mistral-dashboard: Actions screen 2.0.0.0b2 --------- * Updated from global requirements * UI: Enable CSS use in mistral dashboard * UI: Cron Trigger screens - list and overview * UI: Execution & task screens - state info tooltip * Remove "builtins = \_" from tox.ini * UI: Task screen auto refresh * Use the default \`url\` tag instead * UI: Execution screen auto refresh * Delete python bytecode before every test run * Remove version from setup.cfg * UI: Execution pagination on client side 2.0.0.0b1 --------- * Updated from global requirements * UI: Execution update - update description modal * UI: Execution update - table actions * UI:Actions list:remove desc column+details screen * UI: Task & Execution screen refactored * UI: Task & Execution screen refactored * Updated from global requirements * UI: Execution Overview screen refactored * executions overview screen: workflow link * Added home-page link in setup.cfg file * Set version for Mitaka * mistral-dashboard: refer dashboard debug instructions to the updated mistral trbouelshooting page * Add .mailmap for pbr AUTHORS generation * Update AUTHORS file * mistral-dashboard: refer dashboard debug instructions to the updated mistral trbouelshooting page * Add .mailmap for pbr AUTHORS generation * Update AUTHORS file * Changed titles of modal forms 1.0.0.0rc1 ---------- * Updated from global requirements * Updated from global requirements * Mistral-dashboard: Tasks list-addition of Execution detail screen 1.0.0.0b3 --------- * Updated from global requirements * Remove the test cases for handle\_errors to fix the py27 gate issue * Updated from global requirements * mistral-dashboard: added Task details overview screen infrastructure * Rename Mistral dashboard to Workflow * Add tables filter action support * Restrict the pbr version to sync with global requirement * Add actions and executions unit tests * Add unit tests for tasks * mistral-dashboard: executions input & output modal header - added corresponding name * mistral-dashboard executions list table: input & output sections content via a modal * Update AUTHORS * mistral-dashboard - fixed the installation readme: fixed an issue where horizon was not showing mistral dashboard upon installation * Add unit tests for workbooks * mistral-dashboard Execution enhancements * Tasks list - removal and addition of a column 1.0.0.0b2 --------- * Fixed bug in error handing * Correct wrong argument name in workbook\_delete() * Add delete action support * Add update action support * Add create action support * Add mistral APIException as a Recoverable Exception * Add 'update' and 'delete' unit tests for workflows * mistral-dashboard documentation * Add 'index' and 'create' unit tests for workflows * Added execution get method to mistral-dashboard api * Show Action definition * Add actions panel * Add test helpers and test data * Add \*.lock and .secret\_key\_store to .gitignore * Added delete button to execution screen ui on mistral-dashboard * added git clone explanation * Add update workbook support * Show task result when click the id * Fix errors when user click execution id * Add delete workbook support * Add update workflow support * Add create workbook support * Remove H101, H803, H238 from ignore list * Remove H302 check * Add delete workflow support * Add create workflow support * Fix H306 errors * Show workbook definition * Refactor workbook table fields * Show workflow definition * Adding info on how to install mistraldashboard module in venv * Enable Unit Test * Refactor workflow table fields * Reduce API client object in a request * Add handle\_error decorator to API calls * Ignore swap files from getting into repository * Wrap mistralclient code in api.py * Not compressing the mistral specific css * Switch to v2 mistral api 1.0.0.0b1 --------- * Removing redundant header from setup.py * Update .gitreview file for project rename 2015.1.0 -------- * Updating AUTHORS file 0.1.1 ----- * Making style changes and adding AUTHORS file * Fix showing all statuses in UI * Update requirements according to global requirements (master) 0.1 --- * Fix missing static folder * Reenable Task view inside Executions * Modify to use API v2 * Enable hacking check H306 * Fixing readme file * Fixing dependency to Mistral client 0.0.4 ----- * Readme updates * Make use of /executions endpoint API * Horizon plugin system compatibility * Color statuses for both executions and tasks * Add Task's output and parameters columns * Move dashboard from python-mistralclient * Add .gitreview * Initial commit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/LICENSE0000664000175000017500000002363600000000000016315 0ustar00zuulzuul00000000000000 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. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7801814 mistral_dashboard-20.0.0/PKG-INFO0000644000175000017500000000644500000000000016402 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: mistral-dashboard Version: 20.0.0 Summary: Mistral dashboard Home-page: https://docs.openstack.org/mistral/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache License, Version 2.0 Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Requires-Python: >=3.8 License-File: LICENSE Requires-Dist: pbr!=2.1.0,>=2.0.0 Requires-Dist: iso8601>=0.1.11 Requires-Dist: python-mistralclient>=4.3.0 Requires-Dist: PyYAML>=3.12 Requires-Dist: horizon>=17.1.0 ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/mistral-dashboard.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ================= Mistral Dashboard ================= Horizon plugin for Mistral. Setup Instructions ================== This instruction assumes that Horizon is already installed and it's installation folder is . Detailed information on how to install Horizon can be found at https://docs.openstack.org/horizon/latest/contributor/quickstart.html#setup. The installation folder of Mistral Dashboard will be referred to as . The following should get you started. Clone the repository into your local OpenStack directory: .. code:: console $ git clone https://opendev.org/openstack/mistral-dashboard.git Install mistral-dashboard: .. code:: console $ sudo pip install -e Or if you're planning to run Horizon server in a virtual environment (see below): .. code:: console $ tox -evenv -- pip install -e ../mistral-dashboard/ and then: .. code:: console $ cp -b /mistraldashboard/enabled/_50_mistral.py /openstack_dashboard/local/enabled/_50_mistral.py Since Mistral only supports Identity v3, you must ensure that the dashboard points the proper OPENSTACK_KEYSTONE_URL in /openstack_dashboard/local/local_settings.py file:: OPENSTACK_API_VERSIONS = { "identity": 3, } OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST Also, make sure you have changed OPENSTACK_HOST to point to your Keystone server and check all endpoints are accessible. You may want to change OPENSTACK_ENDPOINT_TYPE to "publicURL" if some of them are not. When you're ready, you would need to either restart your apache: .. code:: console $ sudo service apache2 restart or run the development server (in case you have decided to use local horizon): .. code:: console $ cd ../horizon/ $ tox -evenv -- python manage.py runserver Mistral-Dashboard Debug Instructions ------------------------------------ For debug instructions refer to `OpenStack Mistral Troubleshooting `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/README.rst0000664000175000017500000000445600000000000016776 0ustar00zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/mistral-dashboard.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ================= Mistral Dashboard ================= Horizon plugin for Mistral. Setup Instructions ================== This instruction assumes that Horizon is already installed and it's installation folder is . Detailed information on how to install Horizon can be found at https://docs.openstack.org/horizon/latest/contributor/quickstart.html#setup. The installation folder of Mistral Dashboard will be referred to as . The following should get you started. Clone the repository into your local OpenStack directory: .. code:: console $ git clone https://opendev.org/openstack/mistral-dashboard.git Install mistral-dashboard: .. code:: console $ sudo pip install -e Or if you're planning to run Horizon server in a virtual environment (see below): .. code:: console $ tox -evenv -- pip install -e ../mistral-dashboard/ and then: .. code:: console $ cp -b /mistraldashboard/enabled/_50_mistral.py /openstack_dashboard/local/enabled/_50_mistral.py Since Mistral only supports Identity v3, you must ensure that the dashboard points the proper OPENSTACK_KEYSTONE_URL in /openstack_dashboard/local/local_settings.py file:: OPENSTACK_API_VERSIONS = { "identity": 3, } OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST Also, make sure you have changed OPENSTACK_HOST to point to your Keystone server and check all endpoints are accessible. You may want to change OPENSTACK_ENDPOINT_TYPE to "publicURL" if some of them are not. When you're ready, you would need to either restart your apache: .. code:: console $ sudo service apache2 restart or run the development server (in case you have decided to use local horizon): .. code:: console $ cd ../horizon/ $ tox -evenv -- python manage.py runserver Mistral-Dashboard Debug Instructions ------------------------------------ For debug instructions refer to `OpenStack Mistral Troubleshooting `_ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7441814 mistral_dashboard-20.0.0/doc/0000775000175000017500000000000000000000000016043 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/doc/requirements.txt0000664000175000017500000000022500000000000021326 0ustar00zuulzuul00000000000000openstackdocstheme>=2.2.1 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD reno>=3.1.0 # Apache-2.0 docutils>=0.11 # OSI-Approved Open Source, Public Domain ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7441814 mistral_dashboard-20.0.0/doc/source/0000775000175000017500000000000000000000000017343 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/doc/source/conf.py0000664000175000017500000000426100000000000020645 0ustar00zuulzuul00000000000000# 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 sys.path.insert(0, os.path.abspath('../..')) # -- 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.intersphinx', 'openstackdocstheme', ] # 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 = 'mistral-dashboard' copyright = '2014, OpenStack Foundation' # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/mistral-dashboard' openstackdocs_bug_project = 'mistral' openstackdocs_bug_tag = 'doc' # 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 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 = 'openstackdocs' # html_static_path = [] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/doc/source/contributing.rst0000664000175000017500000000011300000000000022577 0ustar00zuulzuul00000000000000============ Contributing ============ .. include:: ../../CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/doc/source/index.rst0000664000175000017500000000037500000000000021211 0ustar00zuulzuul00000000000000 Welcome to Mistral Dashboard's documentation! ============================================= Contents: .. toctree:: :maxdepth: 1 readme contributing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/doc/source/readme.rst0000664000175000017500000000003500000000000021330 0ustar00zuulzuul00000000000000.. include:: ../../README.rst././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/manage.py0000775000175000017500000000151000000000000017100 0ustar00zuulzuul00000000000000#!/usr/bin/env python # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import sys from django.core.management import execute_from_command_line # noqa if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mistraldashboard.test.settings") execute_from_command_line(sys.argv) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7801814 mistral_dashboard-20.0.0/mistral_dashboard.egg-info/0000775000175000017500000000000000000000000022452 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591352.0 mistral_dashboard-20.0.0/mistral_dashboard.egg-info/PKG-INFO0000644000175000017500000000644500000000000023556 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: mistral-dashboard Version: 20.0.0 Summary: Mistral dashboard Home-page: https://docs.openstack.org/mistral/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache License, Version 2.0 Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Requires-Python: >=3.8 License-File: LICENSE Requires-Dist: pbr!=2.1.0,>=2.0.0 Requires-Dist: iso8601>=0.1.11 Requires-Dist: python-mistralclient>=4.3.0 Requires-Dist: PyYAML>=3.12 Requires-Dist: horizon>=17.1.0 ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/mistral-dashboard.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ================= Mistral Dashboard ================= Horizon plugin for Mistral. Setup Instructions ================== This instruction assumes that Horizon is already installed and it's installation folder is . Detailed information on how to install Horizon can be found at https://docs.openstack.org/horizon/latest/contributor/quickstart.html#setup. The installation folder of Mistral Dashboard will be referred to as . The following should get you started. Clone the repository into your local OpenStack directory: .. code:: console $ git clone https://opendev.org/openstack/mistral-dashboard.git Install mistral-dashboard: .. code:: console $ sudo pip install -e Or if you're planning to run Horizon server in a virtual environment (see below): .. code:: console $ tox -evenv -- pip install -e ../mistral-dashboard/ and then: .. code:: console $ cp -b /mistraldashboard/enabled/_50_mistral.py /openstack_dashboard/local/enabled/_50_mistral.py Since Mistral only supports Identity v3, you must ensure that the dashboard points the proper OPENSTACK_KEYSTONE_URL in /openstack_dashboard/local/local_settings.py file:: OPENSTACK_API_VERSIONS = { "identity": 3, } OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST Also, make sure you have changed OPENSTACK_HOST to point to your Keystone server and check all endpoints are accessible. You may want to change OPENSTACK_ENDPOINT_TYPE to "publicURL" if some of them are not. When you're ready, you would need to either restart your apache: .. code:: console $ sudo service apache2 restart or run the development server (in case you have decided to use local horizon): .. code:: console $ cd ../horizon/ $ tox -evenv -- python manage.py runserver Mistral-Dashboard Debug Instructions ------------------------------------ For debug instructions refer to `OpenStack Mistral Troubleshooting `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591352.0 mistral_dashboard-20.0.0/mistral_dashboard.egg-info/SOURCES.txt0000664000175000017500000001601400000000000024340 0ustar00zuulzuul00000000000000.stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog LICENSE README.rst manage.py requirements.txt run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/contributing.rst doc/source/index.rst doc/source/readme.rst mistral_dashboard.egg-info/PKG-INFO mistral_dashboard.egg-info/SOURCES.txt mistral_dashboard.egg-info/dependency_links.txt mistral_dashboard.egg-info/not-zip-safe mistral_dashboard.egg-info/pbr.json mistral_dashboard.egg-info/requires.txt mistral_dashboard.egg-info/top_level.txt mistraldashboard/__init__.py mistraldashboard/api.py mistraldashboard/dashboard.py mistraldashboard/exceptions.py mistraldashboard/forms.py mistraldashboard/handle_errors.py mistraldashboard/action_executions/__init__.py mistraldashboard/action_executions/forms.py mistraldashboard/action_executions/panel.py mistraldashboard/action_executions/tables.py mistraldashboard/action_executions/tests.py mistraldashboard/action_executions/urls.py mistraldashboard/action_executions/views.py mistraldashboard/action_executions/templates/action_executions/_update.html mistraldashboard/action_executions/templates/action_executions/detail.html mistraldashboard/action_executions/templates/action_executions/filtered.html mistraldashboard/action_executions/templates/action_executions/index.html mistraldashboard/action_executions/templates/action_executions/update.html mistraldashboard/actions/__init__.py mistraldashboard/actions/forms.py mistraldashboard/actions/panel.py mistraldashboard/actions/tables.py mistraldashboard/actions/tests.py mistraldashboard/actions/urls.py mistraldashboard/actions/views.py mistraldashboard/actions/templates/actions/_create.html mistraldashboard/actions/templates/actions/_run.html mistraldashboard/actions/templates/actions/_update.html mistraldashboard/actions/templates/actions/create.html mistraldashboard/actions/templates/actions/detail.html mistraldashboard/actions/templates/actions/index.html mistraldashboard/actions/templates/actions/run.html mistraldashboard/actions/templates/actions/update.html mistraldashboard/cron_triggers/__init__.py mistraldashboard/cron_triggers/forms.py mistraldashboard/cron_triggers/panel.py mistraldashboard/cron_triggers/tables.py mistraldashboard/cron_triggers/tests.py mistraldashboard/cron_triggers/urls.py mistraldashboard/cron_triggers/views.py mistraldashboard/cron_triggers/templates/cron_triggers/_create.html mistraldashboard/cron_triggers/templates/cron_triggers/create.html mistraldashboard/cron_triggers/templates/cron_triggers/detail.html mistraldashboard/cron_triggers/templates/cron_triggers/index.html mistraldashboard/default/__init__.py mistraldashboard/default/panel.py mistraldashboard/default/smart_cell.py mistraldashboard/default/utils.py mistraldashboard/default/templates/default/_booleanfield.html mistraldashboard/default/templates/default/_code.html mistraldashboard/default/templates/default/_humantime.html mistraldashboard/default/templates/default/_label.html mistraldashboard/default/templates/default/_preprint.html mistraldashboard/default/templates/default/_prettyprint.html mistraldashboard/default/templates/default/base.html mistraldashboard/default/templates/default/table.html mistraldashboard/enabled/_50_mistral.py mistraldashboard/enabled/__init__.py mistraldashboard/executions/__init__.py mistraldashboard/executions/forms.py mistraldashboard/executions/panel.py mistraldashboard/executions/tables.py mistraldashboard/executions/tests.py mistraldashboard/executions/urls.py mistraldashboard/executions/views.py mistraldashboard/executions/templates/executions/_update_description.html mistraldashboard/executions/templates/executions/detail.html mistraldashboard/executions/templates/executions/index.html mistraldashboard/executions/templates/executions/index_filtered_task.html mistraldashboard/executions/templates/executions/update_description.html mistraldashboard/static/mistraldashboard/css/style.css mistraldashboard/tasks/__init__.py mistraldashboard/tasks/panel.py mistraldashboard/tasks/tables.py mistraldashboard/tasks/tests.py mistraldashboard/tasks/urls.py mistraldashboard/tasks/views.py mistraldashboard/tasks/templates/tasks/detail.html mistraldashboard/tasks/templates/tasks/filtered.html mistraldashboard/tasks/templates/tasks/index.html mistraldashboard/test/__init__.py mistraldashboard/test/helpers.py mistraldashboard/test/settings.py mistraldashboard/test/urls.py mistraldashboard/test/test_data/__init__.py mistraldashboard/test/test_data/mistral_data.py mistraldashboard/test/test_data/utils.py mistraldashboard/workbooks/__init__.py mistraldashboard/workbooks/forms.py mistraldashboard/workbooks/panel.py mistraldashboard/workbooks/tables.py mistraldashboard/workbooks/tests.py mistraldashboard/workbooks/urls.py mistraldashboard/workbooks/views.py mistraldashboard/workbooks/templates/workbooks/_create.html mistraldashboard/workbooks/templates/workbooks/_select_definition.html mistraldashboard/workbooks/templates/workbooks/_update.html mistraldashboard/workbooks/templates/workbooks/create.html mistraldashboard/workbooks/templates/workbooks/detail.html mistraldashboard/workbooks/templates/workbooks/index.html mistraldashboard/workbooks/templates/workbooks/select_definition.html mistraldashboard/workbooks/templates/workbooks/update.html mistraldashboard/workflows/__init__.py mistraldashboard/workflows/forms.py mistraldashboard/workflows/panel.py mistraldashboard/workflows/tables.py mistraldashboard/workflows/tests.py mistraldashboard/workflows/urls.py mistraldashboard/workflows/views.py mistraldashboard/workflows/templates/workflows/_create.html mistraldashboard/workflows/templates/workflows/_execute.html mistraldashboard/workflows/templates/workflows/_select_definition.html mistraldashboard/workflows/templates/workflows/_update.html mistraldashboard/workflows/templates/workflows/create.html mistraldashboard/workflows/templates/workflows/detail.html mistraldashboard/workflows/templates/workflows/execute.html mistraldashboard/workflows/templates/workflows/index.html mistraldashboard/workflows/templates/workflows/select_definition.html mistraldashboard/workflows/templates/workflows/update.html releasenotes/notes/.placeholder releasenotes/notes/bug-1931558-4674cdde721dfab8.yaml releasenotes/notes/drop-py-2-7-022d0dd59feb8b07.yaml releasenotes/notes/drop-python-3-6-and-3-7-ae37bc21f97de767.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/2024.2.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.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././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591352.0 mistral_dashboard-20.0.0/mistral_dashboard.egg-info/dependency_links.txt0000664000175000017500000000000100000000000026520 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591352.0 mistral_dashboard-20.0.0/mistral_dashboard.egg-info/not-zip-safe0000664000175000017500000000000100000000000024700 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591352.0 mistral_dashboard-20.0.0/mistral_dashboard.egg-info/pbr.json0000664000175000017500000000005600000000000024131 0ustar00zuulzuul00000000000000{"git_version": "5e3e024", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591352.0 mistral_dashboard-20.0.0/mistral_dashboard.egg-info/requires.txt0000664000175000017500000000013400000000000025050 0ustar00zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 iso8601>=0.1.11 python-mistralclient>=4.3.0 PyYAML>=3.12 horizon>=17.1.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591352.0 mistral_dashboard-20.0.0/mistral_dashboard.egg-info/top_level.txt0000664000175000017500000000002100000000000025175 0ustar00zuulzuul00000000000000mistraldashboard ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7481816 mistral_dashboard-20.0.0/mistraldashboard/0000775000175000017500000000000000000000000020621 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/__init__.py0000664000175000017500000000000000000000000022720 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7481816 mistral_dashboard-20.0.0/mistraldashboard/action_executions/0000775000175000017500000000000000000000000024344 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/__init__.py0000664000175000017500000000000000000000000026443 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/forms.py0000664000175000017500000000673600000000000026060 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # 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 forms from mistraldashboard import api from mistraldashboard.handle_errors import handle_errors class UpdateForm(forms.SelfHandlingForm): action_execution_id = forms.CharField(label=_("Action Execution ID"), widget=forms.HiddenInput(), required=False) output_source = forms.ChoiceField( label=_('Output'), help_text=_('Content for output. ' 'Select either file, raw content or Null value.'), choices=[('null', _(' (sends empty value)')), ('file', _('File')), ('raw', _('Direct Input'))], widget=forms.Select( attrs={'class': 'switchable', 'data-slug': 'outputsource'} ), required=False ) output_upload = forms.FileField( label=_('Output File'), help_text=_('A local output to upload'), widget=forms.FileInput( attrs={'class': 'switched', 'data-switch-on': 'outputsource', 'data-outputsource-file': _('Output File')} ), required=False ) output_data = forms.CharField( label=_('Output Data'), help_text=_('The raw content for output'), widget=forms.widgets.Textarea( attrs={'class': 'switched', 'data-switch-on': 'outputsource', 'data-outputsource-raw': _('Output Data'), 'rows': 4} ), required=False ) state = forms.ChoiceField( label=_('State'), help_text=_('Select state to update'), choices=[('null', _(' (sends empty value)')), ('SUCCESS', _('Success')), ('ERROR', _('Error'))], widget=forms.Select( attrs={'class': 'switchable'} ), required=False ) def clean(self): cleaned_data = super(UpdateForm, self).clean() cleaned_data['output'] = None if cleaned_data.get('output_upload'): files = self.request.FILES cleaned_data['output'] = files['output_upload'].read() elif cleaned_data.get('output_data'): cleaned_data['output'] = cleaned_data['output_data'] elif cleaned_data.get('output_source') == 'null': cleaned_data['output'] = None del cleaned_data['output_upload'] del cleaned_data['output_data'] del cleaned_data['output_source'] if cleaned_data['state'] == 'null': cleaned_data['state'] = None return cleaned_data @handle_errors(_("Unable to update Action Execution"), []) def handle(self, request, data): return api.action_execution_update( request, data['action_execution_id'], data['state'], data['output'], ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/panel.py0000664000175000017500000000151100000000000026013 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # # 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 from mistraldashboard import dashboard class ActionExecutions(horizon.Panel): name = _("Action Executions") slug = 'action_executions' dashboard.MistralDashboard.register(ActionExecutions) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/tables.py0000664000175000017500000001030600000000000026170 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # # 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 reverse from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from horizon import tables from mistraldashboard import api from mistraldashboard.default import smart_cell from mistraldashboard.default import utils smart_cell.init() class UpdateRow(tables.Row): ajax = True def get_data(self, request, id): instance = api.action_execution_get(request, id) return instance class DeleteActionExecution(tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy( u"Delete Action Execution", u"Delete Action Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( u"Deleted Action Execution", u"Deleted Action Executions", count ) def delete(self, request, action_execution_id): api.action_execution_delete(request, action_execution_id) class UpdateActionExecution(tables.LinkAction): name = "updateAE" verbose_name = _("Update") url = "horizon:mistral:action_executions:update" classes = ("ajax-modal",) class TaskExecutionIDColumn(tables.Column): def get_link_url(self, datum): task_url = "horizon:mistral:tasks:detail" obj_id = datum.task_execution_id return reverse(task_url, args=[obj_id]) class WorkflowNameColumn(tables.Column): def get_link_url(self, datum): workflow_url = "horizon:mistral:workflows:detail" obj_id = datum.workflow_name return reverse(workflow_url, args=[obj_id]) class ActionExecutionsTable(tables.DataTable): def getHoverHelp(data): if hasattr(data, 'state_info') and data.state_info: return {'title': data.state_info} STATE_STATUS_CHOICES = ( ("success", True), ("error", False), ("idle", None), ("running", None), ("canceled", None), ) id = tables.Column( "id", verbose_name=_("ID"), link="horizon:mistral:action_executions:detail" ) name = tables.Column( "name", verbose_name=_("Name") ) tags = tables.Column( "tags", verbose_name=_("Tags") ) workflow_name = WorkflowNameColumn( "workflow_name", verbose_name=_("Workflow Name"), link=True ) task_execution_id = TaskExecutionIDColumn( "task_execution_id", verbose_name=_("Task Execution ID"), link=True ) task_name = tables.Column( "task_name", verbose_name=_("Task name") ) description = tables.Column( "description", verbose_name=_("Description") ) created_at = tables.Column( "created_at", verbose_name=_("Created at"), filters=[utils.humantime] ) updated_at = tables.Column( "updated_at", verbose_name=_("Updated at"), filters=[utils.humantime] ) accepted = tables.Column( "accepted", verbose_name=_("Accepted"), filters=[utils.booleanfield], ) state = tables.Column( "state", status=True, status_choices=STATE_STATUS_CHOICES, verbose_name=_("State"), filters=[utils.label], cell_attributes_getter=getHoverHelp ) class Meta(object): name = "actionExecutions" verbose_name = _("Action Executions") status_columns = ["state"] row_class = UpdateRow table_actions = ( tables.FilterAction, DeleteActionExecution ) row_actions = (UpdateActionExecution, DeleteActionExecution) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7361815 mistral_dashboard-20.0.0/mistraldashboard/action_executions/templates/0000775000175000017500000000000000000000000026342 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7521815 mistral_dashboard-20.0.0/mistraldashboard/action_executions/templates/action_executions/0000775000175000017500000000000000000000000032065 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/templates/action_executions/_update.html0000664000175000017500000000102200000000000034367 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Enter new output and or state to update the corresponding Action Execution." %}

{% trans "For more info refer to:" %}
{% trans "Mistral documentation - Action Executions" %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/templates/action_executions/detail.html0000664000175000017500000000706300000000000034223 0ustar00zuulzuul00000000000000 {% extends 'mistral/default/base.html' %} {% load i18n %} {% block title %}{% trans "Action Execution Details" %}{% endblock %} {% block page_header %}

{% trans "Action Execution Details" %}

{% endblock page_header %} {% block main %} {% load i18n sizeformat %}

{% trans "Overview" %}


{% trans "Name" %}
{{ action_execution.name }}
{% trans "ID" %}
{{ action_execution.id }}
{% if action_execution.description %}
{% trans "Description" %}
{{ action_execution.description }}
{% endif %}
{% trans "State" %}
{{ action_execution.state }}
{% if action_execution.state_info %}
{% trans "State Info" %}
{{ action_execution.state_info }}
{% endif %}
{% trans "Accepted" %}
{{ action_execution.accepted }}
{% trans "Tags" %}
{{ action_execution.tags }}

{% trans "Creation Date" %}
{{ action_execution.created_at|parse_isotime}}
{% trans "Time Since Created" %}
{{ action_execution.created_at|parse_isotime|timesince }}

{% trans "Update Date" %}
{{ action_execution.updated_at|parse_isotime}}
{% trans "Time Since Updated" %}
{{ action_execution.updated_at|parse_isotime|timesince }}

{% trans "Input" %}
{{ action_execution.input }}
{% trans "Output" %}
{{ action_execution.output }}
{% if action_execution.workflow_url %}

{% trans "Workflow" %}


{% trans "Name" %}
{{ action_execution.workflow_name }}
{% endif %} {% if action_execution.task_execution_url %}

{% trans "Task Execution" %}


{% trans "Name" %}
{{ action_execution.task_name }}
{% trans "ID" %}
{{ action_execution.task_execution_id }}
{% endif %} {% endblock %}
././@PaxHeader0000000000000000000000000000020500000000000011452 xustar0000000000000000111 path=mistral_dashboard-20.0.0/mistraldashboard/action_executions/templates/action_executions/filtered.html 22 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/templates/action_executions/filtered.htm0000664000175000017500000000052200000000000034374 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block title %} {% trans "Action Executions" %} {{ task_id }} {% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Action Executions of Task ID:") %}

{{ task_id }}

{% endblock page_header %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/templates/action_executions/index.html0000664000175000017500000000041000000000000034055 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block title %} {% trans "Action Executions" %} {% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Action Executions")%} {% endblock page_header %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/templates/action_executions/update.html0000664000175000017500000000040500000000000034234 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" %} {% endblock page_header %} {% block main %} {% include 'mistral/executions/_update.html' %} {% endblock %}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/tests.py0000664000175000017500000000531100000000000026060 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from openstack_dashboard.test import helpers from mistraldashboard import api from mistraldashboard.test import helpers as test INDEX_URL = reverse('horizon:mistral:action_executions:index') class ActionExecutionsTest(test.TestCase): @helpers.create_mocks({api: ('action_executions_list',)}) def test_index(self): self.mock_action_executions_list.return_value =\ self.mistralclient_action_executions.list() res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'mistral/action_executions/index.html') self.mock_action_executions_list.assert_called_once_with( helpers.IsHttpRequest()) @helpers.create_mocks({api: ('action_execution_update',)}) def test_update_post(self): action_execution = self.mistralclient_action_executions.first() self.mock_action_execution_update.return_value = action_execution form_data = {"action_execution_id": action_execution.id, "state": action_execution.state, "output_source": "raw", "output_data": action_execution.output} res = self.client.post( reverse('horizon:mistral:action_executions:update', args=(action_execution.id,)), form_data) self.assertNoFormErrors(res) self.mock_action_execution_update.assert_called_once_with( helpers.IsHttpRequest(), action_execution.id, action_execution.state, action_execution.output) @helpers.create_mocks({api: ('action_execution_get',)}) def test_detail(self): action_execution = self.mistralclient_action_executions.list()[0] self.mock_action_execution_get.return_value = action_execution url = reverse('horizon:mistral:action_executions:detail', args=[action_execution.id]) res = self.client.get(url) self.assertTemplateUsed(res, 'mistral/action_executions/detail.html') self.mock_action_execution_get.assert_called_once_with( helpers.IsHttpRequest(), action_execution.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/urls.py0000664000175000017500000000250600000000000025706 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # # 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 mistraldashboard.action_executions import views ACTION_EXECUTIONS = r'^(?P[^/]+)/%s$' TASKS = r'^(?P[^/]+)/%s$' urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(ACTION_EXECUTIONS % 'detail', views.OverviewView.as_view(), name='detail'), re_path(ACTION_EXECUTIONS % 'input', views.CodeView.as_view(), {'column': 'input'}, name='input'), re_path(ACTION_EXECUTIONS % 'output', views.CodeView.as_view(), {'column': 'output'}, name='output'), re_path(ACTION_EXECUTIONS % 'update', views.UpdateView.as_view(), name='update'), re_path(TASKS % 'task', views.FilteredByTaskView.as_view(), name='task') ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/action_executions/views.py0000664000175000017500000001255500000000000026063 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # # 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 reverse from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views import generic from horizon import forms from horizon import tables from mistraldashboard.action_executions import forms as action_execution_forms from mistraldashboard.action_executions import tables as mistral_tables from mistraldashboard import api from mistraldashboard.default import utils from mistraldashboard import forms as mistral_forms def get_single_action_execution_data(request, **kwargs): action_execution_id = kwargs['action_execution_id'] action_execution = api.action_execution_get( request, action_execution_id ) return action_execution class OverviewView(generic.TemplateView): template_name = 'mistral/action_executions/detail.html' page_title = _("Action Execution Details") workflow_url = 'horizon:mistral:workflows:detail' task_execution_url = 'horizon:mistral:tasks:detail' def get_context_data(self, **kwargs): context = super(OverviewView, self).get_context_data(**kwargs) action_execution = get_single_action_execution_data( self.request, **kwargs ) if action_execution.workflow_name: action_execution.workflow_url = reverse( self.workflow_url, args=[action_execution.workflow_name]) if action_execution.task_execution_id: action_execution.task_execution_url = reverse( self.task_execution_url, args=[action_execution.task_execution_id] ) if action_execution.input: action_execution.input = utils.prettyprint(action_execution.input) if action_execution.output: action_execution.output = utils.prettyprint( action_execution.output ) if action_execution.state: action_execution.state = utils.label(action_execution.state) action_execution.accepted = utils.booleanfield( action_execution.accepted ) breadcrumb = [(action_execution.id, reverse( 'horizon:mistral:action_executions:detail', args=[action_execution.id] ))] context["custom_breadcrumb"] = breadcrumb context['action_execution'] = action_execution return context class CodeView(forms.ModalFormView): template_name = 'mistral/default/code.html' modal_header = _("Code view") form_id = "code_view" form_class = mistral_forms.EmptyForm cancel_label = "OK" cancel_url = reverse_lazy("horizon:mistral:action_executions:index") page_title = _("Code view") def get_context_data(self, **kwargs): context = super(CodeView, self).get_context_data(**kwargs) column = self.kwargs['column'] action_execution = get_single_action_execution_data( self.request, **self.kwargs ) io = {} if column == 'input': io['name'] = _('Input') io['value'] = utils.prettyprint(action_execution.input) elif column == 'output': io['name'] = _('Output') io['value'] = ( utils.prettyprint(action_execution.output) if action_execution.output else _("No available output yet") ) context['io'] = io return context class IndexView(tables.DataTableView): table_class = mistral_tables.ActionExecutionsTable template_name = 'mistral/action_executions/index.html' def get_data(self): return api.action_executions_list(self.request) class UpdateView(forms.ModalFormView): template_name = 'mistral/action_executions/update.html' modal_header = _("Update Action Execution") form_id = "update_action_execution" form_class = action_execution_forms.UpdateForm submit_label = _("Update") success_url = reverse_lazy("horizon:mistral:action_executions:index") submit_url = "horizon:mistral:action_executions:update" cancel_url = "horizon:mistral:action_executions:index" page_title = _("Update Action Execution") def get_initial(self): return {"action_execution_id": self.kwargs["action_execution_id"]} def get_context_data(self, **kwargs): context = super(UpdateView, self).get_context_data(**kwargs) context['submit_url'] = reverse( self.submit_url, args=[self.kwargs["action_execution_id"]] ) return context class FilteredByTaskView(tables.DataTableView): table_class = mistral_tables.ActionExecutionsTable template_name = 'mistral/action_executions/filtered.html' data = {} def get_data(self, **kwargs): task_id = self.kwargs['task_id'] data = api.action_executions_list(self.request, task_id) return data ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7521815 mistral_dashboard-20.0.0/mistraldashboard/actions/0000775000175000017500000000000000000000000022261 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/__init__.py0000664000175000017500000000000000000000000024360 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/forms.py0000664000175000017500000001467600000000000023777 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 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 mistraldashboard import api class RunForm(forms.SelfHandlingForm): action_name = forms.CharField( label=_("Action"), required=True, widget=forms.TextInput(attrs={'readonly': 'readonly'}) ) input = forms.CharField( label=_("Input"), required=False, initial="{}", widget=forms.widgets.Textarea() ) save_result = forms.CharField( label=_("Save result to DB"), required=False, widget=forms.CheckboxInput() ) def handle(self, request, data): try: input = json.loads(data['input']) except Exception as e: msg = _('Action input is invalid JSON: %s') % str(e) messages.error(request, msg) return False try: params = {"save_result": data['save_result'] == 'True'} action = api.action_run( request, data['action_name'], input, params ) msg = _('Run action has been created with name ' '"%s".') % action.name messages.success(request, msg) return True except Exception as e: # In case of a failure, keep the dialog open and show the error msg = _('Failed to run action "%(action_name)s"' ' %(e)s:') % {'action_name': data['action_name'], 'e': str(e) } messages.error(request, msg) return False class CreateForm(forms.SelfHandlingForm): definition_source = forms.ChoiceField( label=_('Definition Source'), choices=[('file', _('File')), ('raw', _('Direct Input'))], widget=forms.Select( attrs={'class': 'switchable', 'data-slug': 'definitionsource'}) ) definition_upload = forms.FileField( label=_('Definition File'), help_text=_('A local definition to upload.'), widget=forms.FileInput( attrs={'class': 'switched', 'data-switch-on': 'definitionsource', 'data-required-when-shown': 'true', 'data-definitionsource-file': _('Definition File')} ), required=False ) definition_data = forms.CharField( label=_('Definition Data'), help_text=_('The raw contents of the definition.'), widget=forms.widgets.Textarea( attrs={'class': 'switched', 'data-switch-on': 'definitionsource', 'data-required-when-shown': 'true', 'data-definitionsource-raw': _('Definition Data'), 'rows': 4} ), required=False ) def clean(self): cleaned_data = super(CreateForm, self).clean() if cleaned_data.get('definition_upload'): files = self.request.FILES cleaned_data['definition'] = files['definition_upload'].read() elif cleaned_data.get('definition_data'): cleaned_data['definition'] = cleaned_data['definition_data'] else: raise forms.ValidationError( _('You must specify the definition source.')) return cleaned_data def handle(self, request, data): try: api.action_create(request, data['definition']) msg = _('Successfully created action.') messages.success(request, msg) return True except Exception: msg = _('Failed to create action.') redirect = reverse('horizon:mistral:actions:index') exceptions.handle(request, msg, redirect=redirect) class UpdateForm(forms.SelfHandlingForm): definition_source = forms.ChoiceField( label=_('Definition Source'), choices=[('file', _('File')), ('raw', _('Direct Input'))], widget=forms.Select( attrs={'class': 'switchable', 'data-slug': 'definitionsource'}) ) definition_upload = forms.FileField( label=_('Definition File'), help_text=_('A local definition to upload.'), widget=forms.FileInput( attrs={'class': 'switched', 'data-switch-on': 'definitionsource', 'data-definitionsource-file': _('Definition File')} ), required=False ) definition_data = forms.CharField( label=_('Definition Data'), help_text=_('The raw contents of the definition.'), widget=forms.widgets.Textarea( attrs={'class': 'switched', 'data-switch-on': 'definitionsource', 'data-definitionsource-raw': _('Definition Data'), 'rows': 4} ), required=False ) def clean(self): cleaned_data = super(UpdateForm, self).clean() if cleaned_data.get('definition_upload'): files = self.request.FILES cleaned_data['definition'] = files['definition_upload'].read() elif cleaned_data.get('definition_data'): cleaned_data['definition'] = cleaned_data['definition_data'] else: raise forms.ValidationError( _('You must specify the definition source.')) return cleaned_data def handle(self, request, data): try: api.action_update(request, data['definition']) msg = _('Successfully updated action.') messages.success(request, msg) return True except Exception: msg = _('Failed to update action.') redirect = reverse('horizon:mistral:actions:index') exceptions.handle(request, msg, redirect=redirect) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/panel.py0000664000175000017500000000147100000000000023735 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 from mistraldashboard import dashboard class Actions(horizon.Panel): name = _("Actions") slug = 'actions' dashboard.MistralDashboard.register(Actions) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/tables.py0000664000175000017500000000700500000000000024107 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from horizon import tables from horizon.utils import filters from mistraldashboard import api from mistraldashboard.default import smart_cell from mistraldashboard.default import utils smart_cell.init() class CreateAction(tables.LinkAction): name = "create" verbose_name = _("Create Action") url = "horizon:mistral:actions:create" classes = ("ajax-modal",) icon = "plus" class UpdateAction(tables.LinkAction): name = "update" verbose_name = _("Update Action") url = "horizon:mistral:actions:update" classes = ("ajax-modal",) icon = "pencil" class DeleteAction(tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy( u"Delete Action", u"Delete Actions", count ) @staticmethod def action_past(count): return ngettext_lazy( u"Deleted Action", u"Deleted Actions", count ) def delete(self, request, action_name): api.action_delete(request, action_name) def allowed(self, request, action=None): if action: return not action.is_system else: return True def tags_to_string(action): return ', '.join(action.tags) if action.tags else None def cut(action, length=100): inputs = action.input if inputs and len(inputs) > length: return "%s..." % inputs[:length] else: return inputs class RunAction(tables.LinkAction): name = "run" verbose_name = _("Run") classes = ("ajax-modal",) def get_link_url(self, datum): obj_id = datum.name url = "horizon:mistral:actions:run" return reverse(url, args=[obj_id]) class ActionsTable(tables.DataTable): name = tables.Column( "name", verbose_name=_("Name"), link="horizon:mistral:actions:detail" ) is_system = tables.Column( "is_system", verbose_name=_("Is System"), filters=[utils.booleanfield] ) tags = tables.Column( tags_to_string, verbose_name=_("Tags") ) inputs = tables.Column( cut, verbose_name=_("Input") ) created = tables.Column( "created_at", verbose_name=_("Created"), filters=( filters.parse_isotime, filters.timesince_or_never ) ) updated = tables.Column( "updated_at", verbose_name=_("Updated"), filters=( filters.parse_isotime, filters.timesince_or_never ) ) class Meta(object): name = "actions" verbose_name = _("Actions") table_actions = ( CreateAction, UpdateAction, DeleteAction, tables.FilterAction, ) row_actions = (RunAction, DeleteAction) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7361815 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/0000775000175000017500000000000000000000000024257 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7521815 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/actions/0000775000175000017500000000000000000000000025717 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/actions/_create.html0000664000175000017500000000114000000000000030203 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block form_attrs %}enctype="multipart/form-data"{% endblock %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Use one of the available definition source options to specify the definition to be used in creating this action." %}

{% trans "Refer"%} Mistral Workflow Language {% trans " documentation for syntax details." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/actions/_run.html0000664000175000017500000000031700000000000027551 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "From here you can run an action." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/actions/_update.html0000664000175000017500000000114000000000000030222 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block form_attrs %}enctype="multipart/form-data"{% endblock %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Use one of the available definition source options to specify the definition to be used in updating this action." %}

{% trans "Refer"%} Mistral Workflow Language {% trans " documentation for syntax details." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/actions/create.html0000664000175000017500000000026500000000000030053 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create Action" %}{% endblock %} {% block main %} {% include 'mistral/actions/_create.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/actions/detail.html0000664000175000017500000000251700000000000030054 0ustar00zuulzuul00000000000000 {% extends 'mistral/default/base.html' %} {% load i18n %} {% block title %}{% trans "Action Definition" %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Action Definition") %} {% endblock page_header %} {% block main %}

{% trans "Overview" %}


{% trans "Name" %}
{{ action.name }}
{% trans "ID" %}
{{ action.id }}
{% trans "Tags" %}
{{ action.tags }}
{% trans "Created at" %}
{{ action.created_at }}
{% trans "Is system" %}
{{ action.is_system }}
{% trans "Updated at" %}
{{ action.updated_at }}
{% trans "Scope" %}
{{ action.scope }}
{% trans "Input" %}
{{ action.input }}
{% trans "Description" %}
{{ action.description }}
{% trans "Definition" %}
{{ action.definition }}
{% endblock %}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/actions/index.html0000664000175000017500000000035600000000000027720 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block title %}{% trans "Actions" %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Actions") %} {% endblock page_header %}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/actions/run.html0000664000175000017500000000037600000000000027417 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" %} {% endblock page_header %} {% block main %} {% include 'mistral/actions/_run.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/templates/actions/update.html0000664000175000017500000000026500000000000030072 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Update Action" %}{% endblock %} {% block main %} {% include 'mistral/actions/_update.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/tests.py0000664000175000017500000000604200000000000023777 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from openstack_dashboard.test import helpers as horizon_test from mistraldashboard import api from mistraldashboard.test import helpers as test INDEX_URL = reverse('horizon:mistral:actions:index') class ActionsTest(test.TestCase): @horizon_test.create_mocks({api: ('pagination_list',)}) def test_index(self): self.mock_pagination_list.return_value =\ [self.mistralclient_actions.list(), False, False] res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'mistral/actions/index.html') self.mock_pagination_list.assert_called_once_with( entity="actions", request=horizon_test.IsHttpRequest(), marker=None, sort_keys='name', sort_dirs='desc', paginate=True, reversed_order=True) @horizon_test.create_mocks({api: ('action_create',)}) def test_create_post(self): action = self.mistralclient_actions.first() self.mock_action_create.return_value = action url = reverse("horizon:mistral:actions:create") form_data = { 'definition_source': 'raw', 'definition_data': action.definition } res = self.client.post(url, form_data) self.assertNoFormErrors(res) self.mock_action_create.assert_called_once_with( horizon_test.IsHttpRequest(), action.definition) @horizon_test.create_mocks({api: ('action_update',)}) def test_update_post(self): action = self.mistralclient_actions.first() self.mock_action_update.return_value = action url = reverse("horizon:mistral:actions:update") form_data = { 'definition_source': 'raw', 'definition_data': action.definition } res = self.client.post(url, form_data) self.assertNoFormErrors(res) self.mock_action_update.assert_called_once_with( horizon_test.IsHttpRequest(), action.definition) @horizon_test.create_mocks({api: ('action_get',)}) def test_detail(self): action = self.mistralclient_actions.list()[0] self.mock_action_get.return_value = action url = reverse('horizon:mistral:actions:detail', args=[action.id]) res = self.client.get(url) self.assertTemplateUsed(res, 'mistral/actions/detail.html') self.mock_action_get.assert_called_once_with( horizon_test.IsHttpRequest(), action.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/urls.py0000664000175000017500000000205500000000000023622 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 mistraldashboard.actions import views ACTIONS = r'^(?P[^/]+)/%s$' urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(ACTIONS % 'detail', views.DetailView.as_view(), name='detail'), re_path(ACTIONS % 'run', views.RunView.as_view(), name='run'), re_path(r'^create$', views.CreateView.as_view(), name='create'), re_path(r'^update$', views.UpdateView.as_view(), name='update'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/actions/views.py0000664000175000017500000001202300000000000023766 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views import generic from horizon import exceptions from horizon import forms from horizon import tables from mistraldashboard.actions import forms as mistral_forms from mistraldashboard.actions import tables as mistral_tables from mistraldashboard import api from mistraldashboard.default import utils class CreateView(forms.ModalFormView): template_name = 'mistral/actions/create.html' modal_header = _("Create Action") form_id = "create_action" form_class = mistral_forms.CreateForm submit_label = _("Create") submit_url = reverse_lazy("horizon:mistral:actions:create") success_url = reverse_lazy('horizon:mistral:actions:index') page_title = _("Create Action") class UpdateView(forms.ModalFormView): template_name = 'mistral/actions/update.html' modal_header = _("Update Action") form_id = "update_action" form_class = mistral_forms.UpdateForm submit_label = _("Update") submit_url = reverse_lazy("horizon:mistral:actions:update") success_url = reverse_lazy('horizon:mistral:actions:index') page_title = _("Update Action") class IndexView(tables.DataTableView): table_id = "workflow_action" table_class = mistral_tables.ActionsTable template_name = 'mistral/actions/index.html' def has_prev_data(self, table): return self._prev def has_more_data(self, table): return self._more def get_data(self): actions = [] prev_marker = self.request.GET.get( mistral_tables.ActionsTable._meta.prev_pagination_param, None ) if prev_marker is not None: sort_dir = 'asc' marker = prev_marker else: sort_dir = 'desc' marker = self.request.GET.get( mistral_tables.ActionsTable._meta.pagination_param, None ) try: actions, self._more, self._prev = api.pagination_list( entity="actions", request=self.request, marker=marker, sort_keys='name', sort_dirs=sort_dir, paginate=True, reversed_order=True ) if prev_marker is not None: actions = sorted( actions, key=lambda action: getattr( action, 'name' ), reverse=False ) except Exception: self._prev = False self._more = False msg = _('Unable to retrieve actions list.') exceptions.handle(self.request, msg) return actions class DetailView(generic.TemplateView): template_name = 'mistral/actions/detail.html' page_title = _("Action Definition") def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) action = self.get_data(self.request, **kwargs) action.is_system = utils.booleanfield(action.is_system) breadcrumb = [(action.name, reverse( 'horizon:mistral:actions:detail', args=[action.id] ))] context["custom_breadcrumb"] = breadcrumb context['action'] = action return context def get_data(self, request, **kwargs): try: action_name = kwargs['action_name'] action = api.action_get(request, action_name) except Exception: msg = _('Unable to get action "%s".') % action_name redirect = reverse('horizon:mistral:actions:index') exceptions.handle(self.request, msg, redirect=redirect) return action class RunView(forms.ModalFormView): form_class = mistral_forms.RunForm template_name = 'mistral/actions/run.html' form_id = "run_action" success_url = reverse_lazy("horizon:mistral:actions:index") submit_label = _("Run") modal_header = _("Run Action") page_title = _("Run Action") submit_url = "horizon:mistral:actions:run" def get_initial(self, **kwargs): return {'action_name': self.kwargs['action_name']} def get_context_data(self, **kwargs): context = super(RunView, self).get_context_data(**kwargs) context['submit_url'] = reverse( self.submit_url, args=[self.kwargs["action_name"]] ) return context ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/api.py0000664000175000017500000002776100000000000021761 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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. import itertools from django.conf import settings from django.utils.translation import gettext_lazy as _ from horizon.utils import functions as utils from horizon.utils import memoized from mistralclient.api import client as mistral_client from mistraldashboard.handle_errors import handle_errors from openstack_dashboard.api import base SERVICE_TYPE = 'workflowv2' @memoized.memoized def mistralclient(request): return mistral_client.client( username=request.user.username, auth_token=request.user.token.id, project_id=request.user.tenant_id, # We can't use auth_url param in here if we config # and use keystone federation mistral_url=base.url_for(request, 'workflowv2'), # Todo: add SECONDARY_ENDPOINT_TYPE support endpoint_type=getattr( settings, 'OPENSTACK_ENDPOINT_TYPE', 'internalURL' ), service_type=SERVICE_TYPE, # We should not treat definition as file path or uri otherwise # we allow access to contents in internal servers enforce_raw_definition=False ) @handle_errors(_("Unable to retrieve list"), []) def pagination_list(entity, request, marker='', sort_keys='', sort_dirs='asc', paginate=False, reversed_order=False, selector=None): """Retrieve a listing of specific entity and handles pagination. :param entity: Requested entity (String) :param request: Request data :param marker: Pagination marker for large data sets: entity id :param sort_keys: Columns to sort results by :param sort_dirs: Sorting Directions (asc/desc). Default:asc :param paginate: If true will perform pagination based on settings. Default:False :param reversed_order: flag to reverse list. Default:False :param selector: additional selector to allow further server filtering """ limit = getattr(settings, 'API_RESULT_LIMIT', 1000) page_size = utils.get_page_size(request) if paginate: request_size = page_size + 1 else: request_size = limit if reversed_order: sort_dirs = 'desc' if sort_dirs == 'asc' else 'asc' api = mistralclient(request) entities_iter = ( getattr(api, entity).list( selector, marker=marker, limit=limit, sort_keys=sort_keys, sort_dirs=sort_dirs ) if selector else ( getattr(api, entity).list( marker=marker, limit=limit, sort_keys=sort_keys, sort_dirs=sort_dirs ) ) ) has_prev_data = has_more_data = False if paginate: entities = list(itertools.islice(entities_iter, request_size)) # first and middle page condition if len(entities) > page_size: entities.pop(-1) has_more_data = True # middle page condition if marker is not None: has_prev_data = True # first page condition when reached via prev back elif reversed_order and marker is not None: has_more_data = True # last page condition elif marker is not None: has_prev_data = True # restore the original ordering here if reversed_order: entities = sorted(entities, key=lambda ent: (getattr(ent, sort_keys) or '').lower(), reverse=(sort_dirs == 'desc') ) else: entities = list(entities_iter) return entities, has_more_data, has_prev_data def execution_create(request, **data): """Creates new execution.""" return mistralclient(request).executions.create(**data) def execution_get(request, execution_id): """Get specific execution. :param execution_id: Execution ID """ return mistralclient(request).executions.get(execution_id) def execution_update(request, execution_id, field, value): """update specific execution field, either state or description. :param request: Request data :param execution_id: Execution ID :param field: flag - either Execution state or description :param value: new update value """ if field == "state": return mistralclient(request).\ executions.update(execution_id, value) elif field == "description": return mistralclient(request).\ executions.update(execution_id, None, value) def execution_delete(request, execution_name): """Delete execution. :param execution_name: Execution name """ return mistralclient(request).executions.delete(execution_name) @handle_errors(_("Unable to retrieve tasks."), []) def task_list(request, execution_id=None): """Returns all tasks. :param execution_id: Workflow execution ID associated with list of tasks """ return mistralclient(request).tasks.list(execution_id) def task_get(request, task_id=None): """Get specific task. :param task_id: Task ID """ return mistralclient(request).tasks.get(task_id) @handle_errors(_("Unable to retrieve workflows"), []) def workflow_list(request): """Returns all workflows.""" return mistralclient(request).workflows.list() def workflow_get(request, workflow_name): """Get specific workflow. :param workflow_name: Workflow name """ return mistralclient(request).workflows.get(workflow_name) def workflow_create(request, workflows_definition): """Create workflow. :param workflows_definition: Workflows definition """ return mistralclient(request).workflows.create(workflows_definition) def workflow_validate(request, workflow_definition): """Validate workflow. :param workflow_definition: Workflow definition """ return mistralclient(request).workflows.validate(workflow_definition) def workflow_delete(request, workflow_name): """Delete workflow. :param workflow_name: Workflow name """ return mistralclient(request).workflows.delete(workflow_name) def workflow_update(request, workflows_definition): """Update workflow. :param workflows_definition: Workflows definition """ return mistralclient(request).workflows.update(workflows_definition) @handle_errors(_("Unable to retrieve workbooks."), []) def workbook_list(request): """Returns all workbooks.""" return mistralclient(request).workbooks.list() def workbook_get(request, workbook_name): """Get specific workbook. :param workbook_name: Workbook name """ return mistralclient(request).workbooks.get(workbook_name) def workbook_create(request, workbook_definition): """Create workbook. :param workbook_definition: Workbook definition """ return mistralclient(request).workbooks.create(workbook_definition) def workbook_validate(request, workbook_definition): """Validate workbook. :param workbook_definition: Workbook definition """ return mistralclient(request).workbooks.validate(workbook_definition) def workbook_delete(request, workbook_name): """Delete workbook. :param workbook_name: Workbook name """ return mistralclient(request).workbooks.delete(workbook_name) def workbook_update(request, workbook_definition): """Update workbook. :param workbook_definition: Workbook definition """ return mistralclient(request).workbooks.update(workbook_definition) @handle_errors(_("Unable to retrieve actions."), []) def action_list(request): """Returns all actions.""" return mistralclient(request).actions.list() def action_get(request, action_name): """Get specific action. :param action_name: Action name """ return mistralclient(request).actions.get(action_name) def action_create(request, action_definition): """Create action. :param action_definition: Action definition """ return mistralclient(request).actions.create(action_definition) def action_update(request, action_definition): """Update action. :param action_definition: Action definition """ return mistralclient(request).actions.update(action_definition) def action_run(request, action_name, input, params): """Run specific action execution. :param action_name: Action name :param input: input :param params: params """ return mistralclient(request).action_executions.create( action_name, input, **params ) def action_delete(request, action_name): """Delete action. :param action_name: Action name """ return mistralclient(request).actions.delete(action_name) @handle_errors(_("Unable to retrieve action executions list"), []) def action_executions_list(request, task_execution_id=None): """Returns all actions executions. :param request: Request data :param task_execution_id: (Optional) Task Execution ID to filter by """ return mistralclient(request).action_executions.list(task_execution_id) @handle_errors(_("Unable to retrieve action execution"), []) def action_execution_get(request, action_execution_id): """Get specific action execution. :param action_execution_id: Action Execution ID """ return mistralclient(request).action_executions.get(action_execution_id) @handle_errors(_("Unable to delete action execution/s"), []) def action_execution_delete(request, action_execution_id): """Delete action execution. :param action_execution_id: Action execution ID """ return mistralclient(request).action_executions.delete(action_execution_id) def action_execution_update(request, id, state=None, output=None): """Update action execution output and or state. :param id: action execution id :param output: action execution output :param state: action execution state """ return mistralclient(request).action_executions.update(id, state, output) @handle_errors(_("Unable to retrieve cron trigger list"), []) def cron_trigger_list(request): """Returns all cron triggers. :param request: Request data """ return mistralclient(request).cron_triggers.list() @handle_errors(_("Unable to retrieve cron trigger"), []) def cron_trigger_get(request, cron_trigger_name): """Get specific cron trigger. :param request: Request data :param cron_trigger_name: Cron trigger name """ return mistralclient(request).cron_triggers.get(cron_trigger_name) @handle_errors(_("Unable to delete cron trigger/s"), []) def cron_trigger_delete(request, cron_trigger_name): """Delete Cron Trigger. :param request: Request data :param cron_trigger_name: Cron Trigger name """ return mistralclient(request).cron_triggers.delete(cron_trigger_name) def cron_trigger_create( request, cron_trigger_name, workflow_ID, workflow_input, workflow_params, pattern, first_time, count ): """Create Cron Trigger. :param request: Request data :param cron_trigger_name: Cron Trigger name :param workflow_ID: Workflow ID :param workflow_input: Workflow input :param workflow_params: Workflow params <* * * * *> :param pattern: <* * * * *> :param first_time: Date and time of the first execution :param count: Number of wanted executions """ return mistralclient(request).cron_triggers.create( cron_trigger_name, workflow_ID, workflow_input, workflow_params, pattern, first_time, count ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7561815 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/0000775000175000017500000000000000000000000023470 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/__init__.py0000664000175000017500000000000000000000000025567 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/forms.py0000664000175000017500000001606100000000000025174 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # # 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 from django.utils.translation import gettext_lazy as _ from horizon import forms from horizon import messages from mistraldashboard import api from mistraldashboard.default.utils import convert_empty_string_to_none from mistraldashboard.handle_errors import handle_errors class CreateForm(forms.SelfHandlingForm): name = forms.CharField( max_length=255, label=_("Name"), help_text=_('Cron Trigger name.'), required=True ) workflow_id = forms.ChoiceField( label=_('Workflow ID'), help_text=_('Select Workflow ID.'), widget=forms.Select( attrs={'class': 'switchable', 'data-slug': 'workflow_select'} ) ) input_source = forms.ChoiceField( label=_('Input'), help_text=_('JSON of input values defined in the workflow. ' 'Select either file or raw content.'), choices=[('file', _('File')), ('raw', _('Direct Input'))], widget=forms.Select( attrs={'class': 'switchable', 'data-slug': 'inputsource'} ) ) input_upload = forms.FileField( label=_('Input File'), help_text=_('A local input to upload.'), widget=forms.FileInput( attrs={'class': 'switched', 'data-switch-on': 'inputsource', 'data-required-when-shown': 'true', 'data-inputsource-file': _('Input File')} ), required=False ) input_data = forms.CharField( label=_('Input Data'), help_text=_('The raw contents of the input.'), widget=forms.widgets.Textarea( attrs={'class': 'switched', 'data-switch-on': 'inputsource', 'data-required-when-shown': 'true', 'data-inputsource-raw': _('Input Data'), 'rows': 4} ), required=False ) params_source = forms.ChoiceField( label=_('Params'), help_text=_('JSON of params values defined in the workflow. ' 'Select either file or raw content.'), choices=[('file', _('File')), ('raw', _('Direct Input'))], widget=forms.Select( attrs={'class': 'switchable', 'data-slug': 'paramssource'} ) ) params_upload = forms.FileField( label=_('Params File'), help_text=_('A local input to upload.'), widget=forms.FileInput( attrs={'class': 'switched', 'data-switch-on': 'paramssource', 'data-required-when-shown': 'true', 'data-paramssource-file': _('Params File')} ), required=False ) params_data = forms.CharField( label=_('Params Data'), help_text=_('The raw contents of the params.'), widget=forms.widgets.Textarea( attrs={'class': 'switched', 'data-switch-on': 'paramssource', 'data-required-when-shown': 'true', 'data-paramssource-raw': _('Params Data'), 'rows': 4} ), required=False ) first_time = forms.CharField( label=_('First Time (YYYY-MM-DD HH:MM)'), help_text=_('Date and time of the first execution.'), widget=forms.widgets.TextInput(), required=False ) schedule_count = forms.CharField( label=_('Count'), help_text=_('Number of desired executions.'), widget=forms.widgets.TextInput(), required=False ) schedule_pattern = forms.CharField( label=_('Pattern (* * * * *)'), help_text=_('Cron Trigger pattern, mind the space between each char.'), widget=forms.widgets.TextInput(), required=False ) def __init__(self, request, *args, **kwargs): super(CreateForm, self).__init__(request, *args, **kwargs) workflow_list = api.workflow_list(request) workflow_id_list = [] for wf in workflow_list: workflow_id_list.append( (wf.id, "{id} ({name})".format(id=wf.id, name=wf.name)) ) self.fields['workflow_id'].choices = workflow_id_list def clean(self): cleaned_data = super(CreateForm, self).clean() cleaned_data['input'] = "" cleaned_data['params'] = "" if cleaned_data.get('input_upload'): files = self.request.FILES cleaned_data['input'] = files['input_upload'].read() elif cleaned_data.get('input_data'): cleaned_data['input'] = cleaned_data['input_data'] del cleaned_data['input_upload'] del cleaned_data['input_data'] if len(cleaned_data['input']) > 0: try: cleaned_data['input'] = json.loads(cleaned_data['input']) except Exception as e: msg = _('Input is invalid JSON: %s') % str(e) raise forms.ValidationError(msg) if cleaned_data.get('params_upload'): files = self.request.FILES cleaned_data['params'] = files['params_upload'].read() elif cleaned_data.get('params_data'): cleaned_data['params'] = cleaned_data['params_data'] del cleaned_data['params_upload'] del cleaned_data['params_data'] if len(cleaned_data['params']) > 0: try: cleaned_data['params'] = json.loads(cleaned_data['params']) except Exception as e: msg = _('Params is invalid JSON: %s') % str(e) raise forms.ValidationError(msg) return cleaned_data @handle_errors(_("Unable to create Cron Trigger"), []) def handle(self, request, data): data['input'] = convert_empty_string_to_none(data['input']) data['params'] = convert_empty_string_to_none(data['params']) data['schedule_pattern'] = convert_empty_string_to_none( data['schedule_pattern'] ) data['first_time'] = convert_empty_string_to_none(data['first_time']) data['schedule_count'] = convert_empty_string_to_none( data['schedule_count'] ) api.cron_trigger_create( request, data['name'], data['workflow_id'], data['input'], data['params'], data['schedule_pattern'], data['first_time'], data['schedule_count'], ) msg = _('Successfully created Cron Trigger.') messages.success(request, msg) return True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/panel.py0000664000175000017500000000147100000000000025144 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # # 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 from mistraldashboard import dashboard class CronTriggers(horizon.Panel): name = _("Cron Triggers") slug = 'cron_triggers' dashboard.MistralDashboard.register(CronTriggers) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/tables.py0000664000175000017500000000617300000000000025323 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # # 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 reverse from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from horizon import tables from mistraldashboard import api from mistraldashboard.default.utils import humantime class CreateCronTrigger(tables.LinkAction): name = "create" verbose_name = _("Create Cron Trigger") url = "horizon:mistral:cron_triggers:create" classes = ("ajax-modal",) icon = "plus" class DeleteCronTrigger(tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy( u"Delete Cron Trigger", u"Delete Cron Triggers", count ) @staticmethod def action_past(count): return ngettext_lazy( u"Deleted Cron Trigger", u"Deleted Cron Triggers", count ) def delete(self, request, cron_trigger_name): api.cron_trigger_delete(request, cron_trigger_name) class WorkflowColumn(tables.Column): def get_link_url(self, datum): workflow_url = "horizon:mistral:workflows:detail" obj_id = datum.workflow_name return reverse(workflow_url, args=[obj_id]) class CronTriggersTable(tables.DataTable): id = tables.Column( "id", verbose_name=_("ID"), link="horizon:mistral:cron_triggers:detail" ) name = tables.Column( "name", verbose_name=_("Name") ) workflow_name = WorkflowColumn( "workflow_name", verbose_name=_("Workflow"), link=True ) pattern = tables.Column( "pattern", verbose_name=_("Pattern"), ) next_execution_time = tables.Column( "next_execution_time", verbose_name=_("Next Execution Time"), ) remaining_executions = tables.Column( "remaining_executions", verbose_name=_("Remaining Executions"), ) first_execution_time = tables.Column( "first_execution_time", verbose_name=_("First Execution Time"), ) created_at = tables.Column( "created_at", verbose_name=_("Created at"), filters=[humantime] ) updated_at = tables.Column( "updated_at", verbose_name=_("Updated at"), filters=[humantime] ) def get_object_id(self, datum): return datum.name class Meta(object): name = "cron trigger" verbose_name = _("Cron Trigger") table_actions = ( tables.FilterAction, CreateCronTrigger, DeleteCronTrigger ) row_actions = (DeleteCronTrigger,) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7361815 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/templates/0000775000175000017500000000000000000000000025466 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7561815 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/templates/cron_triggers/0000775000175000017500000000000000000000000030335 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/templates/cron_triggers/_create.html0000664000175000017500000000271200000000000032627 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block form_attrs %}enctype="multipart/form-data"{% endblock %} {% block modal-body-right %}

{% trans "Description" %}:

{% blocktrans %} Cron Trigger is an object allowing to run workflow on a schedule. {% endblocktrans %}

{% blocktrans %} Using Cron Triggers it is possible to run workflows according to specific rules: periodically setting a pattern or on external events like Ceilometer alarm. {% endblocktrans %}

{% trans "For more info" %}:


{% trans "Please note" %}:
{% blocktrans %} Name, Workflow ID and Pattern are mandatory fields. {% endblocktrans %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/templates/cron_triggers/create.html0000664000175000017500000000022400000000000032464 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% load static %} {% block main %} {% include 'mistral/cron_triggers/_create.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/templates/cron_triggers/detail.html0000664000175000017500000000650400000000000032472 0ustar00zuulzuul00000000000000 {% extends 'mistral/default/base.html' %} {% load i18n %} {% block title %}{% trans "Cron Trigger Details" %}{% endblock %} {% block page_header %}

{% trans "Cron Trigger Details" %}

{% endblock page_header %} {% block main %} {% load i18n sizeformat %}

{% trans "Overview" %}


{% trans "Name" %}
{{ cron_trigger.name }}
{% trans "ID" %}
{{ cron_trigger.id }}
{% trans "Pattern" %}
{{ cron_trigger.pattern }}

{% trans "Creation Date" %}
{{ cron_trigger.created_at|parse_isotime }}
{% trans "Time Since Created" %}
{{ cron_trigger.created_at|parse_isotime|timesince }}

{% trans "First Execution" %}
{% with time=cron_trigger.first_execution_time %} {{ time|parse_isotime|default:"Empty" }} {% endwith %}
{% trans "Time Since first Execution" %}
{% with time=cron_trigger.first_execution_time %} {{ time|parse_isotime|timesince|default:"Empty" }} {% endwith %}

{% trans "Update Date" %}
{{ cron_trigger.updated_at|parse_isotime|default:"Empty" }}
{% trans "Time Since Updated" %}
{% with time=cron_trigger.updated_at %} {{ time|parse_isotime|timesince|default:"Empty" }} {% endwith %}

{% trans "Next Execution Time" %}
{% with time=cron_trigger.next_execution_time %} {{ time|parse_isotime|default:"Empty" }} {% endwith %}
{% trans "Time Until Next Execution" %}
{% with time=cron_trigger.next_execution_time %} {{ time|parse_isotime|timeuntil|default:"Empty" }} {% endwith %}
{% trans "Remaining Executions" %}
{{ cron_trigger.remaining_executions|default:"0"}}

{% trans "Workflow" %}


{% trans "Workflow Name" %}
{{ cron_trigger.workflow_name }}
{% endblock %}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/templates/cron_triggers/index.html0000664000175000017500000000040000000000000032324 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block title %} {% trans "Cron Triggers" %} {% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Cron Triggers")%} {% endblock page_header %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/tests.py0000664000175000017500000000607500000000000025214 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from openstack_dashboard.test import helpers from mistraldashboard import api from mistraldashboard.test import helpers as test INDEX_URL = reverse('horizon:mistral:cron_triggers:index') class CronTriggersTest(test.TestCase): @helpers.create_mocks({api: ('cron_trigger_list',)}) def test_index(self): self.mock_cron_trigger_list.return_value =\ self.mistralclient_cron_triggers.list() res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'mistral/cron_triggers/index.html') self.mock_cron_trigger_list.assert_called_once_with( helpers.IsHttpRequest()) @helpers.create_mocks({api: ('cron_trigger_create', 'workflow_list')}) def test_create_post(self): cron_trigger = self.mistralclient_cron_triggers.first() workflows = self.mistralclient_workflows.list() self.mock_cron_trigger_create.return_value = cron_trigger self.mock_workflow_list.return_value = workflows url = reverse("horizon:mistral:cron_triggers:create") form_data = { 'name': cron_trigger.name, 'workflow_id': '1', 'input_source': 'raw', 'input_data': '{"a":"b"}', 'params_source': 'raw', 'params_data': '{"a":"b"}', 'schedule_pattern': cron_trigger.pattern, 'first_time': cron_trigger.first_execution_time, 'schedule_count': '1' } res = self.client.post(url, form_data) self.assertNoFormErrors(res) self.mock_cron_trigger_create.assert_called_once_with( helpers.IsHttpRequest(), cron_trigger.name, form_data["workflow_id"], {u'a': u'b'}, {u'a': u'b'}, None, None, form_data["schedule_count"] ) self.mock_workflow_list.assert_called_once_with( helpers.IsHttpRequest()) @helpers.create_mocks({api: ('cron_trigger_get',)}) def test_detail(self): cron_trigger = self.mistralclient_cron_triggers.list()[0] self.mock_cron_trigger_get.return_value = cron_trigger url = reverse('horizon:mistral:cron_triggers:detail', args=[cron_trigger.id]) res = self.client.get(url) self.assertTemplateUsed(res, 'mistral/cron_triggers/detail.html') self.mock_cron_trigger_get.assert_called_once_with( helpers.IsHttpRequest(), cron_trigger.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/urls.py0000664000175000017500000000166600000000000025040 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # # 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 mistraldashboard.cron_triggers import views CRON_TRIGGERS = r'^(?P[^/]+)/%s$' urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(CRON_TRIGGERS % 'detail', views.OverviewView.as_view(), name='detail'), re_path(r'^create$', views.CreateView.as_view(), name='create'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/cron_triggers/views.py0000664000175000017500000000522400000000000025202 0ustar00zuulzuul00000000000000# Copyright 2016 - Nokia. # # 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 reverse from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views import generic from horizon import forms from horizon import tables from mistraldashboard import api from mistraldashboard.cron_triggers import forms as mistral_forms from mistraldashboard.cron_triggers import tables as mistral_tables class OverviewView(generic.TemplateView): template_name = 'mistral/cron_triggers/detail.html' page_title = _("Cron Trigger Details") workflow_url = 'horizon:mistral:workflows:detail' list_url = 'horizon:mistral:cron_triggers:index' def get_context_data(self, **kwargs): context = super(OverviewView, self).get_context_data(**kwargs) cron_trigger = {} cron_trigger = api.cron_trigger_get( self.request, kwargs['cron_trigger_name'] ) cron_trigger.workflow_url = reverse( self.workflow_url, args=[cron_trigger.workflow_name] ) cron_trigger.list_url = reverse_lazy(self.list_url) breadcrumb = [(cron_trigger.name, reverse( 'horizon:mistral:cron_triggers:detail', args=[cron_trigger.name] ))] context["custom_breadcrumb"] = breadcrumb context['cron_trigger'] = cron_trigger return context class CreateView(forms.ModalFormView): template_name = 'mistral/cron_triggers/create.html' modal_header = _("Create Cron Trigger") form_id = "create_cron_trigger" form_class = mistral_forms.CreateForm submit_label = _("Create Cron Trigger") submit_url = reverse_lazy("horizon:mistral:cron_triggers:create") success_url = reverse_lazy('horizon:mistral:cron_triggers:index') page_title = _("Create Cron Trigger") def get_form_kwargs(self): kwargs = super(CreateView, self).get_form_kwargs() return kwargs class IndexView(tables.DataTableView): table_class = mistral_tables.CronTriggersTable template_name = 'mistral/cron_triggers/index.html' def get_data(self): return api.cron_trigger_list(self.request) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/dashboard.py0000664000175000017500000000212600000000000023123 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.utils.translation import gettext_lazy as _ import horizon from mistraldashboard.default import panel class MistralDashboard(horizon.Dashboard): name = _("Workflow") slug = "mistral" panels = ( 'default', 'workbooks', 'workflows', 'actions', 'executions', 'tasks', 'action_executions', 'cron_triggers', ) default_panel = 'default' roles = ('admin',) horizon.register(MistralDashboard) MistralDashboard.register(panel.Default) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7561815 mistral_dashboard-20.0.0/mistraldashboard/default/0000775000175000017500000000000000000000000022245 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/__init__.py0000664000175000017500000000000000000000000024344 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/panel.py0000664000175000017500000000142400000000000023717 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.utils.translation import gettext_lazy as _ import horizon class Default(horizon.Panel): name = _("Default") slug = 'default' urls = 'mistraldashboard.workbooks.urls' nav = False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/smart_cell.py0000664000175000017500000000604700000000000024753 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright © 2015 - Alcatel-Lucent # # 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. """File Overrides OpenStack Horizon Cell method to return the whole row data using cell_attributes_getter""" from django import forms from django import template from horizon.tables import base def get_data(self, datum, column, row): """Fetches the data to be displayed in this cell.""" table = row.table if column.auto == "multi_select": data = "" if row.can_be_selected(datum): widget = forms.CheckboxInput(check_test=lambda value: False) # Convert value to string to avoid accidental type conversion data = widget.render('object_ids', str(table.get_object_id(datum)), {'class': 'table-row-multi-select'}) table._data_cache[column][table.get_object_id(datum)] = data elif column.auto == "form_field": widget = column.form_field if issubclass(widget.__class__, forms.Field): widget = widget.widget widget_name = "%s__%s" % \ (column.name, str(table.get_object_id(datum))) # Create local copy of attributes, so it don't change column # class form_field_attributes form_field_attributes = {} form_field_attributes.update(column.form_field_attributes) # Adding id of the input so it pairs with label correctly form_field_attributes['id'] = widget_name if (template.defaultfilters.urlize in column.filters or template.defaultfilters.yesno in column.filters): data = widget.render(widget_name, column.get_raw_data(datum), form_field_attributes) else: data = widget.render(widget_name, column.get_data(datum), form_field_attributes) table._data_cache[column][table.get_object_id(datum)] = data elif column.auto == "actions": data = table.render_row_actions(datum) table._data_cache[column][table.get_object_id(datum)] = data else: data = column.get_data(datum) if column.cell_attributes_getter: # Following line is the change: cell_attributes_getter called with # "datum" instead of "data" cell_attributes = column.cell_attributes_getter(datum) or {} self.attrs.update(cell_attributes) return data def init(): base.Cell.get_data = get_data ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7361815 mistral_dashboard-20.0.0/mistraldashboard/default/templates/0000775000175000017500000000000000000000000024243 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7601814 mistral_dashboard-20.0.0/mistraldashboard/default/templates/default/0000775000175000017500000000000000000000000025667 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/templates/default/_booleanfield.html0000664000175000017500000000017000000000000031335 0ustar00zuulzuul00000000000000 {{ bool }} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/templates/default/_code.html0000664000175000017500000000060500000000000027627 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal.html" %} {% block modal-header %} {{ io.name }} {% endblock %} {% block modal-body %}
{{ io.value }}
{% endblock %} {% block modal-footer %} {{ cancel_label }} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/templates/default/_humantime.html0000664000175000017500000000013700000000000030704 0ustar00zuulzuul00000000000000{% load humanize %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/templates/default/_label.html0000664000175000017500000000006200000000000027771 0ustar00zuulzuul00000000000000{{ label }} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/templates/default/_preprint.html0000664000175000017500000000003300000000000030553 0ustar00zuulzuul00000000000000
    {{ pre }}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/templates/default/_prettyprint.html0000664000175000017500000000013700000000000031321 0ustar00zuulzuul00000000000000
{{ full }}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/templates/default/base.html0000664000175000017500000000033700000000000027472 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block css %} {% include "_stylesheets.html" %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/templates/default/table.html0000664000175000017500000000021700000000000027644 0ustar00zuulzuul00000000000000{% extends 'mistral/default/base.html' %} {% block main %}
{{ table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/default/utils.py0000664000175000017500000000451200000000000023761 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.template.loader import render_to_string import iso8601 import json TYPES = { 'SUCCESS': 'label-success', 'ERROR': 'label-danger', 'DELAYED': 'label-default', 'PAUSED': 'label-primary', 'RUNNING': 'label-info', } BOOLEAN_FIELD = { 'True': { 'color': 'green', 'icon': 'fa fa-check' }, 'False': { 'color': 'red', 'icon': 'fa fa-remove' } } def label(x): return render_to_string("mistral/default/_label.html", {"label": x, "type": TYPES.get(x)}) def booleanfield(x): # todo: check undefined instead of the if blocks in view # todo: check the red version return render_to_string("mistral/default/_booleanfield.html", {"bool": str(x), "type": BOOLEAN_FIELD.get(str(x))}) def humantime(x): return render_to_string("mistral/default/_humantime.html", {"datetime": iso8601.parse_date(x)}) def prettyprint(x): short = None full = json.dumps(json.loads(x), indent=4, ensure_ascii=False) lines = full.split('\n') if (len(lines) > 5): short = '\n'.join(lines[:5] + ['...']) return render_to_string("mistral/default/_prettyprint.html", {"full": full, "short": short}) def htmlpre(pre): return render_to_string("mistral/default/_preprint.html", {"pre": pre}) def convert_empty_string_to_none(str): """Returns None if given string is empty. Empty string is default for Django form empty HTML input. python-mistral-client does not handle empty strings, only "None" type. :param str: string variable """ return str if len(str) != 0 else None ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7601814 mistral_dashboard-20.0.0/mistraldashboard/enabled/0000775000175000017500000000000000000000000022213 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/enabled/_50_mistral.py0000664000175000017500000000153000000000000024702 0ustar00zuulzuul00000000000000# Copyright (c) 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 mistraldashboard import exceptions DASHBOARD = 'mistral' ADD_INSTALLED_APPS = ['mistraldashboard'] DEFAULT = False ADD_EXCEPTIONS = { 'recoverable': exceptions.RECOVERABLE, 'not_found': exceptions.NOT_FOUND, 'unauthorized': exceptions.UNAUTHORIZED, } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/enabled/__init__.py0000664000175000017500000000000000000000000024312 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/exceptions.py0000664000175000017500000000150100000000000023351 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 mistralclient.api import base from openstack_dashboard import exceptions NOT_FOUND = exceptions.NOT_FOUND RECOVERABLE = exceptions.RECOVERABLE + (base.APIException,) UNAUTHORIZED = exceptions.UNAUTHORIZED ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7601814 mistral_dashboard-20.0.0/mistraldashboard/executions/0000775000175000017500000000000000000000000023007 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/__init__.py0000664000175000017500000000000000000000000025106 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/forms.py0000664000175000017500000000320300000000000024505 0ustar00zuulzuul00000000000000# 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 reverse from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import messages from mistraldashboard import api class UpdateDescriptionForm(forms.SelfHandlingForm): execution_id = forms.CharField(label=_("Execution ID"), widget=forms.HiddenInput(), required=False) description = forms.CharField(max_length=255, label=_("Execution description")) def handle(self, request, data): try: api.execution_update( request, data["execution_id"], "description", data["description"]) msg = _('Successfully updated execution description.') messages.success(request, msg) return True except Exception: msg = _('Failed to update execution description.') redirect = reverse('horizon:mistral:executions:index') exceptions.handle(request, msg, redirect=redirect) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/panel.py0000664000175000017500000000150200000000000024456 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.utils.translation import gettext_lazy as _ import horizon from mistraldashboard import dashboard class Executions(horizon.Panel): name = _("Workflow Executions") slug = 'executions' dashboard.MistralDashboard.register(Executions) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/tables.py0000664000175000017500000001407600000000000024643 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from horizon import exceptions from horizon import tables from mistraldashboard import api from mistraldashboard.default import smart_cell from mistraldashboard.default.utils import humantime from mistraldashboard.default.utils import label smart_cell.init() class DeleteExecution(tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy( u"Delete Execution", u"Delete Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( u"Deleted Execution", u"Deleted Executions", count ) def delete(self, request, execution_name): api.execution_delete(request, execution_name) class CancelExecution(tables.BatchAction): name = "cancel execution" classes = ("btn-danger",) @staticmethod def action_present(count): return ngettext_lazy( u"Cancel Execution", u"Cancel Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( u"Canceled Execution", u"Canceled Executions", count ) def allowed(self, request, instance): if instance.state == "RUNNING": return True return False def action(self, request, obj_id): api.execution_update(request, obj_id, "state", "ERROR") class PauseExecution(tables.BatchAction): name = "pause execution" @staticmethod def action_present(count): return ngettext_lazy( u"Pause Execution", u"Pause Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( u"Paused Execution", u"Paused Executions", count ) def allowed(self, request, instance): if instance.state == "RUNNING": return True return False def action(self, request, obj_id): api.execution_update(request, obj_id, "state", "PAUSED") class ResumeExecution(tables.BatchAction): name = "resume execution" @staticmethod def action_present(count): return ngettext_lazy( u"Resume Execution", u"Resume Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( u"Resumed Execution", u"Resumed Executions", count ) def allowed(self, request, instance): if instance.state == "PAUSED": return True return False def action(self, request, obj_id): api.execution_update(request, obj_id, "state", "RUNNING") class UpdateDescription(tables.LinkAction): name = "updateDescription" verbose_name = _("Update Description") url = "horizon:mistral:executions:update_description" classes = ("ajax-modal",) class UpdateRow(tables.Row): ajax = True def get_data(self, request, id): try: instance = api.execution_get(request, id) except Exception: msg = _('Unable to get execution by ID "%s".') % id exceptions.handle(request, msg) return instance class ExecutionsTable(tables.DataTable): def getHoverHelp(data): if hasattr(data, 'state_info') and data.state_info: return {'title': data.state_info} STATE_STATUS_CHOICES = ( ("success", True), ("error", False), ("paused", False), ("delayed", None), ("running", None), ) STATUS_DISPLAY_CHOICES = ( ("success", _("Success")), ("error", _("Error")), ("paused", _("Paused")), ("delayed", _("Delayed")), ("running", _("Running")), ) id = tables.Column( "id", verbose_name=_("ID"), link="horizon:mistral:executions:detail" ) workflow_name = tables.Column( "workflow_name", verbose_name=_("Workflow") ) task = tables.Column( "task", verbose_name=_("Tasks"), empty_value=_("View"), link="horizon:mistral:tasks:execution" ) input = tables.Column( "", verbose_name=_("Input"), empty_value=_("View"), link="horizon:mistral:executions:input", link_classes=("ajax-modal",) ) output = tables.Column( "", verbose_name=_("Output"), empty_value=_("View"), link="horizon:mistral:executions:output", link_classes=("ajax-modal",) ) created_at = tables.Column( "created_at", verbose_name=_("Created at"), filters=[humantime] ) updated_at = tables.Column( "updated_at", verbose_name=_("Updated at"), filters=[humantime] ) state = tables.Column( "state", verbose_name=_("State"), filters=[label], status=True, status_choices=STATE_STATUS_CHOICES, display_choices=STATUS_DISPLAY_CHOICES, cell_attributes_getter=getHoverHelp ) class Meta(object): name = "executions" verbose_name = _("Executions") status_columns = ["state"] row_class = UpdateRow table_actions = (DeleteExecution, tables.FilterAction) row_actions = (DeleteExecution, UpdateDescription, PauseExecution, CancelExecution, ResumeExecution, DeleteExecution) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7401814 mistral_dashboard-20.0.0/mistraldashboard/executions/templates/0000775000175000017500000000000000000000000025005 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7641814 mistral_dashboard-20.0.0/mistraldashboard/executions/templates/executions/0000775000175000017500000000000000000000000027173 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/templates/executions/_update_description.html0000664000175000017500000000031200000000000034101 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Enter execution description." %}

{% endblock %}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/templates/executions/detail.html0000664000175000017500000000464100000000000031330 0ustar00zuulzuul00000000000000 {% extends 'mistral/default/base.html' %} {% load i18n %} {% block title %}{% trans "Execution Details" %}{% endblock %} {% block page_header %}

{% trans "Execution Details" %}

{% endblock page_header %} {% block main %} {% load i18n sizeformat %}

{% trans "Overview" %}


{% trans "ID" %}
{{ execution.id|default:_("None") }}
{% if execution.description %}
{% trans "Description" %}
{{ execution.description }}
{% endif %}
{% trans "State" %}
{{ execution.state }}
{% if execution.state_info %}
{% trans "State Info" %}
{{ execution.state_info }}
{% endif %}
{% trans "Worflow Name" %}
{{ execution.workflow_name }}
{% trans "Tasks" %}
{% trans "view corresponding tasks" %}

{% trans "Creation Date" %}
{{ execution.created_at|parse_isotime}}
{% trans "Time Since Created" %}
{{ execution.created_at|parse_isotime|timesince }}

{% trans "Update Date" %}
{{ execution.updated_at|parse_isotime}}
{% trans "Time Since Updated" %}
{{ execution.updated_at|parse_isotime|timesince }}

{% trans "Params" %}
{{ execution.params }}
{% trans "Input" %}
{{ execution.input }}
{% trans "Output" %}
{{ execution.output }}
{% endblock %}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/templates/executions/index.html0000664000175000017500000000040700000000000031171 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block title %}{% trans "Workflow Executions" %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Workflow Executions") %} {% endblock page_header %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/templates/executions/index_filtered_task.html0000664000175000017500000000040600000000000034070 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Workflow Executions of Task ID:") %}

{{ task_execution_id }}

{% endblock page_header %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/templates/executions/update_description.html0000664000175000017500000000042100000000000033743 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" %} {% endblock page_header %} {% block main %} {% include 'mistral/executions/_update_description.html' %} {% endblock %}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/tests.py0000664000175000017500000000556200000000000024533 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from openstack_dashboard.test import helpers from mistraldashboard import api from mistraldashboard.test import helpers as test INDEX_URL = reverse('horizon:mistral:executions:index') class ExecutionsTest(test.TestCase): @helpers.create_mocks({api: ('pagination_list',)}) def test_index(self): self.mock_pagination_list.return_value =\ [self.mistralclient_executions.list(), False, False] res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'mistral/executions/index.html') self.assertCountEqual(res.context['table'].data, self.mistralclient_executions.list()) self.mock_pagination_list.assert_called_once_with( entity="executions", request=helpers.IsHttpRequest(), marker=None, sort_dirs='desc', paginate=True) @helpers.create_mocks({api: ('execution_update',)}) def test_update_post(self): execution = self.mistralclient_executions.first() self.mock_execution_update.return_value = execution form_data = { "execution_id": execution.id, "description": "description"} res = self.client.post( reverse('horizon:mistral:executions:update_description', args=(execution.id,)), form_data) self.assertNoFormErrors(res) self.mock_execution_update.assert_called_once_with( helpers.IsHttpRequest(), execution.id, "description", "description") @helpers.create_mocks({api: ('execution_get', 'task_list')}) def test_detail(self): execution = self.mistralclient_executions.list()[0] tasks = self.mistralclient_tasks.list() self.mock_execution_get.return_value = execution self.mock_task_list.return_value = tasks url = reverse('horizon:mistral:executions:detail', args=[execution.id]) res = self.client.get(url) self.assertTemplateUsed(res, 'mistral/executions/detail.html') self.mock_execution_get.assert_called_once_with( helpers.IsHttpRequest(), execution.id) self.mock_task_list.assert_called_once_with( helpers.IsHttpRequest(), execution.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/urls.py0000664000175000017500000000266700000000000024361 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.urls import re_path from mistraldashboard.executions import views EXECUTIONS = r'^(?P[^/]+)/%s$' TASKS = r'^(?P[^/]+)/%s$' urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(EXECUTIONS % 'detail', views.DetailView.as_view(), name='detail'), re_path(TASKS % 'tasks', views.TasksView.as_view(), name='tasks'), re_path(EXECUTIONS % 'detail_task_id', views.DetailView.as_view(), {'caller': 'task'}, name='detail_task_id'), re_path(EXECUTIONS % 'output', views.CodeView.as_view(), {'column': 'output'}, name='output'), re_path(EXECUTIONS % 'input', views.CodeView.as_view(), {'column': 'input'}, name='input'), re_path(EXECUTIONS % 'update_description', views.UpdateDescriptionView.as_view(), name='update_description'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/executions/views.py0000664000175000017500000002202300000000000024515 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.views import generic from django.urls import reverse from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import tables from mistraldashboard import api from mistraldashboard.default import utils from mistraldashboard.executions import forms as m_forms from mistraldashboard.executions import tables as mistral_tables from mistraldashboard import forms as mistral_forms def get_single_data(request, id, type="execution"): """Get Execution or Task data by ID. :param request: Request data :param id: Entity ID :param type: Request dispatch flag, Default: Execution """ if type == "execution": try: execution = api.execution_get(request, id) except Exception: msg = _('Unable to get execution by its ID"%s".') % id redirect = reverse('horizon:mistral:executions:index') exceptions.handle(request, msg, redirect=redirect) return execution elif type == "task": try: task = api.task_get(request, id) except Exception: msg = _('Unable to get task by its ID "%s".') % id redirect = reverse('horizon:mistral:tasks:index') exceptions.handle(request, msg, redirect=redirect) return task elif type == "task_by_execution": try: task = api.task_list(request, id)[0] except Exception: msg = _('Unable to get task by Execution ID "%s".') % id redirect = reverse('horizon:mistral:executions:index') exceptions.handle(request, msg, redirect=redirect) return task class IndexView(tables.DataTableView): table_class = mistral_tables.ExecutionsTable template_name = 'mistral/executions/index.html' def has_prev_data(self, table): return self._prev def has_more_data(self, table): return self._more def get_data(self): executions = [] prev_marker = self.request.GET.get( mistral_tables.ExecutionsTable._meta.prev_pagination_param, None ) if prev_marker is not None: sort_dir = 'asc' marker = prev_marker else: sort_dir = 'desc' marker = self.request.GET.get( mistral_tables.ExecutionsTable._meta.pagination_param, None ) try: executions, self._more, self._prev = api.pagination_list( entity="executions", request=self.request, marker=marker, sort_dirs=sort_dir, paginate=True ) if prev_marker is not None: executions = sorted( executions, key=lambda execution: getattr( execution, 'created_at' ), reverse=True ) except Exception: self._prev = False self._more = False msg = _('Unable to retrieve executions list.') exceptions.handle(self.request, msg) return executions class TasksView(tables.DataTableView): table_class = mistral_tables.ExecutionsTable template_name = 'mistral/executions/index_filtered_task.html' def has_prev_data(self, table): return self._prev def has_more_data(self, table): return self._more def get_data(self): executions = [] prev_marker = self.request.GET.get( mistral_tables.ExecutionsTable._meta.prev_pagination_param, None ) if prev_marker is not None: sort_dir = 'asc' marker = prev_marker else: sort_dir = 'desc' marker = self.request.GET.get( mistral_tables.ExecutionsTable._meta.pagination_param, None ) try: executions, self._more, self._prev = api.pagination_list( entity="executions", request=self.request, marker=marker, sort_dirs=sort_dir, paginate=True, selector=self.kwargs['task_execution_id'] ) if prev_marker is not None: executions = sorted( executions, key=lambda execution: getattr( execution, 'created_at' ), reverse=True ) except Exception: self._prev = False self._more = False msg = _('Unable to retrieve executions list of ' 'the requested task.') exceptions.handle(self.request, msg) return executions class DetailView(generic.TemplateView): template_name = 'mistral/executions/detail.html' page_title = _("Execution Overview") workflow_url = 'horizon:mistral:workflows:detail' task_url = 'horizon:mistral:tasks:execution' def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) task = {} execution = {} if 'caller' in kwargs: if kwargs['caller'] == 'task': kwargs['task_id'] = kwargs['execution_id'] del kwargs['execution_id'] task = get_single_data( self.request, kwargs['task_id'], "task" ) execution = get_single_data( self.request, task.workflow_execution_id, ) else: execution = get_single_data( self.request, kwargs['execution_id'], ) task = get_single_data( self.request, self.kwargs['execution_id'], "task_by_execution" ) execution.workflow_url = reverse(self.workflow_url, args=[execution.workflow_name]) execution.input = utils.prettyprint(execution.input) execution.output = utils.prettyprint(execution.output) execution.params = utils.prettyprint(execution.params) execution.state = utils.label(execution.state) task.url = reverse(self.task_url, args=[execution.id]) breadcrumb = [(execution.id, reverse( 'horizon:mistral:executions:detail', args=[execution.id] ))] context["custom_breadcrumb"] = breadcrumb context['execution'] = execution context['task'] = task return context class CodeView(forms.ModalFormView): template_name = 'mistral/default/code.html' modal_header = _("Code view") form_id = "code_view" form_class = mistral_forms.EmptyForm cancel_label = "OK" cancel_url = reverse_lazy("horizon:mistral:executions:index") page_title = _("Code view") def get_context_data(self, **kwargs): context = super(CodeView, self).get_context_data(**kwargs) execution = get_single_data( self.request, self.kwargs['execution_id'], ) column = self.kwargs['column'] io = {} if column == 'input': io['name'] = _('Input') io['value'] = execution.input = utils.prettyprint(execution.input) elif column == 'output': io['name'] = _('Output') io['value'] = execution.output = utils.prettyprint( execution.output ) context['io'] = io return context class UpdateDescriptionView(forms.ModalFormView): template_name = 'mistral/executions/update_description.html' modal_header = _("Update Execution Description") form_id = "update_execution_description" form_class = m_forms.UpdateDescriptionForm submit_label = _("Update") success_url = reverse_lazy("horizon:mistral:executions:index") submit_url = "horizon:mistral:executions:update_description" cancel_url = "horizon:mistral:executions:index" page_title = _("Update Execution Description") def get_initial(self): return {"execution_id": self.kwargs["execution_id"]} def get_context_data(self, **kwargs): context = super(UpdateDescriptionView, self).get_context_data(**kwargs) context['submit_url'] = reverse( self.submit_url, args=[self.kwargs["execution_id"]] ) return context ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/forms.py0000664000175000017500000000122600000000000022322 0ustar00zuulzuul00000000000000# 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 import forms class EmptyForm(forms.SelfHandlingForm): def handle(self, request, data): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/handle_errors.py0000664000175000017500000000564000000000000024027 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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): if request_arg is None: _request_arg = 'request' if _request_arg not in inspect.getfullargspec(func).args: 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 as e: callargs = inspect.getcallargs(func, *args, **kwargs) request = callargs[_request_arg] _error_message += ': ' + str(e) horizon.exceptions.handle(request, _error_message, ignore=_error_ignore, redirect=_error_redirect) return _error_default wrapper.wrapped = func return wrapper return decorator ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7401814 mistral_dashboard-20.0.0/mistraldashboard/static/0000775000175000017500000000000000000000000022110 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7401814 mistral_dashboard-20.0.0/mistraldashboard/static/mistraldashboard/0000775000175000017500000000000000000000000025433 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7641814 mistral_dashboard-20.0.0/mistraldashboard/static/mistraldashboard/css/0000775000175000017500000000000000000000000026223 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/static/mistraldashboard/css/style.css0000664000175000017500000000120200000000000030070 0ustar00zuulzuul00000000000000.mistral-wrapper.list{ list-style: inherit; } .mistral-wrapper #actions{ width:100%; } .mistral-wrapper #actions a.btn{ width:initial; } .mistral-wrapper.detail-screen .page-breadcrumb ol li{ max-width: inherit; } .mistral-wrapper.detail-screen .page-breadcrumb li:last-child{ display:none; } .mistral-wrapper .navbar-brand{ padding: 6px 10px; } .boolfield{ font-style: italic; } .boolfield i{ padding-right: .2em; } .boolfield i.green{ color: green; } .boolfield i.red{ color: red; } .line-space{ margin: .3em 0; } .line-space dd{ display:inline-block; margin-left: 1.5em; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7641814 mistral_dashboard-20.0.0/mistraldashboard/tasks/0000775000175000017500000000000000000000000021746 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/tasks/__init__.py0000664000175000017500000000000000000000000024045 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/tasks/panel.py0000664000175000017500000000145700000000000023426 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.utils.translation import gettext_lazy as _ import horizon from mistraldashboard import dashboard class Tasks(horizon.Panel): name = _("Task Executions") slug = 'tasks' dashboard.MistralDashboard.register(Tasks) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/tasks/tables.py0000664000175000017500000000702600000000000023577 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.template.defaultfilters import title from django.urls import reverse from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import tables from mistraldashboard import api from mistraldashboard.default import smart_cell from mistraldashboard.default.utils import humantime from mistraldashboard.default.utils import label smart_cell.init() class UpdateRow(tables.Row): ajax = True def get_data(self, request, id): try: instance = api.task_get(request, id) except Exception: msg = _('Unable to get task by ID "%s".') % id exceptions.handle(request, msg) return instance class TypeColumn(tables.Column): def get_link_url(self, datum): obj_id = datum.id url = "" action_execution_url = "horizon:mistral:action_executions:task" workflow_execution_url = "horizon:mistral:executions:tasks" if datum.type == "ACTION": url = action_execution_url elif datum.type == "WORKFLOW": url = workflow_execution_url return reverse(url, args=[obj_id]) class TaskTable(tables.DataTable): def getHoverHelp(data): if hasattr(data, 'state_info') and data.state_info: return {'title': data.state_info} STATE_STATUS_CHOICES = ( ("success", True), ("error", False), ("idle", None), ("running", None), ) id = tables.Column( "id", verbose_name=_("ID"), link="horizon:mistral:tasks:detail" ) name = tables.Column( "name", verbose_name=_("Name") ) workflow_execution_id = tables.Column( "workflow_execution_id", verbose_name=_("Workflow Execution ID"), link="horizon:mistral:executions:detail_task_id" ) type = TypeColumn( "type", verbose_name=_("Type"), filters=[title], link=True ) result = tables.Column( "", verbose_name=_("Result"), empty_value=_("View"), link="horizon:mistral:tasks:result", link_classes=("ajax-modal",) ) published = tables.Column( "", verbose_name=_("Published"), empty_value=_("View"), link="horizon:mistral:tasks:published", link_classes=("ajax-modal",) ) created_at = tables.Column( "created_at", verbose_name=_("Created at"), filters=[humantime] ) updated_at = tables.Column( "updated_at", verbose_name=_("Updated at"), filters=[humantime] ) state = tables.Column( "state", status=True, status_choices=STATE_STATUS_CHOICES, verbose_name=_("State"), filters=[label], cell_attributes_getter=getHoverHelp ) class Meta(object): name = "tasks" verbose_name = _("Tasks") table_actions = (tables.FilterAction,) status_columns = ["state"] row_class = UpdateRow ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7401814 mistral_dashboard-20.0.0/mistraldashboard/tasks/templates/0000775000175000017500000000000000000000000023744 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7641814 mistral_dashboard-20.0.0/mistraldashboard/tasks/templates/tasks/0000775000175000017500000000000000000000000025071 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/tasks/templates/tasks/detail.html0000664000175000017500000000510700000000000027224 0ustar00zuulzuul00000000000000 {% extends 'mistral/default/base.html' %} {% load i18n %} {% block title %}{% trans "Task Execution Details" %}{% endblock %} {% block page_header %}

{% trans "Task Execution Details" %}

{% endblock page_header %} {% block main %}

{% trans "Overview" %}


{% trans "Name" %}
{{ task.name }}
{% trans "ID" %}
{{ task.id }}
{% trans "Type" %}
{{ task.type |title }}
{% if task.state_info %}
{% trans "State Info" %}
{{ task.state_info }}
{% endif %}
{% trans "State" %}
{{ task.state }}

{% trans "Creation Date" %}
{{ task.created_at|parse_isotime }}
{% trans "Time Since Created" %}
{{ task.created_at|parse_isotime|timesince }}

{% trans "Update Date" %}
{{ task.updated_at|parse_isotime }}
{% trans "Time Since Updated" %}
{{ task.updated_at|parse_isotime|timesince }}

{% trans "Result" %}
{{ task.result }}
{% trans "Published" %}
{{ task.published }}

{% trans "Workflow" %}


{% trans "Workflow Name" %}
{{ task.workflow_name }}
{% trans "Workflow Execution ID" %}
{{ task.workflow_execution_id }}
{% endblock %}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/tasks/templates/tasks/filtered.html0000664000175000017500000000051500000000000027556 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block title %} {% trans "Tasks" %} {{ task_id }} {% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Tasks of Workflow Execution ID:") %}

{{ execution_id }}

{% endblock page_header %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/tasks/templates/tasks/index.html0000664000175000017500000000040500000000000027065 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block title %} {% trans "Task Executions" %} {% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Task Executions") %} {% endblock page_header %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/tasks/tests.py0000664000175000017500000000341200000000000023462 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from openstack_dashboard.test import helpers from mistraldashboard import api from mistraldashboard.test import helpers as test INDEX_URL = reverse('horizon:mistral:tasks:index') class TasksTest(test.TestCase): @helpers.create_mocks({api: ('task_list',)}) def test_index(self): self.mock_task_list.return_value =\ self.mistralclient_tasks.list() res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'mistral/tasks/index.html') self.assertCountEqual(res.context['table'].data, self.mistralclient_tasks.list()) self.mock_task_list.assert_called_once_with(helpers.IsHttpRequest()) @helpers.create_mocks({api: ('task_get',)}) def test_detail(self): task = self.mistralclient_tasks.list()[0] self.mock_task_get.return_value = task url = reverse('horizon:mistral:tasks:detail', args=[task.id]) res = self.client.get(url) self.assertTemplateUsed(res, 'mistral/tasks/detail.html') self.mock_task_get.assert_called_once_with( helpers.IsHttpRequest(), task.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/tasks/urls.py0000664000175000017500000000230700000000000023307 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.urls import re_path from mistraldashboard.tasks import views TASKS = r'^(?P[^/]+)/%s$' EXECUTIONS = r'^(?P[^/]+)/%s$' urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(TASKS % 'detail', views.OverviewView.as_view(), name='detail'), re_path(EXECUTIONS % 'execution', views.ExecutionView.as_view(), name='execution'), re_path(TASKS % 'result', views.CodeView.as_view(), {'column': 'result'}, name='result'), re_path(TASKS % 'published', views.CodeView.as_view(), {'column': 'published'}, name='published'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/tasks/views.py0000664000175000017500000001070300000000000023456 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.urls import reverse from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views import generic from horizon import exceptions from horizon import forms from horizon import tables from mistraldashboard import api from mistraldashboard.default import utils from mistraldashboard import forms as mistral_forms from mistraldashboard.tasks import tables as mistral_tables def get_single_task_data(request, **kwargs): try: task_id = kwargs['task_id'] task = api.task_get(request, task_id) except Exception: msg = _('Unable to get task "%s".') % task_id redirect = reverse('horizon:mistral:tasks:index') exceptions.handle(request, msg, redirect=redirect) return task class ExecutionView(tables.DataTableView): table_class = mistral_tables.TaskTable template_name = 'mistral/tasks/filtered.html' def get_data(self, **kwargs): try: execution_id = self.kwargs['execution_id'] tasks = api.task_list(self.request, execution_id) except Exception: msg = _('Unable to get task by execution id "%s".') % execution_id redirect = reverse('horizon:mistral:executions:index') exceptions.handle(self.request, msg, redirect=redirect) return tasks class OverviewView(generic.TemplateView): template_name = 'mistral/tasks/detail.html' page_title = _("Task Details") workflow_detail_url = 'horizon:mistral:workflows:detail' workflow_execution_tasks_url = 'horizon:mistral:executions:tasks' execution_url = 'horizon:mistral:executions:detail' action_execution_url = 'horizon:mistral:action_executions:task' def get_context_data(self, **kwargs): context = super(OverviewView, self).get_context_data(**kwargs) task = get_single_task_data(self.request, **kwargs) task.workflow_detail_url = reverse(self.workflow_detail_url, args=[task.workflow_name]) task.execution_url = reverse(self.execution_url, args=[task.workflow_execution_id]) task.result = utils.prettyprint(task.result) task.published = utils.prettyprint(task.published) task.state = utils.label(task.state) if task.type == "ACTION": task.type_url = reverse( self.action_execution_url, args=[task.id] ) elif task.type == "WORKFLOW": task.type_url = reverse( self.workflow_execution_tasks_url, args=[task.id] ) breadcrumb = [(task.id, reverse( 'horizon:mistral:tasks:detail', args=[task.id] ))] context["custom_breadcrumb"] = breadcrumb context['task'] = task return context class CodeView(forms.ModalFormView): template_name = 'mistral/default/code.html' modal_header = _("Code view") form_id = "code_view" form_class = mistral_forms.EmptyForm cancel_label = "OK" cancel_url = reverse_lazy("horizon:mistral:tasks:index") page_title = _("Code view") def get_context_data(self, **kwargs): context = super(CodeView, self).get_context_data(**kwargs) column = self.kwargs['column'] task = get_single_task_data(self.request, **self.kwargs) io = {} if column == 'result': io['name'] = _('Result') io['value'] = task.result = utils.prettyprint(task.result) elif column == 'published': io['name'] = _('Published') io['value'] = task.published = utils.prettyprint(task.published) context['io'] = io return context class IndexView(tables.DataTableView): table_class = mistral_tables.TaskTable template_name = 'mistral/tasks/index.html' def get_data(self): return api.task_list(self.request) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7641814 mistral_dashboard-20.0.0/mistraldashboard/test/0000775000175000017500000000000000000000000021600 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/test/__init__.py0000664000175000017500000000000000000000000023677 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/test/helpers.py0000664000175000017500000000176400000000000023624 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from openstack_dashboard.test import helpers from mistraldashboard.test.test_data import utils class MistralTestsMixin(object): def _setup_test_data(self): super(MistralTestsMixin, self)._setup_test_data() utils.load_test_data(self) class TestCase(MistralTestsMixin, helpers.TestCase): pass class APITestCase(MistralTestsMixin, helpers.APITestCase): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/test/settings.py0000664000175000017500000000154700000000000024021 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # Copyright 2016 NEC Corporation. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from horizon.test.settings import * # noqa from openstack_dashboard.test.settings import * # noqa INSTALLED_APPS = list(INSTALLED_APPS) # noqa: F405 INSTALLED_APPS.append('mistraldashboard') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7681816 mistral_dashboard-20.0.0/mistraldashboard/test/test_data/0000775000175000017500000000000000000000000023550 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/test/test_data/__init__.py0000664000175000017500000000000000000000000025647 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/test/test_data/mistral_data.py0000664000175000017500000001142300000000000026567 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 mistralclient.api.v2 import action_executions from mistralclient.api.v2 import actions from mistralclient.api.v2 import cron_triggers from mistralclient.api.v2 import executions from mistralclient.api.v2 import tasks from mistralclient.api.v2 import workbooks from mistralclient.api.v2 import workflows from openstack_dashboard.test.test_data import utils as test_data_utils # Workbooks WB_DEF = """ --- version: 2.0 name: wb workflows: wf1: type: direct input: - param1 - param2 tasks: task1: action: std.http url="localhost:8989" on-success: - test_subsequent test_subsequent: action: std.http url="http://some_url" server_id=1 """.strip() # Workflows WF_DEF = """ version: '2.0' flow: tasks: task1: action: nova.servers_get server="1" """.strip() def data(TEST): # MistralActions TEST.mistralclient_actions = test_data_utils.TestDataContainer() action_1 = actions.Action( actions.ActionManager(None), {'name': 'a', 'id': '1', 'is_system': True, 'input': 'param1', 'description': 'my cool action', 'tags': ['test'], 'definition': '1', 'created_at': '1', 'updated_at': '1' } ) TEST.mistralclient_actions.add(action_1) # MistralExecutions TEST.mistralclient_executions = test_data_utils.TestDataContainer() execution_1 = executions.Execution( executions.ExecutionManager(None), {'id': '123', 'workflow_name': 'my_wf', 'description': '', 'state': 'RUNNING', 'input': '{"person": {"first_name": "John", "last_name": "Doe"}}', 'output': "1", 'params': "1", } ) TEST.mistralclient_executions.add(execution_1) # Tasks TEST.mistralclient_tasks = test_data_utils.TestDataContainer() task_1 = tasks.Task( tasks.TaskManager(None), {'id': '1', 'workflow_execution_id': '123', 'name': 'my_task', 'workflow_name': 'my_wf', 'state': 'RUNNING', 'type': 'ACTION', 'tags': ['deployment', 'demo'], 'result': "1", 'published': '{"a":"1"}'}) TEST.mistralclient_tasks.add(task_1) # Workbooks TEST.mistralclient_workbooks = test_data_utils.TestDataContainer() workbook_1 = workbooks.Workbook( workbooks.WorkbookManager(None), {'name': 'a', 'tags': ['a', 'b'], 'created_at': '1', 'updated_at': '1', 'definition': WB_DEF} ) TEST.mistralclient_workbooks.add(workbook_1) # Workflows TEST.mistralclient_workflows = test_data_utils.TestDataContainer() workflow_1 = workflows.Workflow( workflows.WorkflowManager(None), {'name': 'a', 'id': '1', 'tags': ['a', 'b'], 'input': 'param', 'created_at': '1', 'updated_at': '1', 'definition': WF_DEF} ) TEST.mistralclient_workflows.add(workflow_1) # MistralActionsExecutions TEST.mistralclient_action_executions = test_data_utils.TestDataContainer() action_executions_1 = action_executions.ActionExecution( action_executions.ActionExecutionManager(None), {'id': '1', 'name': 'a', 'tags': ['a', 'b'], 'workflow_name': 'my work flow', 'task_execution_id': '1', 'task_name': 'b', 'input': "1", 'output': "1", 'description': '', 'created_at': '1', 'updated_at': '1', 'accepted': True, 'state': 'SUCCESS' } ) TEST.mistralclient_action_executions.add(action_executions_1) # MistralCronTriggers TEST.mistralclient_cron_triggers = test_data_utils.TestDataContainer() cron_triggers_1 = cron_triggers.CronTrigger( cron_triggers.CronTriggerManager(None), {'id': '1', 'name': 'a', 'workflow_name': 'my work flow', 'pattern': '', 'next_execution_time': '', 'remaining_executions': '', 'first_execution_time': '', 'created_at': '1', 'updated_at': '1' }) TEST.mistralclient_cron_triggers.add(cron_triggers_1) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/test/test_data/utils.py0000664000175000017500000000216000000000000025261 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 utils def load_test_data(load_onto=None): from mistraldashboard.test.test_data import mistral_data from openstack_dashboard.test.test_data import exceptions # The order of these loaders matters, some depend on others. loaders = ( exceptions.data, mistral_data.data, ) if load_onto: for data_func in loaders: data_func(load_onto) return load_onto else: return utils.TestData(*loaders) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/test/urls.py0000664000175000017500000000131100000000000023133 0ustar00zuulzuul00000000000000# # 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 import openstack_dashboard.urls urlpatterns = [ urls.url(r'', urls.include(openstack_dashboard.urls)) ] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7681816 mistral_dashboard-20.0.0/mistraldashboard/workbooks/0000775000175000017500000000000000000000000022641 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/__init__.py0000664000175000017500000000000000000000000024740 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/forms.py0000664000175000017500000001037600000000000024350 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import messages from mistraldashboard import api class DefinitionForm(forms.SelfHandlingForm): definition_source = forms.ChoiceField( label=_('Definition Source'), choices=[('file', _('File')), ('raw', _('Direct Input'))], widget=forms.Select( attrs={'class': 'switchable', 'data-slug': 'definitionsource'}) ) definition_upload = forms.FileField( label=_('Definition File'), help_text=_('A local definition to upload.'), widget=forms.FileInput( attrs={'class': 'switched', 'data-switch-on': 'definitionsource', 'data-required-when-shown': 'true', 'data-definitionsource-file': _('Definition File')} ), required=False ) definition_data = forms.CharField( label=_('Definition Data'), help_text=_('The raw contents of the definition.'), widget=forms.widgets.Textarea( attrs={'class': 'switched', 'data-switch-on': 'definitionsource', 'data-required-when-shown': 'true', 'data-definitionsource-raw': _('Definition Data'), 'rows': 4} ), required=False ) def __init__(self, *args, **kwargs): self.next_view = kwargs.pop('next_view') super(DefinitionForm, self).__init__(*args, **kwargs) def clean(self): cleaned_data = super(DefinitionForm, self).clean() if cleaned_data.get('definition_upload'): files = self.request.FILES cleaned_data['definition'] = files['definition_upload'].read() elif cleaned_data.get('definition_data'): cleaned_data['definition'] = cleaned_data['definition_data'] else: raise forms.ValidationError( _('You must specify the definition source.')) try: validated = api.workbook_validate( self.request, cleaned_data['definition'] ) except Exception as e: raise forms.ValidationError(str(e)) if not validated.get('valid'): raise forms.ValidationError( validated.get('error', _('Validated failed'))) return cleaned_data def handle(self, request, data): kwargs = {'definition': data['definition']} request.method = 'GET' return self.next_view.as_view()(request, **kwargs) class CreateForm(forms.SelfHandlingForm): definition = forms.CharField( widget=forms.widgets.Textarea( attrs={'rows': 12} ), required=False ) def handle(self, request, data): try: api.workbook_create(request, data['definition']) msg = _('Successfully created workbook.') messages.success(request, msg) return True except Exception: msg = _('Failed to create workbook.') redirect = reverse('horizon:mistral:workbooks:index') exceptions.handle(request, msg, redirect=redirect) class UpdateForm(CreateForm): def handle(self, request, data): try: api.workbook_update(request, data['definition']) msg = _('Successfully updated workbook.') messages.success(request, msg) return True except Exception: msg = _('Failed to update workbook.') redirect = reverse('horizon:mistral:workbooks:index') exceptions.handle(request, msg, redirect=redirect) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/panel.py0000664000175000017500000000146600000000000024321 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.utils.translation import gettext_lazy as _ import horizon from mistraldashboard import dashboard class Workbooks(horizon.Panel): name = _("Workbooks") slug = 'workbooks' dashboard.MistralDashboard.register(Workbooks) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/tables.py0000664000175000017500000000527400000000000024475 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from horizon import tables from horizon.utils import filters from mistraldashboard import api class CreateWorkbook(tables.LinkAction): name = "create" verbose_name = _("Create Workbook") url = "horizon:mistral:workbooks:select_definition" classes = ("ajax-modal",) icon = "plus" class UpdateWorkbook(tables.LinkAction): name = "update" verbose_name = _("Update Workbook") url = "horizon:mistral:workbooks:change_definition" classes = ("ajax-modal",) icon = "pencil" class DeleteWorkbook(tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy( u"Delete Workbook", u"Delete Workbooks", count ) @staticmethod def action_past(count): return ngettext_lazy( u"Deleted Workbook", u"Deleted Workbooks", count ) def delete(self, request, workbook_name): api.workbook_delete(request, workbook_name) def tags_to_string(workbook): return ', '.join(workbook.tags) if workbook.tags else None class WorkbooksTable(tables.DataTable): name = tables.Column( "name", verbose_name=_("Name"), link="horizon:mistral:workbooks:detail" ) tags = tables.Column(tags_to_string, verbose_name=_("Tags")) created = tables.Column( "created_at", verbose_name=_("Created"), filters=( filters.parse_isotime, filters.timesince_or_never ) ) updated = tables.Column( "updated_at", verbose_name=_("Updated"), filters=( filters.parse_isotime, filters.timesince_or_never ) ) def get_object_id(self, datum): return datum.name class Meta(object): name = "workbooks" verbose_name = _("Workbooks") table_actions = ( CreateWorkbook, UpdateWorkbook, DeleteWorkbook, tables.FilterAction ) row_actions = (DeleteWorkbook,) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7401814 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/0000775000175000017500000000000000000000000024637 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7681816 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/workbooks/0000775000175000017500000000000000000000000026657 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/workbooks/_create.html0000664000175000017500000000033500000000000031150 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Create a new workbook with the provided definition." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/workbooks/_select_definition.html0000664000175000017500000000114400000000000033373 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block form_attrs %}enctype="multipart/form-data"{% endblock %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Use one of the available definition source options to specify the definition to be used in creating this workbook." %}

{% trans "Refer"%} Mistral Workflow Language {% trans " documentation for syntax details." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/workbooks/_update.html0000664000175000017500000000033000000000000031162 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Update workbooks with the provided definition." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/workbooks/create.html0000664000175000017500000000027100000000000031010 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create Workbook" %}{% endblock %} {% block main %} {% include 'mistral/workbooks/_create.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/workbooks/detail.html0000664000175000017500000000063400000000000031012 0ustar00zuulzuul00000000000000 {% extends 'mistral/default/base.html' %} {% load i18n %} {% block title %}{% trans "Workbook Definition" %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Workbook Definition") %} {% endblock page_header %} {% block main %}
{{ definition }}
{% endblock %}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/workbooks/index.html0000664000175000017500000000036300000000000030656 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block title %}{% trans "Workbooks" %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Workbooks") %} {% endblock page_header %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/workbooks/select_definition.html0000664000175000017500000000030600000000000033233 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Select Definition" %}{% endblock %} {% block main %} {% include 'mistral/workbooks/_select_definition.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/templates/workbooks/update.html0000664000175000017500000000027100000000000031027 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Update Workbook" %}{% endblock %} {% block main %} {% include 'mistral/workbooks/_update.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/tests.py0000664000175000017500000001303400000000000024356 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from openstack_dashboard.test import helpers from mistraldashboard import api from mistraldashboard.test import helpers as test INDEX_URL = reverse('horizon:mistral:workbooks:index') CREATE_URL = reverse('horizon:mistral:workbooks:create') UPDATE_URL = reverse('horizon:mistral:workbooks:update') class WorkflowsTest(test.TestCase): @helpers.create_mocks({api: ('workbook_list',)}) def test_index(self): self.mock_workbook_list.return_value =\ self.mistralclient_workbooks.list() res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'mistral/workbooks/index.html') self.assertCountEqual(res.context['table'].data, self.mistralclient_workbooks.list()) self.mock_workbook_list.\ assert_called_once_with(helpers.IsHttpRequest()) def test_create_get(self): res = self.client.get(CREATE_URL) self.assertTemplateUsed(res, 'mistral/workbooks/create.html') @helpers.create_mocks({api: ('workbook_validate', 'workbook_create')}) def test_create_post(self): self.mock_workbook_validate.return_value = {'valid': True} workbook = self.mistralclient_workbooks.first() url = reverse('horizon:mistral:workbooks:select_definition') res = self.client.get(url) self.assertTemplateUsed( res, 'mistral/workbooks/select_definition.html' ) form_data = { 'definition_source': 'raw', 'definition_data': workbook.definition } res = self.client.post(url, form_data) self.assertTemplateUsed(res, 'mistral/workbooks/create.html') self.mock_workbook_validate.assert_called_once_with( helpers.IsHttpRequest(), workbook.definition) form_data = { 'definition': workbook.definition } self.mock_workbook_create.return_value = workbook res = self.client.post(CREATE_URL, form_data) self.assertNoFormErrors(res) self.assertEqual(res.status_code, 302) self.assertRedirectsNoFollow(res, INDEX_URL) self.mock_workbook_create.assert_called_once_with( helpers.IsHttpRequest(), workbook.definition) def test_update_get(self): res = self.client.get(UPDATE_URL) self.assertTemplateUsed(res, 'mistral/workbooks/update.html') @helpers.create_mocks({api: ('workbook_validate', 'workbook_update')}) def test_update_post(self): workbook = self.mistralclient_workbooks.first() self.mock_workbook_validate.return_value = {'valid': True} url = reverse('horizon:mistral:workbooks:change_definition') res = self.client.get(url) self.assertTemplateUsed( res, 'mistral/workbooks/select_definition.html' ) form_data = { 'definition_source': 'raw', 'definition_data': workbook.definition } res = self.client.post(url, form_data) self.assertTemplateUsed(res, 'mistral/workbooks/update.html') self.mock_workbook_validate.assert_called_once_with( helpers.IsHttpRequest(), workbook.definition) form_data = { 'definition': workbook.definition } self.mock_workbook_update.return_value = workbook res = self.client.post(UPDATE_URL, form_data) self.assertNoFormErrors(res) self.assertEqual(res.status_code, 302) self.assertRedirectsNoFollow(res, INDEX_URL) self.mock_workbook_update.assert_called_once_with( helpers.IsHttpRequest(), workbook.definition) @helpers.create_mocks({api: ('workbook_list', 'workbook_delete')}) def test_delete_ok(self): workbooks = self.mistralclient_workbooks.list() self.mock_workbook_list.return_value = workbooks self.mock_workbook_delete.return_value = None data = {'action': 'workbooks__delete', 'object_ids': [workbooks[0].name]} res = self.client.post(INDEX_URL, data) self.mock_workbook_delete.assert_called_once_with( helpers.IsHttpRequest(), workbooks[0].name) self.mock_workbook_list.assert_called_once_with( helpers.IsHttpRequest()) self.assertNoFormErrors(res) self.assertRedirectsNoFollow(res, INDEX_URL) @helpers.create_mocks({api: ('workbook_get',)}) def test_detail(self): workbook = self.mistralclient_workbooks.list()[0] self.mock_workbook_get.return_value = workbook url = reverse('horizon:mistral:workbooks:detail', args=[workbook.name]) res = self.client.get(url) self.assertTemplateUsed(res, 'mistral/workbooks/detail.html') self.mock_workbook_get.assert_called_once_with( helpers.IsHttpRequest(), workbook.name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/urls.py0000664000175000017500000000234100000000000024200 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.urls import re_path from mistraldashboard.workbooks import views WORKBOOKS = r'^(?P[^/]+)/%s$' urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(r'^select_definition$', views.SelectDefinitionView.as_view(), name='select_definition'), re_path(r'^change_definition$', views.ChangeDefinitionView.as_view(), name='change_definition'), re_path(r'^create$', views.CreateView.as_view(), name='create'), re_path(r'^update$', views.UpdateView.as_view(), name='update'), re_path(WORKBOOKS % 'detail', views.DetailView.as_view(), name='detail'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workbooks/views.py0000664000175000017500000001010500000000000024345 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.urls import reverse from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views import generic from horizon import exceptions from horizon import forms from horizon import tables from mistraldashboard import api from mistraldashboard.workbooks import forms as mistral_forms from mistraldashboard.workbooks import tables as mistral_tables class IndexView(tables.DataTableView): table_class = mistral_tables.WorkbooksTable template_name = 'mistral/workbooks/index.html' def get_data(self): return api.workbook_list(self.request) class DetailView(generic.TemplateView): template_name = 'mistral/workbooks/detail.html' page_title = _("Workbook Definition") def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) workbook = self.get_data(self.request, **kwargs) breadcrumb = [(workbook.name, reverse( 'horizon:mistral:workbooks:detail', args=[workbook.name] ))] context["custom_breadcrumb"] = breadcrumb context['definition'] = workbook.definition return context def get_data(self, request, **kwargs): try: workbook_name = kwargs['workbook_name'] workbook = api.workbook_get(request, workbook_name) except Exception: msg = _('Unable to get workbook "%s".') % workbook_name redirect = reverse('horizon:mistral:workbooks:index') exceptions.handle(self.request, msg, redirect=redirect) return workbook class SelectDefinitionView(forms.ModalFormView): template_name = 'mistral/workbooks/select_definition.html' modal_header = _("Create Workbook") form_id = "select_definition" form_class = mistral_forms.DefinitionForm submit_label = _("Validate") submit_url = reverse_lazy("horizon:mistral:workbooks:select_definition") success_url = reverse_lazy('horizon:mistral:workbooks:create') page_title = _("Select Definition") def get_form_kwargs(self): kwargs = super(SelectDefinitionView, self).get_form_kwargs() kwargs['next_view'] = CreateView return kwargs class ChangeDefinitionView(SelectDefinitionView): modal_header = _("Update Workbook") submit_url = reverse_lazy("horizon:mistral:workbooks:change_definition") success_url = reverse_lazy('horizon:mistral:workbooks:update') page_title = _("Update Definition") def get_form_kwargs(self): kwargs = super(ChangeDefinitionView, self).get_form_kwargs() kwargs['next_view'] = UpdateView return kwargs class CreateView(forms.ModalFormView): template_name = 'mistral/workbooks/create.html' modal_header = _("Create Workbook") form_id = "create_workbook" form_class = mistral_forms.CreateForm submit_label = _("Create") submit_url = reverse_lazy("horizon:mistral:workbooks:create") success_url = reverse_lazy('horizon:mistral:workbooks:index') page_title = _("Create Workbook") def get_initial(self): initial = {} if 'definition' in self.kwargs: initial['definition'] = self.kwargs['definition'] return initial class UpdateView(CreateView): template_name = 'mistral/workbooks/update.html' modal_header = _("Update Workbook") form_id = "update_workbook" form_class = mistral_forms.UpdateForm submit_label = _("Update") submit_url = reverse_lazy("horizon:mistral:workbooks:update") page_title = _("Update Workbook") ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7721815 mistral_dashboard-20.0.0/mistraldashboard/workflows/0000775000175000017500000000000000000000000022656 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/__init__.py0000664000175000017500000000000000000000000024755 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/forms.py0000664000175000017500000001421200000000000024356 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 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 mistraldashboard import api class ExecuteForm(forms.SelfHandlingForm): workflow_name = forms.CharField( label=_("Workflow"), required=True, widget=forms.TextInput(attrs={'readonly': 'readonly'}) ) task_name = forms.CharField( label=_("Task name"), required=False, widget=forms.TextInput() ) def __init__(self, *args, **kwargs): super(ExecuteForm, self).__init__(*args, **kwargs) self._generate_parameter_fields(kwargs["initial"]["parameter_list"]) def _generate_parameter_fields(self, list): self.workflow_parameters = [] for entry in list.split(","): label, _, default = entry.partition("=") label = label.strip() if label != '': self.workflow_parameters.append(label) if default == "None": default = None required = False else: required = True self.fields[label] = forms.CharField(label=label, required=required, initial=default) def handle(self, request, data): try: data['wf_identifier'] = data.pop('workflow_name') data['workflow_input'] = {} for param in self.workflow_parameters: value = data.pop(param) if value == "": value = None data['workflow_input'][param] = value ex = api.execution_create(request, **data) msg = _('Execution has been created with id "%s".') % ex.id messages.success(request, msg) return True except Exception: msg = _('Failed to execute workflow.') redirect = reverse('horizon:mistral:workflows:index') exceptions.handle(request, msg, redirect=redirect) class DefinitionForm(forms.SelfHandlingForm): definition_source = forms.ChoiceField( label=_('Definition Source'), choices=[('file', _('File')), ('raw', _('Direct Input'))], widget=forms.Select( attrs={'class': 'switchable', 'data-slug': 'definitionsource'}) ) definition_upload = forms.FileField( label=_('Definition File'), help_text=_('A local definition to upload.'), widget=forms.FileInput( attrs={'class': 'switched', 'data-switch-on': 'definitionsource', 'data-required-when-shown': 'true', 'data-definitionsource-file': _('Definition File')} ), required=False ) definition_data = forms.CharField( label=_('Definition Data'), help_text=_('The raw contents of the definition.'), widget=forms.widgets.Textarea( attrs={'class': 'switched', 'data-switch-on': 'definitionsource', 'data-required-when-shown': 'true', 'data-definitionsource-raw': _('Definition Data'), 'rows': 4} ), required=False ) def __init__(self, *args, **kwargs): self.next_view = kwargs.pop('next_view') super(DefinitionForm, self).__init__(*args, **kwargs) def clean(self): cleaned_data = super(DefinitionForm, self).clean() if cleaned_data.get('definition_upload'): files = self.request.FILES cleaned_data['definition'] = files['definition_upload'].read() elif cleaned_data.get('definition_data'): cleaned_data['definition'] = cleaned_data['definition_data'] else: raise forms.ValidationError( _('You must specify the definition source.')) try: validated = api.workflow_validate( self.request, cleaned_data['definition'] ) except Exception as e: raise forms.ValidationError(str(e)) if not validated.get('valid'): raise forms.ValidationError( validated.get('error', _('Validated failed'))) return cleaned_data def handle(self, request, data): kwargs = {'definition': data['definition']} request.method = 'GET' return self.next_view.as_view()(request, **kwargs) class CreateForm(forms.SelfHandlingForm): definition = forms.CharField( widget=forms.widgets.Textarea( attrs={'rows': 12} ), required=False ) def handle(self, request, data): try: api.workflow_create(request, data['definition']) msg = _('Successfully created workflow.') messages.success(request, msg) return True except Exception: msg = _('Failed to create workflow.') redirect = reverse('horizon:mistral:workflows:index') exceptions.handle(request, msg, redirect=redirect) class UpdateForm(CreateForm): def handle(self, request, data): try: api.workflow_update(request, data['definition']) msg = _('Successfully updated workflow.') messages.success(request, msg) return True except Exception: msg = _('Failed to update workflow.') redirect = reverse('horizon:mistral:workflows:index') exceptions.handle(request, msg, redirect=redirect) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/panel.py0000664000175000017500000000146600000000000024336 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.utils.translation import gettext_lazy as _ import horizon from mistraldashboard import dashboard class Workflows(horizon.Panel): name = _("Workflows") slug = 'workflows' dashboard.MistralDashboard.register(Workflows) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/tables.py0000664000175000017500000000724600000000000024513 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.template.defaultfilters import title from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from horizon import tables from horizon.utils import filters from mistraldashboard import api class CreateWorkflow(tables.LinkAction): name = "create" verbose_name = _("Create Workflow") url = "horizon:mistral:workflows:select_definition" classes = ("ajax-modal",) icon = "plus" class UpdateWorkflow(tables.LinkAction): name = "update" verbose_name = _("Update Workflow") url = "horizon:mistral:workflows:change_definition" classes = ("ajax-modal",) icon = "pencil" class DeleteWorkflow(tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy( u"Delete Workflow", u"Delete Workflows", count ) @staticmethod def action_past(count): return ngettext_lazy( u"Deleted Workflow", u"Deleted Workflows", count ) def delete(self, request, workflow_name): api.workflow_delete(request, workflow_name) class ExecuteWorkflow(tables.LinkAction): name = "execute" verbose_name = _("Execute") url = "horizon:mistral:workflows:execute" classes = ("ajax-modal", "btn-edit") def tags_to_string(workflow): return ', '.join(workflow.tags) if workflow.tags else None def cut(workflow, length=50): inputs = workflow.input if inputs and len(inputs) > length: return "%s..." % inputs[:length] else: return inputs class WorkflowsTable(tables.DataTable): name = tables.Column( "name", verbose_name=_("Name"), link="horizon:mistral:workflows:detail" ) id = tables.Column( "id", verbose_name=_("ID"), ) scope = tables.Column( "scope", verbose_name=_("Scope"), filters=[title], ) definition = tables.Column( "", verbose_name=_("Definition"), empty_value=_("View"), link="horizon:mistral:workflows:definition", link_classes=("ajax-modal",) ) tags = tables.Column( tags_to_string, verbose_name=_("Tags") ) inputs = tables.Column( cut, verbose_name=_("Input"), link="horizon:mistral:workflows:input", link_classes=("ajax-modal",) ) created = tables.Column( "created_at", verbose_name=_("Created"), filters=( filters.parse_isotime, filters.timesince_or_never ) ) updated = tables.Column( "updated_at", verbose_name=_("Updated"), filters=( filters.parse_isotime, filters.timesince_or_never ) ) def get_object_id(self, datum): return datum.name class Meta(object): name = "workflows" verbose_name = _("Workflows") table_actions = ( CreateWorkflow, UpdateWorkflow, DeleteWorkflow, tables.FilterAction ) row_actions = (ExecuteWorkflow, DeleteWorkflow) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7401814 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/0000775000175000017500000000000000000000000024654 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7721815 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/0000775000175000017500000000000000000000000026711 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/_create.html0000664000175000017500000000033500000000000031202 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Create a new workflow with the provided definition." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/_execute.html0000664000175000017500000000104400000000000031377 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block form_id %}execute_form{% endblock %} {% block form_action %}{% url 'horizon:mistral:workflows:execute' workflow_name %}{% endblock %} {% block modal-header %}{% trans "Execute" %}{% endblock %} {% block modal-body %}
{% include "horizon/common/_form_fields.html" %}

{% trans "Description:" %}

{% trans "From here you can execute a workflow." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/_select_definition.html0000664000175000017500000000114400000000000033425 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block form_attrs %}enctype="multipart/form-data"{% endblock %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Use one of the available definition source options to specify the definition to be used in creating this workflow." %}

{% trans "Refer "%} Mistral Workflow Language {% trans " documentation for syntax details." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/_update.html0000664000175000017500000000033000000000000031214 0ustar00zuulzuul00000000000000{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Update workflows with the provided definition." %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/create.html0000664000175000017500000000027100000000000031042 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create Workflow" %}{% endblock %} {% block main %} {% include 'mistral/workflows/_create.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/detail.html0000664000175000017500000000063300000000000031043 0ustar00zuulzuul00000000000000 {% extends 'mistral/default/base.html' %} {% load i18n %} {% block title %}{% trans "Workflow Definition" %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Workflow Definition") %} {% endblock page_header %} {% block main %}
{{ definition }}
{% endblock %}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/execute.html0000664000175000017500000000052300000000000031241 0ustar00zuulzuul00000000000000{% extends 'mistral/default/base.html' %} {% load i18n %} {% block title %}{% trans "Execute workflow" %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Execute workflow") %} {% endblock page_header %} {% block main %} {% include 'mistral/workflows/_execute.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/index.html0000664000175000017500000000036300000000000030710 0ustar00zuulzuul00000000000000{% extends 'mistral/default/table.html' %} {% load i18n %} {% block title %}{% trans "Workflows" %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Workflows") %} {% endblock page_header %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/select_definition.html0000664000175000017500000000030600000000000033265 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Select Definition" %}{% endblock %} {% block main %} {% include 'mistral/workflows/_select_definition.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/templates/workflows/update.html0000664000175000017500000000027100000000000031061 0ustar00zuulzuul00000000000000{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Update Workflow" %}{% endblock %} {% block main %} {% include 'mistral/workflows/_update.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/tests.py0000664000175000017500000001314100000000000024372 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 reverse from openstack_dashboard.test import helpers from mistraldashboard import api from mistraldashboard.test import helpers as test INDEX_URL = reverse('horizon:mistral:workflows:index') CREATE_URL = reverse('horizon:mistral:workflows:create') UPDATE_URL = reverse('horizon:mistral:workflows:update') class WorkflowsTest(test.TestCase): @helpers.create_mocks({api: ('workflow_list',)}) def test_index(self): self.mock_workflow_list.return_value =\ self.mistralclient_workflows.list() res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'mistral/workflows/index.html') self.assertCountEqual(res.context['table'].data, self.mistralclient_workflows.list()) self.mock_workflow_list.assert_called_once_with( helpers.IsHttpRequest()) def test_create_get(self): res = self.client.get(CREATE_URL) self.assertTemplateUsed(res, 'mistral/workflows/create.html') @helpers.create_mocks({api: ('workflow_validate', 'workflow_create')}) def test_create_post(self): workflow = self.mistralclient_workflows.first() self.mock_workflow_validate.return_value = {'valid': True} self.mock_workflow_create.return_value = workflow url = reverse('horizon:mistral:workflows:select_definition') res = self.client.get(url) self.assertTemplateUsed( res, 'mistral/workflows/select_definition.html' ) form_data = { 'definition_source': 'raw', 'definition_data': workflow.definition } res = self.client.post(url, form_data) self.assertTemplateUsed(res, 'mistral/workflows/create.html') self.mock_workflow_validate.assert_called_once_with( helpers.IsHttpRequest(), workflow.definition ) form_data = { 'definition': workflow.definition } res = self.client.post(CREATE_URL, form_data) self.assertNoFormErrors(res) self.assertEqual(res.status_code, 302) self.assertRedirectsNoFollow(res, INDEX_URL) self.mock_workflow_create.assert_called_once_with( helpers.IsHttpRequest(), workflow.definition ) def test_update_get(self): res = self.client.get(UPDATE_URL) self.assertTemplateUsed(res, 'mistral/workflows/update.html') @helpers.create_mocks({api: ('workflow_validate', 'workflow_update')}) def test_update_post(self): workflow = self.mistralclient_workflows.first() self.mock_workflow_validate.return_value = {'valid': True} self.mock_workflow_update.return_value = workflow url = reverse('horizon:mistral:workflows:change_definition') res = self.client.get(url) self.assertTemplateUsed( res, 'mistral/workflows/select_definition.html' ) form_data = { 'definition_source': 'raw', 'definition_data': workflow.definition } res = self.client.post(url, form_data) self.assertTemplateUsed(res, 'mistral/workflows/update.html') self.mock_workflow_validate.assert_called_once_with( helpers.IsHttpRequest(), workflow.definition ) form_data = { 'definition': workflow.definition } res = self.client.post(UPDATE_URL, form_data) self.assertNoFormErrors(res) self.assertEqual(res.status_code, 302) self.assertRedirectsNoFollow(res, INDEX_URL) self.mock_workflow_update.assert_called_once_with( helpers.IsHttpRequest(), workflow.definition ) @helpers.create_mocks({api: ('workflow_list', 'workflow_delete')}) def test_delete_ok(self): workflows = self.mistralclient_workflows.list() self.mock_workflow_list.return_value = workflows self.mock_workflow_delete.return_value = None data = {'action': 'workflows__delete', 'object_ids': [workflows[0].name]} res = self.client.post(INDEX_URL, data) self.mock_workflow_delete.assert_called_once_with( helpers.IsHttpRequest(), workflows[0].name ) self.mock_workflow_list.assert_called_once_with( helpers.IsHttpRequest()) self.assertNoFormErrors(res) self.assertRedirectsNoFollow(res, INDEX_URL) @helpers.create_mocks({api: ('workflow_get',)}) def test_detail(self): workflow = self.mistralclient_workflows.list()[0] self.mock_workflow_get.return_value = workflow url = reverse('horizon:mistral:workflows:detail', args=[workflow.name]) res = self.client.get(url) self.assertTemplateUsed(res, 'mistral/workflows/detail.html') self.mock_workflow_get.assert_called_once_with( helpers.IsHttpRequest(), workflow.name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/urls.py0000664000175000017500000000304300000000000024215 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.urls import re_path from mistraldashboard.workflows import views WORKFLOWS = r'^(?P[^/]+)/%s$' urlpatterns = [ re_path(r'^$', views.IndexView.as_view(), name='index'), re_path(r'^select_definition$', views.SelectDefinitionView.as_view(), name='select_definition'), re_path(r'^change_definition$', views.ChangeDefinitionView.as_view(), name='change_definition'), re_path(r'^create$', views.CreateView.as_view(), name='create'), re_path(r'^update$', views.UpdateView.as_view(), name='update'), re_path(WORKFLOWS % 'execute', views.ExecuteView.as_view(), name='execute'), re_path(WORKFLOWS % 'detail', views.DetailView.as_view(), name='detail'), re_path(WORKFLOWS % 'definition', views.CodeView.as_view(), {'column': 'definition'}, name='definition'), re_path(WORKFLOWS % 'input', views.CodeView.as_view(), {'column': 'input'}, name='input'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/mistraldashboard/workflows/views.py0000664000175000017500000001345200000000000024372 0ustar00zuulzuul00000000000000# Copyright 2014 - StackStorm, 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 django.urls import reverse from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views import generic from horizon import exceptions from horizon import forms from horizon import tables from mistraldashboard import api from mistraldashboard.default import utils from mistraldashboard import forms as mistral_forms from mistraldashboard.workflows import forms as workflow_forms from mistraldashboard.workflows import tables as workflows_tables def get_single_data(request, workflow_name): try: workflow = api.workflow_get(request, workflow_name) except Exception: msg = _('Unable to get workflow "%s".') % workflow_name redirect = reverse('horizon:mistral:workflows:index') exceptions.handle(request, msg, redirect=redirect) return workflow class IndexView(tables.DataTableView): table_class = workflows_tables.WorkflowsTable template_name = 'mistral/workflows/index.html' def get_data(self): return api.workflow_list(self.request) class DetailView(generic.TemplateView): template_name = 'mistral/workflows/detail.html' page_title = _("Workflow Definition") def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) workflow = get_single_data(self.request, kwargs['workflow_name']) breadcrumb = [(workflow.name, reverse( 'horizon:mistral:workflows:detail', args=[workflow.name] ))] context["custom_breadcrumb"] = breadcrumb context['definition'] = ( workflow.definition or 'This workflow was created as part of workbook %s' % workflow.name.split('.')[0]) return context class CodeView(forms.ModalFormView): template_name = 'mistral/default/code.html' modal_header = _("Code view") form_id = "code_view" form_class = mistral_forms.EmptyForm cancel_label = "OK" cancel_url = reverse_lazy("horizon:mistral:workflows:index") page_title = _("Code view") def get_context_data(self, **kwargs): context = super(CodeView, self).get_context_data(**kwargs) workflow = get_single_data(self.request, self.kwargs['workflow_name']) io = {} column = self.kwargs['column'] if column == 'definition': io['name'] = _('Workflow Definition') io['value'] = utils.htmlpre(workflow.definition) elif column == 'input': io['name'] = _('Workflow Input') io['value'] = workflow.input context['io'] = io return context class ExecuteView(forms.ModalFormView): form_class = workflow_forms.ExecuteForm template_name = 'mistral/workflows/execute.html' success_url = reverse_lazy("horizon:mistral:executions:index") submit_label = _("Execute") cancel_url = reverse_lazy("horizon:mistral:workflows:index") def get_context_data(self, **kwargs): context = super(ExecuteView, self).get_context_data(**kwargs) context["workflow_name"] = self.kwargs['workflow_name'] return context def get_initial(self, **kwargs): workflow = get_single_data(self.request, self.kwargs['workflow_name']) return {'workflow_name': self.kwargs['workflow_name'], 'parameter_list': workflow.input} class SelectDefinitionView(forms.ModalFormView): template_name = 'mistral/workflows/select_definition.html' modal_header = _("Create Workflow") form_id = "select_definition" form_class = workflow_forms.DefinitionForm submit_label = _("Validate") submit_url = reverse_lazy("horizon:mistral:workflows:select_definition") success_url = reverse_lazy('horizon:mistral:workflows:create') page_title = _("Select Definition") def get_form_kwargs(self): kwargs = super(SelectDefinitionView, self).get_form_kwargs() kwargs['next_view'] = CreateView return kwargs class ChangeDefinitionView(SelectDefinitionView): modal_header = _("Update Workflow") submit_url = reverse_lazy("horizon:mistral:workflows:change_definition") success_url = reverse_lazy('horizon:mistral:workflows:update') page_title = _("Update Definition") def get_form_kwargs(self): kwargs = super(ChangeDefinitionView, self).get_form_kwargs() kwargs['next_view'] = UpdateView return kwargs class CreateView(forms.ModalFormView): template_name = 'mistral/workflows/create.html' modal_header = _("Create Workflow") form_id = "create_workflow" form_class = workflow_forms.CreateForm submit_label = _("Create") submit_url = reverse_lazy("horizon:mistral:workflows:create") success_url = reverse_lazy('horizon:mistral:workflows:index') page_title = _("Create Workflow") def get_initial(self): initial = {} if 'definition' in self.kwargs: initial['definition'] = self.kwargs['definition'] return initial class UpdateView(CreateView): template_name = 'mistral/workflows/update.html' modal_header = _("Update Workflow") form_id = "update_workflow" form_class = workflow_forms.UpdateForm submit_label = _("Update") submit_url = reverse_lazy("horizon:mistral:workflows:update") page_title = _("Update Workflow") ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7401814 mistral_dashboard-20.0.0/releasenotes/0000775000175000017500000000000000000000000017767 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7761815 mistral_dashboard-20.0.0/releasenotes/notes/0000775000175000017500000000000000000000000021117 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000023370 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/notes/bug-1931558-4674cdde721dfab8.yaml0000664000175000017500000000051000000000000025772 0ustar00zuulzuul00000000000000--- security: - | `Bug #1931558 `_: Previosuly Mistral Dashboard leaked contents of local files if a user put in a local file path in definitions. Now Mistral Dashboard no longer treats inputs as file path or URL but it always use the raw input as resource definitions. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/notes/drop-py-2-7-022d0dd59feb8b07.yaml0000664000175000017500000000034000000000000026166 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of mistral-dashboard to support python 2.7 is OpenStack Train. The minimum version of Python now supported by mistral-dashboard is Python 3.6. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/notes/drop-python-3-6-and-3-7-ae37bc21f97de767.yaml0000664000175000017500000000020100000000000030136 0ustar00zuulzuul00000000000000--- upgrade: - | Python 3.6 & 3.7 support has been dropped. The minimum version of Python now supported is Python 3.8. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7801814 mistral_dashboard-20.0.0/releasenotes/source/0000775000175000017500000000000000000000000021267 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/2023.1.rst0000664000175000017500000000021000000000000022537 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: unmaintained/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000022541 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000022541 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/2024.2.rst0000664000175000017500000000020200000000000022542 0ustar00zuulzuul00000000000000=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7801814 mistral_dashboard-20.0.0/releasenotes/source/_static/0000775000175000017500000000000000000000000022715 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000025166 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7801814 mistral_dashboard-20.0.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000023424 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000025675 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/conf.py0000664000175000017500000000432700000000000022574 0ustar00zuulzuul00000000000000# 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 = [ 'openstackdocstheme', 'reno.sphinxext', ] # 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 = 'Mistral Dashboard Release Notes' copyright = '2016, Mistral developers' # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/mistral-dashboard' openstackdocs_bug_project = 'mistral' openstackdocs_bug_tag = 'doc' # 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 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_theme = 'openstackdocs' # html_static_path = [] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/index.rst0000664000175000017500000000046500000000000023135 0ustar00zuulzuul00000000000000Mistral Dashboard Release Notes =============================== Contents ======== .. toctree:: :maxdepth: 2 unreleased 2024.2 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ocata newton mitaka liberty ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/liberty.rst0000664000175000017500000000021500000000000023471 0ustar00zuulzuul00000000000000============================ Liberty Series Release Notes ============================ .. release-notes:: :branch: origin/stable/liberty ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/mitaka.rst0000664000175000017500000000021100000000000023261 0ustar00zuulzuul00000000000000=========================== Mitaka Series Release Notes =========================== .. release-notes:: :branch: origin/stable/mitaka ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/newton.rst0000664000175000017500000000021100000000000023325 0ustar00zuulzuul00000000000000=========================== Newton Series Release Notes =========================== .. release-notes:: :branch: origin/stable/newton ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/ocata.rst0000664000175000017500000000023000000000000023103 0ustar00zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/pike.rst0000664000175000017500000000021700000000000022751 0ustar00zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/queens.rst0000664000175000017500000000022300000000000023316 0ustar00zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000023143 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/stein.rst0000664000175000017500000000022100000000000023136 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/train.rst0000664000175000017500000000017600000000000023142 0ustar00zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/unreleased.rst0000664000175000017500000000015300000000000024147 0ustar00zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000023345 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/victoria.rst0000664000175000017500000000022000000000000023633 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/wallaby.rst0000664000175000017500000000021400000000000023451 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/xena.rst0000664000175000017500000000020000000000000022744 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000022750 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/releasenotes/source/zed.rst0000664000175000017500000000017400000000000022605 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/requirements.txt0000664000175000017500000000054300000000000020564 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 iso8601>=0.1.11 # MIT python-mistralclient>=4.3.0 # Apache-2.0 PyYAML>=3.12 # MIT horizon>=17.1.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/run_tests.sh0000775000175000017500000003130500000000000017665 0ustar00zuulzuul00000000000000#!/bin/bash set -o errexit function usage { echo "Usage: $0 [OPTION]..." echo "Run Mistral Dashboard'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 " --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 " -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 " --runserver Run the Django development server for" echo " mistraldashboard 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="mistraldashboard" 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 never_venv=0 quiet=0 restore_env=0 runserver=0 only_selenium=0 with_selenium=0 selenium_headless=0 testopts="" testargs="" with_coverage=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;; -f|--force) force=1;; -t|--tabs) just_tabs=1;; -q|--quiet) quiet=1;; -c|--coverage) with_coverage=1;; -m|--manage) manage=1;; --pseudo) pseudo=1;; --only-selenium) only_selenium=1;; --with-selenium) with_selenium=1;; --selenium-headless) selenium_headless=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 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=mistraldashboard.test.settings ${command_wrapper} flake8 $included_dirs } function run_pep8_changed { 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=mistraldashboard.test.settings ${command_wrapper} flake8 --diff exit } function run_sphinx { echo "Building sphinx..." export DJANGO_SETTINGS_MODULE=mistraldashboard.test.settings ${command_wrapper} sphinx-build -b html doc/source doc/build/html 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/.mistral_dashboard_environment/$JOB_NAME ]; then mv /tmp/.mistral_dashboard_environment/$JOB_NAME /tmp/.mistral_dashboard_environment/$JOB_NAME.old rm -rf /tmp/.mistral_dashboard_environment/$JOB_NAME fi mkdir -p /tmp/.mistral_dashboard_environment/$JOB_NAME cp -r $venv /tmp/.mistral_dashboard_environment/$JOB_NAME/ cp .environment_version /tmp/.mistral_dashboard_environment/$JOB_NAME/ # Remove the backup now that we've completed successfully rm -rf /tmp/.mistral_dashboard_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/.mistral_dashboard_environment/$JOB_NAME ]; then echo "No backup to restore from." return 0 fi cp -r /tmp/.mistral_dashboard_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 [ $selenium_headless -eq 1 ]; then export SELENIUM_HEADLESS=1 fi if [ -z "$testargs" ]; then run_tests_all else run_tests_subset fi } function run_tests_subset { project=`echo $testargs | awk -F. '{print $1}'` ${command_wrapper} python $root/manage.py test --settings=$project.test.settings $testopts $testargs } function run_tests_all { echo "Running Mistral-Dashboard application tests" export NOSE_XUNIT_FILE=mistraldashboard/nosetests.xml if [ "$NOSE_WITH_HTML_OUTPUT" = '1' ]; then export NOSE_HTML_OUT_FILE='mistral_dashboard_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 mistraldashboard --settings=mistraldashboard.test.settings $testopts # get results of the Horizon tests TUSKAR_UI_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="mistraldashboard/*" --omit='/usr*,setup.py,*egg*,.venv/*' ${command_wrapper} python -m coverage.__main__ html -i --include="mistraldashboard/*" --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 [ $only_selenium -eq 0 ]; then run_pep8 PEP8_RESULT=$? fi TEST_RESULT=$(($TUSKAR_UI_RESULT || $PEP8_RESULT)) if [ $TEST_RESULT -eq 0 ]; then echo "Tests completed successfully." else echo "Tests failed." fi exit $TEST_RESULT } # ---------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 # 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 # Tab checker if [ $just_tabs -eq 1 ]; then tab_check exit $? fi # Django development server if [ $runserver -eq 1 ]; then run_server exit $? fi # Full test suite run_tests || exit ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1743591352.7841814 mistral_dashboard-20.0.0/setup.cfg0000664000175000017500000000160200000000000017116 0ustar00zuulzuul00000000000000[metadata] name = mistral-dashboard summary = Mistral dashboard description_file = README.rst license = Apache License, Version 2.0 python_requires = >=3.8 classifiers = Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/mistral/latest/ [global] setup_hooks = pbr.hooks.setup_hook [files] packages = mistraldashboard [upload_sphinx] upload_dir = doc/build/html [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/setup.py0000664000175000017500000000200600000000000017006 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/test-requirements.txt0000664000175000017500000000040000000000000021531 0ustar00zuulzuul00000000000000hacking>=6.1.0,<6.2.0 # Apache-2.0 # Testing Requirements coverage!=4.4,>=4.0 # Apache-2.0 nodeenv>=0.9.4 # BSD selenium>=2.50.1 # Apache-2.0 xvfbwrapper>=0.1.3 #license: MIT stestr>=2.0.0 # Apache-2.0 # Horizon requirements django-compressor>=2.0 # MIT ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1743591311.0 mistral_dashboard-20.0.0/tox.ini0000664000175000017500000000304700000000000016615 0ustar00zuulzuul00000000000000[tox] minversion = 3.18.0 envlist = pep8,py3 ignore_basepython_conflict = true [testenv] basepython = python3 usedevelop = True setenv = VIRTUAL_ENV={envdir} deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete /bin/bash run_tests.sh -N --no-pep8 {posargs} allowlist_externals = find /bin/bash [testenv:pep8] skip_install = True commands = flake8 [testenv:venv] commands = {posargs} [testenv:cover] setenv = PYTHON=coverage run --source mistraldashboard --parallel-mode commands = stestr run '{posargs}' coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:debug] allowlist_externals = oslo_debug_helper commands = oslo_debug_helper -t mistraldashboard/test {posargs} [flake8] show-source = True exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,.ropeproject,tools,horizon [testenv:releasenotes] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html