././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0843532 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/0000755000000000000000000000000015243355747030520 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7008193 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/.stestr.conf0000644000000000000000000000007115243355431032755 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com[DEFAULT] test_path=./mistraldashboard/tests top_dir=./ ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7008193 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/.zuul.yaml0000644000000000000000000000031215243355431032443 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com- project: templates: - check-requirements - horizon-non-primary-django-jobs - openstack-python3-jobs-horizon - publish-openstack-docs-pti - release-notes-jobs-python3 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0425441 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/AUTHORS0000644000000000000000000000547215243355747031600 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.comAdriano 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 Anfimov 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 Stephen Finucane 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 shuwcai songwenping sunjia venkatamahesh wu.shiming zhangguoqing zhouyunfeng ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7008193 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/CONTRIBUTING.rst0000644000000000000000000000117115243355431033147 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.comThe 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0238345 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/ChangeLog0000644000000000000000000003463715243355747032307 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.comCHANGES ======= * Add common note about lower constraints of requirements * Remove note about old pip's behavior * Add pagination for objects * Add missing python-dateutil dependency * Fix for issue with translations * Add translation settings * Update packaging configuration * Drop Python 3.10 * Update master for stable/2026.1 22.0.0 ------ * reno: Update master for unmaintained/2024.1 * Update master for stable/2025.2 * Migrate setup configuration to pyproject.toml * Drop leftover of old documentation build * Fix mistraldashboard not pass ca\_cert when enable CA cert in horizon 21.0.0 ------ * Replace license classifier * Remove unicode prefix from code * Drop support for Python 3.9 * fix broken url in README * Remove tags from README * Remove iso8601 and update requirements * tox: Remove ineffective ignore\_basepython\_conflict and bump minimum version * Remove Python 3.8 support * Cleanup setup.py * Update master for stable/2025.1 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7008193 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/LICENSE0000644000000000000000000002363615243355431031525 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com 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. ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0843365 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/PKG-INFO0000644000000000000000000000662315243355747031624 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.comMetadata-Version: 2.4 Name: mistral-dashboard Version: 22.0.0+git20260722.13.a2c6b1c Summary: Mistral UI for Horizon Author-email: OpenStack License-Expression: Apache-2.0 Project-URL: Homepage, https://docs.openstack.org/mistral/latest/ Project-URL: Repository, https://opendev.org/openstack/mistral-dashboard Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.11 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: pbr>=6.1.1 Requires-Dist: python-dateutil>=2.9.0 Requires-Dist: python-mistralclient>=4.3.0 Requires-Dist: PyYAML>=3.12 Requires-Dist: horizon>=23.1.1 Dynamic: license-file Dynamic: requires-dist ================= Mistral Dashboard ================= .. image:: https://governance.openstack.org/tc/badges/mistral-dashboard.svg .. Change things from this point on 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 Debugging and Testing `_ ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7008193 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/README.rst0000644000000000000000000000424315243355431032200 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com================= Mistral Dashboard ================= .. image:: https://governance.openstack.org/tc/badges/mistral-dashboard.svg .. Change things from this point on 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 Debugging and Testing `_ ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7008193 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/babel-django.cfg0000644000000000000000000000016715243355431033500 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com[python: **.py] [django: **/templates/**.html] [extractors] django = horizon.utils.babel_extract_django:extract_django ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7008193 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/babel-djangojs.cfg0000644000000000000000000000017415243355431034033 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com[javascript: **.js] [angular: **/static/**.html] [extractors] angular = horizon.utils.babel_extract_angular:extract_angular ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0763507 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/doc/0000755000000000000000000000000015243355747031265 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7008193 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/doc/requirements.txt0000644000000000000000000000022515243355431034536 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.comopenstackdocstheme>=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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0765347 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/doc/source/0000755000000000000000000000000015243355747032565 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701469 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/doc/source/conf.py0000644000000000000000000000426115243355431034055 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7014997 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/doc/source/contributing.rst0000644000000000000000000000011315243355431036007 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com============ Contributing ============ .. include:: ../../CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7014997 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/doc/source/index.rst0000644000000000000000000000037515243355431034421 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com Welcome to Mistral Dashboard's documentation! ============================================= Contents: .. toctree:: :maxdepth: 1 readme contributing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7014997 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/doc/source/readme.rst0000644000000000000000000000003515243355431034540 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com.. include:: ../../README.rst././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7014997 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/manage.py0000755000000000000000000000151015243355431032310 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com#!/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) ././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681767.076857 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistral_dashboard.egg-info/0000755000000000000000000000000015243355747035674 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0448349 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistral_dashboard.egg-info/PKG-INFO0000644000000000000000000000662315243355747037000 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.comMetadata-Version: 2.4 Name: mistral-dashboard Version: 22.0.0+git20260722.13.a2c6b1c Summary: Mistral UI for Horizon Author-email: OpenStack License-Expression: Apache-2.0 Project-URL: Homepage, https://docs.openstack.org/mistral/latest/ Project-URL: Repository, https://opendev.org/openstack/mistral-dashboard Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.11 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: pbr>=6.1.1 Requires-Dist: python-dateutil>=2.9.0 Requires-Dist: python-mistralclient>=4.3.0 Requires-Dist: PyYAML>=3.12 Requires-Dist: horizon>=23.1.1 Dynamic: license-file Dynamic: requires-dist ================= Mistral Dashboard ================= .. image:: https://governance.openstack.org/tc/badges/mistral-dashboard.svg .. Change things from this point on 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 Debugging and Testing `_ ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0706713 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistral_dashboard.egg-info/SOURCES.txt0000644000000000000000000001635715243355747037574 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com.stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog LICENSE README.rst babel-django.cfg babel-djangojs.cfg manage.py pyproject.toml requirements.txt run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini 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/locale/.placeholder 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/notes/drop-python-38-and-39.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/2024.2.rst releasenotes/source/2025.1.rst releasenotes/source/2025.2.rst releasenotes/source/2026.1.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././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0452104 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistral_dashboard.egg-info/dependency_links.txt0000644000000000000000000000000115243355747041742 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0451522 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistral_dashboard.egg-info/not-zip-safe0000644000000000000000000000000115243355747040122 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com ././@PaxHeader0000000000000000000000000000007500000000000010217 xustar0018 uid=1360376536 18 gid=1360376536 25 mtime=1787681767.0548 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistral_dashboard.egg-info/pbr.json0000644000000000000000000000005715243355747037354 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{"git_version": "a2c6b1c", "is_release": false}././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0453327 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistral_dashboard.egg-info/requires.txt0000644000000000000000000000013315243355747040271 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.compbr>=6.1.1 python-dateutil>=2.9.0 python-mistralclient>=4.3.0 PyYAML>=3.12 horizon>=23.1.1 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0453672 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistral_dashboard.egg-info/top_level.txt0000644000000000000000000000002115243355747040417 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.commistraldashboard ././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681767.077173 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/0000755000000000000000000000000015243355747034043 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7014997 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/__init__.py0000644000000000000000000000000015243355431036130 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0774832 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/0000755000000000000000000000000015243355747037566 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701577 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/__init__.py0000644000000000000000000000000015243355431041653 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7015889 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/forms.py0000644000000000000000000000673615243355431041270 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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'], ) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7015889 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/panel.py0000644000000000000000000000151115243355431041223 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7015889 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/tables.py0000644000000000000000000001030215243355431041374 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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( "Delete Action Execution", "Delete Action Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( "Deleted Action Execution", "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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0738356 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/0000755000000000000000000000000015243355747041564 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000027100000000000010215 xustar00121 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_executions/ 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0777137 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_0000755000000000000000000000000015243355747043121 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000030500000000000010213 xustar00133 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_executions/_update.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7016995 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_0000644000000000000000000000102215243355431043104 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000030300000000000010211 xustar00132 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_executions/detail.html 18 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701728 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_0000644000000000000000000000706315243355431043117 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com {% 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 %}
././@PaxHeader0000000000000000000000000000030500000000000010213 xustar00134 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_executions/filtered.html 18 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701728 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_0000644000000000000000000000052215243355431043110 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000030200000000000010210 xustar00131 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_executions/index.html 18 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701728 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_0000644000000000000000000000041015243355431043104 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000030300000000000010211 xustar00132 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_executions/update.html 18 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701728 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/templates/action_0000644000000000000000000000040515243355431043110 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %}././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701728 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/tests.py0000644000000000000000000000560115243355431041272 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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: ('pagination_list',)}) def test_index(self): self.mock_pagination_list.return_value = ( self.mistralclient_action_executions.list(), False, False, ) res = self.client.get(INDEX_URL) self.assertTemplateUsed( res, 'mistral/action_executions/index.html') self.mock_pagination_list.assert_called_once_with( entity="action_executions", request=helpers.IsHttpRequest(), marker=None, sort_dirs="desc", paginate=True, ) @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) ././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701728 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/urls.py0000644000000000000000000000250615243355431041116 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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') ] ././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701728 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/action_executions/views.py0000644000000000000000000001535615243355431041275 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 exceptions 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 has_prev_data(self, table): return self._prev def has_more_data(self, table): return self._more def get_data(self): action_executions = [] prev_marker = self.request.GET.get( mistral_tables.ActionExecutionsTable._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.ActionExecutionsTable._meta.pagination_param, None ) try: action_executions, self._more, self._prev = api.pagination_list( entity="action_executions", request=self.request, marker=marker, sort_dirs=sort_dir, paginate=True ) if prev_marker is not None: action_executions = sorted( action_executions, key=lambda execution: getattr( execution, 'created_at' ), reverse=True ) except Exception as e: self._prev = False self._more = False msg = _('Unable to retrieve action executions list: %s') % e exceptions.handle(self.request, msg) return action_executions 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0780334 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/0000755000000000000000000000000015243355747035503 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.701728 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/__init__.py0000644000000000000000000000000015243355431037570 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7021577 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/forms.py0000644000000000000000000001467615243355431037207 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7021577 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/panel.py0000644000000000000000000000147115243355431037145 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7021577 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/tables.py0000644000000000000000000000700115243355431037313 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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( "Delete Action", "Delete Actions", count ) @staticmethod def action_past(count): return ngettext_lazy( "Deleted Action", "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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0738356 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/0000755000000000000000000000000015243355747037501 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0784357 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/0000755000000000000000000000000015243355747041141 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000026100000000000010214 xustar00113 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/_create.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7023957 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/_create.h0000644000000000000000000000114015243355431042676 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/_run.html0000644000000000000000000000031715243355431042761 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

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

{% endblock %} ././@PaxHeader0000000000000000000000000000026100000000000010214 xustar00113 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/_update.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/_update.h0000644000000000000000000000114015243355431042715 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000026000000000000010213 xustar00112 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/create.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/create.ht0000644000000000000000000000026515243355431042732 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create Action" %}{% endblock %} {% block main %} {% include 'mistral/actions/_create.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000026000000000000010213 xustar00112 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/detail.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/detail.ht0000644000000000000000000000251715243355431042733 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com {% 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 %}
././@PaxHeader0000000000000000000000000000025700000000000010221 xustar00111 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/index.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/index.htm0000644000000000000000000000035615243355431042754 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %}././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/run.html0000644000000000000000000000037615243355431042627 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000026000000000000010213 xustar00112 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/update.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/templates/actions/update.ht0000644000000000000000000000026515243355431042751 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Update Action" %}{% endblock %} {% block main %} {% include 'mistral/actions/_update.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/tests.py0000644000000000000000000000604215243355431037207 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/urls.py0000644000000000000000000000205515243355431037032 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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'), ] ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/actions/views.py0000644000000000000000000001202315243355431037176 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/api.py0000644000000000000000000003005315243355431035155 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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, cacert=getattr(settings, 'OPENSTACK_SSL_CACERT') ) @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 ) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0788145 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/0000755000000000000000000000000015243355747036712 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7024252 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/__init__.py0000644000000000000000000000000015243355431040777 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7026534 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/forms.py0000644000000000000000000001606115243355431040404 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7026534 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/panel.py0000644000000000000000000000147115243355431040354 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7026534 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/tables.py0000644000000000000000000000616715243355431040536 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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( "Delete Cron Trigger", "Delete Cron Triggers", count ) @staticmethod def action_past(count): return ngettext_lazy( "Deleted Cron Trigger", "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,) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0738356 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/0000755000000000000000000000000015243355747040710 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000026100000000000010214 xustar00113 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_triggers/ 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0790155 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_trigge0000755000000000000000000000000015243355747043133 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000027500000000000010221 xustar00125 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_triggers/_create.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7027414 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_trigge0000644000000000000000000000271215243355431043125 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000027400000000000010220 xustar00124 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_triggers/create.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7027588 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_trigge0000644000000000000000000000022415243355431043121 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% load static %} {% block main %} {% include 'mistral/cron_triggers/_create.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000027400000000000010220 xustar00124 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_triggers/detail.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7027588 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_trigge0000644000000000000000000000650415243355431043130 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com {% 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 %}
././@PaxHeader0000000000000000000000000000027300000000000010217 xustar00123 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_triggers/index.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7027588 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/templates/cron_trigge0000644000000000000000000000040015243355431043115 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7027588 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/tests.py0000644000000000000000000000635515243355431040425 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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: ('pagination_list',)}) def test_index(self): self.mock_pagination_list.return_value = ( self.mistralclient_cron_triggers.list(), False, False, ) res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'mistral/cron_triggers/index.html') self.mock_pagination_list.assert_called_once_with( entity="cron_triggers", request=helpers.IsHttpRequest(), marker=None, sort_dirs="desc", paginate=True, ) @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"], {'a': 'b'}, {'a': '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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7027588 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/urls.py0000644000000000000000000000166615243355431040250 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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'), ] ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7027588 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/cron_triggers/views.py0000644000000000000000000000776715243355431040430 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 exceptions 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 has_prev_data(self, table): return self._prev def has_more_data(self, table): return self._more def get_data(self): cron_triggers = [] prev_marker = self.request.GET.get( mistral_tables.CronTriggersTable._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.CronTriggersTable._meta.pagination_param, None ) try: cron_triggers, self._more, self._prev = api.pagination_list( entity="cron_triggers", request=self.request, marker=marker, sort_dirs=sort_dir, paginate=True ) if prev_marker is not None: cron_triggers = sorted( cron_triggers, key=lambda execution: getattr( execution, 'created_at' ), reverse=True ) except Exception as e: self._prev = False self._more = False msg = _('Unable to retrieve cron triggers list: %s') % e exceptions.handle(self.request, msg) return cron_triggers ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7027588 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/dashboard.py0000644000000000000000000000212615243355431036333 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0791898 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/0000755000000000000000000000000015243355747035467 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7027588 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/__init__.py0000644000000000000000000000000015243355431037554 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029176 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/panel.py0000644000000000000000000000142415243355431037127 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029176 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/smart_cell.py0000644000000000000000000000601215243355431040153 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0738356 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/0000755000000000000000000000000015243355747037465 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0795455 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/0000755000000000000000000000000015243355747041111 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000026600000000000010221 xustar00119 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_booleanfield.html 18 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.702975 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_booleanf0000644000000000000000000000017015243355431042744 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com {{ bool }} ././@PaxHeader0000000000000000000000000000025700000000000010221 xustar00111 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_code.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_code.htm0000644000000000000000000000060515243355431042663 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends "horizon/common/_modal.html" %} {% block modal-header %} {{ io.name }} {% endblock %} {% block modal-body %}
{{ io.value }}
{% endblock %} {% block modal-footer %} {{ cancel_label }} {% endblock %} ././@PaxHeader0000000000000000000000000000026400000000000010217 xustar00116 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_humantime.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_humantim0000644000000000000000000000013715243355431043004 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% load humanize %} ././@PaxHeader0000000000000000000000000000026000000000000010213 xustar00112 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_label.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_label.ht0000644000000000000000000000006215243355431042650 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{{ label }} ././@PaxHeader0000000000000000000000000000026300000000000010216 xustar00115 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_preprint.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_preprint0000644000000000000000000000003315243355431043020 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com
    {{ pre }}
././@PaxHeader0000000000000000000000000000026600000000000010221 xustar00118 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_prettyprint.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/_prettypr0000644000000000000000000000013715243355431043053 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com
{{ full }}
././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/base.html0000644000000000000000000000033715243355431042702 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% block css %} {% include "_stylesheets.html" %} {% endblock %} ././@PaxHeader0000000000000000000000000000025700000000000010221 xustar00111 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/table.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/templates/default/table.htm0000644000000000000000000000021715243355431042700 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'mistral/default/base.html' %} {% block main %}
{{ table.render }}
{% endblock %} ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/default/utils.py0000644000000000000000000000452115243355431037171 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 dateutil import parser from django.template.loader import render_to_string 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": parser.parse(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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0796347 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/enabled/0000755000000000000000000000000015243355747035435 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/enabled/_50_mistral.py0000644000000000000000000000153015243355431040112 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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, } ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7029963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/enabled/__init__.py0000644000000000000000000000000015243355431037522 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7032342 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/exceptions.py0000644000000000000000000000150115243355431036561 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0799568 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/0000755000000000000000000000000015243355747036231 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7032342 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/__init__.py0000644000000000000000000000000015243355431040316 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7032678 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/forms.py0000644000000000000000000000320315243355431037715 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from django.urls import 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7032678 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/panel.py0000644000000000000000000000150215243355431037666 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7032678 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/tables.py0000644000000000000000000001405615243355431040051 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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( "Delete Execution", "Delete Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( "Deleted Execution", "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( "Cancel Execution", "Cancel Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( "Canceled Execution", "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( "Pause Execution", "Pause Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( "Paused Execution", "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( "Resume Execution", "Resume Executions", count ) @staticmethod def action_past(count): return ngettext_lazy( "Resumed Execution", "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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0738356 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/0000755000000000000000000000000015243355747040227 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681767.080211 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/0000755000000000000000000000000015243355747042415 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000030300000000000010211 xustar00131 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/_update_description.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033198 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/_up0000644000000000000000000000031215243355431043105 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Enter execution description." %}

{% endblock %}././@PaxHeader0000000000000000000000000000026600000000000010221 xustar00118 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/detail.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/det0000644000000000000000000000464115243355431043107 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com {% 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 %}
././@PaxHeader0000000000000000000000000000026500000000000010220 xustar00117 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/index.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/ind0000644000000000000000000000040715243355431043101 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000030300000000000010211 xustar00131 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/index_filtered_task.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/ind0000644000000000000000000000040615243355431043100 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000030200000000000010210 xustar00130 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/update_description.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/templates/executions/upd0000644000000000000000000000042115243355431043113 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %}././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/tests.py0000644000000000000000000000556215243355431037743 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/urls.py0000644000000000000000000000266715243355431037571 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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'), ] ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/executions/views.py0000644000000000000000000002202315243355431037725 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/forms.py0000644000000000000000000000122615243355431035532 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from horizon import forms class EmptyForm(forms.SelfHandlingForm): def handle(self, request, data): pass ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/handle_errors.py0000644000000000000000000000564015243355431037237 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0802703 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/locale/0000755000000000000000000000000015243355747035302 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7033412 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/locale/.placeholder0000644000000000000000000000000115243355431037542 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0738356 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/static/0000755000000000000000000000000015243355747035332 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0738356 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/static/mistraldashboard/0000755000000000000000000000000015243355747040655 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0803227 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/static/mistraldashboard/css/0000755000000000000000000000000015243355747041445 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000026000000000000010213 xustar00112 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/static/mistraldashboard/css/style.css 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7035184 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/static/mistraldashboard/css/style.c0000644000000000000000000000120215243355431042732 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com.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; } ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0805933 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/0000755000000000000000000000000015243355747035170 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7035184 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/__init__.py0000644000000000000000000000000015243355431037255 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7035635 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/panel.py0000644000000000000000000000145715243355431036636 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7035635 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/tables.py0000644000000000000000000000702615243355431037007 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0738356 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/templates/0000755000000000000000000000000015243355747037166 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0807333 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/templates/tasks/0000755000000000000000000000000015243355747040313 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7035985 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/templates/tasks/detail.html0000644000000000000000000000510715243355431042434 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com {% 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 %}
././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7036414 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/templates/tasks/filtered.html0000644000000000000000000000051515243355431042766 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7036414 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/templates/tasks/index.html0000644000000000000000000000040515243355431042275 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7036414 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/tests.py0000644000000000000000000000346715243355431036704 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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: ('pagination_list',)}) def test_index(self): self.mock_pagination_list.return_value = ( self.mistralclient_tasks.list(), False, False, ) 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_pagination_list.assert_called_once() @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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7036414 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/urls.py0000644000000000000000000000230715243355431036517 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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'), ] ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7036414 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/tasks/views.py0000644000000000000000000001330615243355431036670 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 has_prev_data(self, table): return self._prev def has_more_data(self, table): return self._more def get_data(self): tasks = [] prev_marker = self.request.GET.get( mistral_tables.TaskTable._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.TaskTable._meta.pagination_param, None ) try: tasks, self._more, self._prev = api.pagination_list( entity="tasks", request=self.request, marker=marker, sort_dirs=sort_dir, paginate=True ) if prev_marker is not None: tasks = sorted( tasks, key=lambda execution: getattr( execution, 'created_at' ), reverse=True ) except Exception as e: self._prev = False self._more = False msg = _('Unable to retrieve tasks list: %s') % e exceptions.handle(self.request, msg) return tasks ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0809047 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/test/0000755000000000000000000000000015243355747035022 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7036414 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/test/__init__.py0000644000000000000000000000000015243355431037107 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7037282 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/test/helpers.py0000644000000000000000000000176415243355431037034 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7037282 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/test/settings.py0000644000000000000000000000154715243355431037231 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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') ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0810454 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/test/test_data/0000755000000000000000000000000015243355747036772 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7037282 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/test/test_data/__init__.py0000644000000000000000000000000015243355431041057 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.703798 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/test/test_data/mistral_data.py0000644000000000000000000001142315243355431041777 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.703798 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/test/test_data/utils.py0000644000000000000000000000216015243355431040471 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.703798 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/test/urls.py0000644000000000000000000000131115243355431036343 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from django.conf import urls import openstack_dashboard.urls urlpatterns = [ urls.url(r'', urls.include(openstack_dashboard.urls)) ] ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0813956 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/0000755000000000000000000000000015243355747036063 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681560.703798 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/__init__.py0000644000000000000000000000000015243355431040150 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7038743 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/forms.py0000644000000000000000000001037615243355431037560 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7038743 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/panel.py0000644000000000000000000000146615243355431037531 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7038743 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/tables.py0000644000000000000000000000527015243355431037701 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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( "Delete Workbook", "Delete Workbooks", count ) @staticmethod def action_past(count): return ngettext_lazy( "Deleted Workbook", "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,) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0748355 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/0000755000000000000000000000000015243355747040061 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000007600000000000010220 xustar0018 uid=1360376536 18 gid=1360376536 26 mtime=1787681767.08176 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/0000755000000000000000000000000015243355747042101 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000026500000000000010220 xustar00117 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/_create.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039256 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/_crea0000644000000000000000000000033515243355431043064 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

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

{% endblock %} ././@PaxHeader0000000000000000000000000000030000000000000010206 xustar00128 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/_select_definition.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/_sele0000644000000000000000000000114415243355431043101 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000026500000000000010220 xustar00117 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/_update.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/_upda0000644000000000000000000000033015243355431043076 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

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

{% endblock %} ././@PaxHeader0000000000000000000000000000026400000000000010217 xustar00116 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/create.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/creat0000644000000000000000000000027115243355431043110 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create Workbook" %}{% endblock %} {% block main %} {% include 'mistral/workbooks/_create.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000026400000000000010217 xustar00116 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/detail.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/detai0000644000000000000000000000063415243355431043103 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com {% 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 %}
././@PaxHeader0000000000000000000000000000026300000000000010216 xustar00115 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/index.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/index0000644000000000000000000000036315243355431043123 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000027700000000000010223 xustar00127 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/select_definition.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/selec0000644000000000000000000000030615243355431043104 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Select Definition" %}{% endblock %} {% block main %} {% include 'mistral/workbooks/_select_definition.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000026400000000000010217 xustar00116 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/update.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/templates/workbooks/updat0000644000000000000000000000027115243355431043127 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Update Workbook" %}{% endblock %} {% block main %} {% include 'mistral/workbooks/_update.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/tests.py0000644000000000000000000001310715243355431037567 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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: ('pagination_list',)}) def test_index(self): self.mock_pagination_list.return_value = ( self.mistralclient_workbooks.list(), False, False, ) 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_pagination_list.assert_called_once() 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: ('pagination_list', 'workbook_delete')}) def test_delete_ok(self): workbooks = self.mistralclient_workbooks.list() self.mock_pagination_list.return_value = ( workbooks, False, False, ) 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_pagination_list.assert_called_once() 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/urls.py0000644000000000000000000000234115243355431037410 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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'), ] ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workbooks/views.py0000644000000000000000000001265415243355431037570 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 has_prev_data(self, table): return self._prev def has_more_data(self, table): return self._more def get_data(self): workbooks = [] prev_marker = self.request.GET.get( mistral_tables.WorkbooksTable._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.WorkbooksTable._meta.pagination_param, None ) try: workbooks, self._more, self._prev = api.pagination_list( entity="workbooks", request=self.request, marker=marker, sort_keys='name', sort_dirs=sort_dir, paginate=True, reversed_order=True ) if prev_marker is not None: workbooks = sorted( workbooks, key=lambda action: getattr( action, 'name' ), reverse=False ) except Exception as e: self._prev = False self._more = False msg = _('Unable to retrieve workbooks list: %s') % str(e) exceptions.handle(self.request, msg) return workbooks 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") ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0820975 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/0000755000000000000000000000000015243355747036100 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7039511 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/__init__.py0000644000000000000000000000000015243355431040165 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7040915 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/forms.py0000644000000000000000000001421215243355431037566 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7040915 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/panel.py0000644000000000000000000000146615243355431037546 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7040915 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/tables.py0000644000000000000000000000724215243355431037717 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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( "Delete Workflow", "Delete Workflows", count ) @staticmethod def action_past(count): return ngettext_lazy( "Deleted Workflow", "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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0748355 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/0000755000000000000000000000000015243355747040076 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0825384 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/0000755000000000000000000000000015243355747042133 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000026500000000000010220 xustar00117 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/_create.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041328 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/_crea0000644000000000000000000000033515243355431043116 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

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

{% endblock %} ././@PaxHeader0000000000000000000000000000026600000000000010221 xustar00118 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/_execute.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/_exec0000644000000000000000000000104415243355431043126 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000030000000000000010206 xustar00128 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/_select_definition.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/_sele0000644000000000000000000000114415243355431043133 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000026500000000000010220 xustar00117 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/_update.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/_upda0000644000000000000000000000033015243355431043130 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends "horizon/common/_modal_form.html" %} {% load i18n %} {% block modal-body-right %}

{% trans "Description:" %}

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

{% endblock %} ././@PaxHeader0000000000000000000000000000026400000000000010217 xustar00116 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/create.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/creat0000644000000000000000000000027115243355431043142 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Create Workflow" %}{% endblock %} {% block main %} {% include 'mistral/workflows/_create.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000026400000000000010217 xustar00116 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/detail.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/detai0000644000000000000000000000063315243355431043134 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com {% 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 %}
././@PaxHeader0000000000000000000000000000026500000000000010220 xustar00117 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/execute.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/execu0000644000000000000000000000052315243355431043155 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000026300000000000010216 xustar00115 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/index.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/index0000644000000000000000000000036315243355431043155 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% 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 %} ././@PaxHeader0000000000000000000000000000027700000000000010223 xustar00127 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/select_definition.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/selec0000644000000000000000000000030615243355431043136 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Select Definition" %}{% endblock %} {% block main %} {% include 'mistral/workflows/_select_definition.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000026400000000000010217 xustar00116 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/update.html 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/templates/workflows/updat0000644000000000000000000000027115243355431043161 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com{% extends 'base.html' %} {% load i18n %} {% block title %}{% trans "Update Workflow" %}{% endblock %} {% block main %} {% include 'mistral/workflows/_update.html' %} {% endblock %} ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/tests.py0000644000000000000000000001357115243355431037611 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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: ('pagination_list',)}) def test_index(self): self.mock_pagination_list.return_value = ( self.mistralclient_workflows.list(), False, False, ) 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_pagination_list.assert_called_once_with( entity="workflows", request=helpers.IsHttpRequest(), marker=None, sort_keys="name", sort_dirs="desc", paginate=True, reversed_order=True, ) 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: ('pagination_list', 'workflow_delete')}) def test_delete_ok(self): workflows = self.mistralclient_workflows.list() self.mock_pagination_list.return_value = ( workflows, False, False, ) 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_pagination_list.assert_called_once() 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) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/urls.py0000644000000000000000000000304315243355431037425 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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'), ] ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/mistraldashboard/workflows/views.py0000644000000000000000000001622515243355431037603 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# 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 has_prev_data(self, table): return self._prev def has_more_data(self, table): return self._more def get_data(self): workflows = [] prev_marker = self.request.GET.get( workflows_tables.WorkflowsTable._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( workflows_tables.WorkflowsTable._meta.pagination_param, None ) try: workflows, self._more, self._prev = api.pagination_list( entity="workflows", request=self.request, marker=marker, sort_keys='name', sort_dirs=sort_dir, paginate=True, reversed_order=True ) if prev_marker is not None: workflows = sorted( workflows, key=lambda action: getattr( action, 'name' ), reverse=False ) except Exception as e: self._prev = False self._more = False msg = _('Unable to retrieve workflows list: %s') % str(e) exceptions.handle(self.request, msg) return workflows 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") ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7041495 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/pyproject.toml0000644000000000000000000000214715243355431033426 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com[build-system] requires = ["pbr>=6.1.1"] build-backend = "pbr.build" [project] name = "mistral-dashboard" description = "Mistral UI for Horizon" authors = [ {name = "OpenStack", email = "openstack-discuss@lists.openstack.org"}, ] readme = {file = "README.rst", content-type = "text/x-rst"} license = "Apache-2.0" dynamic = ["version", "dependencies"] requires-python = ">=3.11" classifiers = [ "Environment :: OpenStack", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", ] [project.urls] Homepage = "https://docs.openstack.org/mistral/latest/" Repository = "https://opendev.org/openstack/mistral-dashboard" [tool.setuptools] packages = [ "mistraldashboard" ] ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0748355 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/0000755000000000000000000000000015243355747033211 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0827556 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/notes/0000755000000000000000000000000015243355747034341 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043226 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/notes/.placeholder0000644000000000000000000000000015243355431036600 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043362 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/notes/bug-1931558-4674cdde721dfab8.yaml0000644000000000000000000000051015243355431041202 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com--- 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. ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043362 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/notes/drop-py-2-7-022d0dd59feb8b07.yaml0000644000000000000000000000034015243355431041376 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com--- 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. ././@PaxHeader0000000000000000000000000000027200000000000010216 xustar00122 path=mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/notes/drop-python-3-6-and-3-7-ae37bc21f97de767.yaml 18 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043362 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/notes/drop-python-3-6-and-3-7-ae37bc21f0000644000000000000000000000020115243355431041450 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com--- upgrade: - | Python 3.6 & 3.7 support has been dropped. The minimum version of Python now supported is Python 3.8. ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043362 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/notes/drop-python-38-and-39.yaml0000644000000000000000000000011015243355431040707 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com--- upgrade: - | Support for Python 3.8 and 3.9 has been dropped. ././@PaxHeader0000000000000000000000000000007700000000000010221 xustar0018 uid=1360376536 18 gid=1360376536 27 mtime=1787681767.083961 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/0000755000000000000000000000000015243355747034511 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043362 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/2023.1.rst0000644000000000000000000000021015243355431035747 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: unmaintained/2023.1 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/2023.2.rst0000644000000000000000000000020215243355431035751 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/2024.1.rst0000644000000000000000000000021015243355431035750 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: unmaintained/2024.1 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/2024.2.rst0000644000000000000000000000020215243355431035752 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/2025.1.rst0000644000000000000000000000020215243355431035752 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== 2025.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2025.1 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/2025.2.rst0000644000000000000000000000020215243355431035753 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== 2025.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2025.2 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/2026.1.rst0000644000000000000000000000020215243355431035753 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== 2026.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2026.1 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0839963 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/_static/0000755000000000000000000000000015243355747036137 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/_static/.placeholder0000644000000000000000000000000015243355431040376 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0840352 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/_templates/0000755000000000000000000000000015243355747036646 5ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/_templates/.placeholder0000644000000000000000000000000015243355431041105 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/conf.py0000644000000000000000000000432715243355431036004 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # -- 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/'] ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/index.rst0000644000000000000000000000052315243355431036340 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.comMistral Dashboard Release Notes =============================== Contents ======== .. toctree:: :maxdepth: 2 unreleased 2026.1 2025.2 2025.1 2024.2 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ocata newton mitaka liberty ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/liberty.rst0000644000000000000000000000021515243355431036701 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com============================ Liberty Series Release Notes ============================ .. release-notes:: :branch: origin/stable/liberty ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/mitaka.rst0000644000000000000000000000021115243355431036471 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== Mitaka Series Release Notes =========================== .. release-notes:: :branch: origin/stable/mitaka ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/newton.rst0000644000000000000000000000021115243355431036535 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== Newton Series Release Notes =========================== .. release-notes:: :branch: origin/stable/newton ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/ocata.rst0000644000000000000000000000023015243355431036313 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/pike.rst0000644000000000000000000000021715243355431036161 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/queens.rst0000644000000000000000000000022315243355431036526 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/rocky.rst0000644000000000000000000000022115243355431036353 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/stein.rst0000644000000000000000000000022115243355431036346 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/train.rst0000644000000000000000000000017615243355431036352 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/unreleased.rst0000644000000000000000000000015315243355431037357 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com============================ Current Series Release Notes ============================ .. release-notes:: ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/ussuri.rst0000644000000000000000000000020215243355431036555 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/victoria.rst0000644000000000000000000000022015243355431037043 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/wallaby.rst0000644000000000000000000000021415243355431036661 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/xena.rst0000644000000000000000000000020015243355431036154 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/yoga.rst0000644000000000000000000000020015243355431036160 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/releasenotes/source/zed.rst0000644000000000000000000000017415243355431036015 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/requirements.txt0000644000000000000000000000056315243355431033776 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. pbr>=6.1.1 # Apache-2.0 python-dateutil>=2.9.0 # BSD python-mistralclient>=4.3.0 # Apache-2.0 PyYAML>=3.12 # MIT horizon>=23.1.1 # Apache-2.0 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/run_tests.sh0000755000000000000000000003130515243355431033075 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com#!/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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681767.0846694 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/setup.cfg0000644000000000000000000000011315243355747032334 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com[metadata] name = mistral-dashboard [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/setup.py0000644000000000000000000000122115243355431032214 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup(pbr=True) ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/test-requirements.txt0000644000000000000000000000031315243355431034744 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.comhacking>=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 ././@PaxHeader0000000000000000000000000000010000000000000010204 xustar0018 uid=1360376536 18 gid=1360376536 28 mtime=1787681560.7043972 mistral_dashboard-22.0.0+git20260722.13.a2c6b1c/tox.ini0000644000000000000000000000271415243355431032025 0ustar00himawan.winarto@canonical.comhimawan.winarto@canonical.com[tox] minversion = 4.4 envlist = pep8,py3 [testenv] usedevelop = True 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