pax_global_header 0000666 0000000 0000000 00000000064 14575054011 0014514 g ustar 00root root 0000000 0000000 52 comment=63daad463aa1b85c4f6b92a0c53ae17c57df9a31
mistral-dashboard-19.0.0/ 0000775 0000000 0000000 00000000000 14575054011 0015203 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/.gitignore 0000664 0000000 0000000 00000001024 14575054011 0017170 0 ustar 00root root 0000000 0000000 *.py[cod]
*.sqlite
*.sw?
# C extensions
*.so
# Packages
*.egg
*.egg-info
dist
build
.venv
eggs
.eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
.stestr/
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
.idea
.DS_Store
*.lock
mistraldashboard/test/.secret_key_store
# Files created by releasenotes build
releasenotes/build
# Files created by doc build
doc/source/api
# pbr generates these
AUTHORS
ChangeLog
mistral-dashboard-19.0.0/.gitreview 0000664 0000000 0000000 00000000124 14575054011 0017206 0 ustar 00root root 0000000 0000000 [gerrit]
host=review.opendev.org
port=29418
project=openstack/mistral-dashboard.git
mistral-dashboard-19.0.0/.stestr.conf 0000664 0000000 0000000 00000000071 14575054011 0017452 0 ustar 00root root 0000000 0000000 [DEFAULT]
test_path=./mistraldashboard/tests
top_dir=./
mistral-dashboard-19.0.0/.zuul.yaml 0000664 0000000 0000000 00000000174 14575054011 0017146 0 ustar 00root root 0000000 0000000 - project:
templates:
- horizon-non-primary-django-jobs
- check-requirements
- openstack-python3-jobs
mistral-dashboard-19.0.0/CONTRIBUTING.rst 0000664 0000000 0000000 00000001171 14575054011 0017644 0 ustar 00root root 0000000 0000000 The 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
mistral-dashboard-19.0.0/LICENSE 0000664 0000000 0000000 00000023636 14575054011 0016222 0 ustar 00root root 0000000 0000000
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.
mistral-dashboard-19.0.0/README.rst 0000664 0000000 0000000 00000004275 14575054011 0016702 0 ustar 00root root 0000000 0000000 ========================
Team and repository tags
========================
.. image:: https://governance.openstack.org/tc/badges/mistral-dashboard.svg
:target: https://governance.openstack.org/tc/reference/tags/index.html
.. Change things from this point on
=================
Mistral Dashboard
=================
Horizon plugin for Mistral.
Setup Instructions
==================
This instruction assumes that Horizon is already installed and it's installation
folder is . Detailed information on how to install Horizon can be
found at https://docs.openstack.org/horizon/latest/contributor/quickstart.html#setup.
The installation folder of Mistral Dashboard will be referred to as .
The following should get you started. Clone the repository into your local
OpenStack directory::
$ git clone https://opendev.org/openstack/mistral-dashboard.git
Install mistral-dashboard
$ sudo pip install -e
Or if you're planning to run Horizon server in a virtual environment (see below):
$ tox -evenv -- pip install -e ../mistral-dashboard/
and then
$ 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::
$ sudo service apache2 restart
or run the development server (in case you have decided to use local horizon)::
$ cd ../horizon/
$ tox -evenv -- python manage.py runserver
Mistral-Dashboard Debug Instructions
------------------------------------
For debug instructions refer to `OpenStack Mistral Troubleshooting
`_
mistral-dashboard-19.0.0/doc/ 0000775 0000000 0000000 00000000000 14575054011 0015750 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/doc/requirements.txt 0000664 0000000 0000000 00000000215 14575054011 0021232 0 ustar 00root root 0000000 0000000 sphinx>=2.0.0,!=2.1.0 # BSD
oslosphinx>=4.7.0 # Apache-2.0
reno>=3.1.0 # Apache-2.0
docutils>=0.11 # OSI-Approved Open Source, Public Domain
mistral-dashboard-19.0.0/doc/source/ 0000775 0000000 0000000 00000000000 14575054011 0017250 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/doc/source/conf.py 0000664 0000000 0000000 00000004244 14575054011 0020553 0 ustar 00root root 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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',
'oslosphinx',
]
# 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 = '_theme'
# html_static_path = []
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
mistral-dashboard-19.0.0/doc/source/contributing.rst 0000664 0000000 0000000 00000000113 14575054011 0022504 0 ustar 00root root 0000000 0000000 ============
Contributing
============
.. include:: ../../CONTRIBUTING.rst
mistral-dashboard-19.0.0/doc/source/index.rst 0000664 0000000 0000000 00000000374 14575054011 0021115 0 ustar 00root root 0000000 0000000
Welcome to Mistral Dashboard's documentation!
============================================
Contents:
.. toctree::
:maxdepth: 1
readme
contributing
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
mistral-dashboard-19.0.0/doc/source/readme.rst 0000664 0000000 0000000 00000000035 14575054011 0021235 0 ustar 00root root 0000000 0000000 .. include:: ../../README.rst mistral-dashboard-19.0.0/manage.py 0000775 0000000 0000000 00000001510 14575054011 0017005 0 ustar 00root root 0000000 0000000 #!/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)
mistral-dashboard-19.0.0/mistraldashboard/ 0000775 0000000 0000000 00000000000 14575054011 0020526 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/__init__.py 0000664 0000000 0000000 00000000000 14575054011 0022625 0 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/action_executions/ 0000775 0000000 0000000 00000000000 14575054011 0024251 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/action_executions/__init__.py 0000664 0000000 0000000 00000000000 14575054011 0026350 0 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/action_executions/forms.py 0000664 0000000 0000000 00000006736 14575054011 0025765 0 ustar 00root root 0000000 0000000 # 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'],
)
mistral-dashboard-19.0.0/mistraldashboard/action_executions/panel.py 0000664 0000000 0000000 00000001511 14575054011 0025720 0 ustar 00root root 0000000 0000000 # 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)
mistral-dashboard-19.0.0/mistraldashboard/action_executions/tables.py 0000664 0000000 0000000 00000010306 14575054011 0026075 0 ustar 00root root 0000000 0000000 # Copyright 2016 - Nokia.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from mistraldashboard import api
from mistraldashboard.default import smart_cell
from mistraldashboard.default import utils
smart_cell.init()
class UpdateRow(tables.Row):
ajax = True
def get_data(self, request, id):
instance = api.action_execution_get(request, id)
return instance
class DeleteActionExecution(tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
u"Delete Action Execution",
u"Delete Action Executions",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
u"Deleted Action Execution",
u"Deleted Action Executions",
count
)
def delete(self, request, action_execution_id):
api.action_execution_delete(request, action_execution_id)
class UpdateActionExecution(tables.LinkAction):
name = "updateAE"
verbose_name = _("Update")
url = "horizon:mistral:action_executions:update"
classes = ("ajax-modal",)
class TaskExecutionIDColumn(tables.Column):
def get_link_url(self, datum):
task_url = "horizon:mistral:tasks:detail"
obj_id = datum.task_execution_id
return reverse(task_url, args=[obj_id])
class WorkflowNameColumn(tables.Column):
def get_link_url(self, datum):
workflow_url = "horizon:mistral:workflows:detail"
obj_id = datum.workflow_name
return reverse(workflow_url, args=[obj_id])
class ActionExecutionsTable(tables.DataTable):
def getHoverHelp(data):
if hasattr(data, 'state_info') and data.state_info:
return {'title': data.state_info}
STATE_STATUS_CHOICES = (
("success", True),
("error", False),
("idle", None),
("running", None),
("canceled", None),
)
id = tables.Column(
"id",
verbose_name=_("ID"),
link="horizon:mistral:action_executions:detail"
)
name = tables.Column(
"name",
verbose_name=_("Name")
)
tags = tables.Column(
"tags",
verbose_name=_("Tags")
)
workflow_name = WorkflowNameColumn(
"workflow_name",
verbose_name=_("Workflow Name"),
link=True
)
task_execution_id = TaskExecutionIDColumn(
"task_execution_id",
verbose_name=_("Task Execution ID"),
link=True
)
task_name = tables.Column(
"task_name",
verbose_name=_("Task name")
)
description = tables.Column(
"description",
verbose_name=_("Description")
)
created_at = tables.Column(
"created_at",
verbose_name=_("Created at"),
filters=[utils.humantime]
)
updated_at = tables.Column(
"updated_at",
verbose_name=_("Updated at"),
filters=[utils.humantime]
)
accepted = tables.Column(
"accepted",
verbose_name=_("Accepted"),
filters=[utils.booleanfield],
)
state = tables.Column(
"state",
status=True,
status_choices=STATE_STATUS_CHOICES,
verbose_name=_("State"),
filters=[utils.label],
cell_attributes_getter=getHoverHelp
)
class Meta(object):
name = "actionExecutions"
verbose_name = _("Action Executions")
status_columns = ["state"]
row_class = UpdateRow
table_actions = (
tables.FilterAction,
DeleteActionExecution
)
row_actions = (UpdateActionExecution, DeleteActionExecution)
mistral-dashboard-19.0.0/mistraldashboard/action_executions/templates/ 0000775 0000000 0000000 00000000000 14575054011 0026247 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/action_executions/templates/action_executions/ 0000775 0000000 0000000 00000000000 14575054011 0031772 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/action_executions/templates/action_executions/_update.html0000664 0000000 0000000 00000001022 14575054011 0034274 0 ustar 00root root 0000000 0000000 {% 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 %}
mistral-dashboard-19.0.0/mistraldashboard/action_executions/templates/action_executions/detail.html 0000664 0000000 0000000 00000007063 14575054011 0034130 0 ustar 00root root 0000000 0000000
{% 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 %}
{% endif %}
{% if action_execution.task_execution_url %}
{% endif %}
{% endblock %}
filtered.html 0000664 0000000 0000000 00000000522 14575054011 0034376 0 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/action_executions/templates/action_executions {% 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 %}
mistral-dashboard-19.0.0/mistraldashboard/action_executions/templates/action_executions/index.html 0000664 0000000 0000000 00000000410 14575054011 0033762 0 ustar 00root root 0000000 0000000 {% 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 %}
mistral-dashboard-19.0.0/mistraldashboard/action_executions/templates/action_executions/update.html 0000664 0000000 0000000 00000000405 14575054011 0034141 0 ustar 00root root 0000000 0000000 {% 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 %} mistral-dashboard-19.0.0/mistraldashboard/action_executions/tests.py 0000664 0000000 0000000 00000005311 14575054011 0025765 0 ustar 00root root 0000000 0000000 # Copyright 2015 Huawei Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import reverse
from openstack_dashboard.test import helpers
from mistraldashboard import api
from mistraldashboard.test import helpers as test
INDEX_URL = reverse('horizon:mistral:action_executions:index')
class ActionExecutionsTest(test.TestCase):
@helpers.create_mocks({api: ('action_executions_list',)})
def test_index(self):
self.mock_action_executions_list.return_value =\
self.mistralclient_action_executions.list()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'mistral/action_executions/index.html')
self.mock_action_executions_list.assert_called_once_with(
helpers.IsHttpRequest())
@helpers.create_mocks({api: ('action_execution_update',)})
def test_update_post(self):
action_execution = self.mistralclient_action_executions.first()
self.mock_action_execution_update.return_value = action_execution
form_data = {"action_execution_id": action_execution.id,
"state": action_execution.state,
"output_source": "raw",
"output_data": action_execution.output}
res = self.client.post(
reverse('horizon:mistral:action_executions:update',
args=(action_execution.id,)),
form_data)
self.assertNoFormErrors(res)
self.mock_action_execution_update.assert_called_once_with(
helpers.IsHttpRequest(), action_execution.id,
action_execution.state, action_execution.output)
@helpers.create_mocks({api: ('action_execution_get',)})
def test_detail(self):
action_execution = self.mistralclient_action_executions.list()[0]
self.mock_action_execution_get.return_value = action_execution
url = reverse('horizon:mistral:action_executions:detail',
args=[action_execution.id])
res = self.client.get(url)
self.assertTemplateUsed(res, 'mistral/action_executions/detail.html')
self.mock_action_execution_get.assert_called_once_with(
helpers.IsHttpRequest(), action_execution.id)
mistral-dashboard-19.0.0/mistraldashboard/action_executions/urls.py 0000664 0000000 0000000 00000002506 14575054011 0025613 0 ustar 00root root 0000000 0000000 # 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')
]
mistral-dashboard-19.0.0/mistraldashboard/action_executions/views.py 0000664 0000000 0000000 00000012555 14575054011 0025770 0 ustar 00root root 0000000 0000000 # Copyright 2016 - Nokia.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views import generic
from horizon import forms
from horizon import tables
from mistraldashboard.action_executions import forms as action_execution_forms
from mistraldashboard.action_executions import tables as mistral_tables
from mistraldashboard import api
from mistraldashboard.default import utils
from mistraldashboard import forms as mistral_forms
def get_single_action_execution_data(request, **kwargs):
action_execution_id = kwargs['action_execution_id']
action_execution = api.action_execution_get(
request,
action_execution_id
)
return action_execution
class OverviewView(generic.TemplateView):
template_name = 'mistral/action_executions/detail.html'
page_title = _("Action Execution Details")
workflow_url = 'horizon:mistral:workflows:detail'
task_execution_url = 'horizon:mistral:tasks:detail'
def get_context_data(self, **kwargs):
context = super(OverviewView, self).get_context_data(**kwargs)
action_execution = get_single_action_execution_data(
self.request,
**kwargs
)
if action_execution.workflow_name:
action_execution.workflow_url = reverse(
self.workflow_url,
args=[action_execution.workflow_name])
if action_execution.task_execution_id:
action_execution.task_execution_url = reverse(
self.task_execution_url,
args=[action_execution.task_execution_id]
)
if action_execution.input:
action_execution.input = utils.prettyprint(action_execution.input)
if action_execution.output:
action_execution.output = utils.prettyprint(
action_execution.output
)
if action_execution.state:
action_execution.state = utils.label(action_execution.state)
action_execution.accepted = utils.booleanfield(
action_execution.accepted
)
breadcrumb = [(action_execution.id, reverse(
'horizon:mistral:action_executions:detail',
args=[action_execution.id]
))]
context["custom_breadcrumb"] = breadcrumb
context['action_execution'] = action_execution
return context
class CodeView(forms.ModalFormView):
template_name = 'mistral/default/code.html'
modal_header = _("Code view")
form_id = "code_view"
form_class = mistral_forms.EmptyForm
cancel_label = "OK"
cancel_url = reverse_lazy("horizon:mistral:action_executions:index")
page_title = _("Code view")
def get_context_data(self, **kwargs):
context = super(CodeView, self).get_context_data(**kwargs)
column = self.kwargs['column']
action_execution = get_single_action_execution_data(
self.request,
**self.kwargs
)
io = {}
if column == 'input':
io['name'] = _('Input')
io['value'] = utils.prettyprint(action_execution.input)
elif column == 'output':
io['name'] = _('Output')
io['value'] = (
utils.prettyprint(action_execution.output)
if action_execution.output
else _("No available output yet")
)
context['io'] = io
return context
class IndexView(tables.DataTableView):
table_class = mistral_tables.ActionExecutionsTable
template_name = 'mistral/action_executions/index.html'
def get_data(self):
return api.action_executions_list(self.request)
class UpdateView(forms.ModalFormView):
template_name = 'mistral/action_executions/update.html'
modal_header = _("Update Action Execution")
form_id = "update_action_execution"
form_class = action_execution_forms.UpdateForm
submit_label = _("Update")
success_url = reverse_lazy("horizon:mistral:action_executions:index")
submit_url = "horizon:mistral:action_executions:update"
cancel_url = "horizon:mistral:action_executions:index"
page_title = _("Update Action Execution")
def get_initial(self):
return {"action_execution_id": self.kwargs["action_execution_id"]}
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(
self.submit_url,
args=[self.kwargs["action_execution_id"]]
)
return context
class FilteredByTaskView(tables.DataTableView):
table_class = mistral_tables.ActionExecutionsTable
template_name = 'mistral/action_executions/filtered.html'
data = {}
def get_data(self, **kwargs):
task_id = self.kwargs['task_id']
data = api.action_executions_list(self.request, task_id)
return data
mistral-dashboard-19.0.0/mistraldashboard/actions/ 0000775 0000000 0000000 00000000000 14575054011 0022166 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/actions/__init__.py 0000664 0000000 0000000 00000000000 14575054011 0024265 0 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/actions/forms.py 0000664 0000000 0000000 00000014676 14575054011 0023704 0 ustar 00root root 0000000 0000000 # 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)
mistral-dashboard-19.0.0/mistraldashboard/actions/panel.py 0000664 0000000 0000000 00000001471 14575054011 0023642 0 ustar 00root root 0000000 0000000 # 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)
mistral-dashboard-19.0.0/mistraldashboard/actions/tables.py 0000664 0000000 0000000 00000007005 14575054011 0024014 0 ustar 00root root 0000000 0000000 # Copyright 2015 Huawei Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from horizon.utils import filters
from mistraldashboard import api
from mistraldashboard.default import smart_cell
from mistraldashboard.default import utils
smart_cell.init()
class CreateAction(tables.LinkAction):
name = "create"
verbose_name = _("Create Action")
url = "horizon:mistral:actions:create"
classes = ("ajax-modal",)
icon = "plus"
class UpdateAction(tables.LinkAction):
name = "update"
verbose_name = _("Update Action")
url = "horizon:mistral:actions:update"
classes = ("ajax-modal",)
icon = "pencil"
class DeleteAction(tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
u"Delete Action",
u"Delete Actions",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
u"Deleted Action",
u"Deleted Actions",
count
)
def delete(self, request, action_name):
api.action_delete(request, action_name)
def allowed(self, request, action=None):
if action:
return not action.is_system
else:
return True
def tags_to_string(action):
return ', '.join(action.tags) if action.tags else None
def cut(action, length=100):
inputs = action.input
if inputs and len(inputs) > length:
return "%s..." % inputs[:length]
else:
return inputs
class RunAction(tables.LinkAction):
name = "run"
verbose_name = _("Run")
classes = ("ajax-modal",)
def get_link_url(self, datum):
obj_id = datum.name
url = "horizon:mistral:actions:run"
return reverse(url, args=[obj_id])
class ActionsTable(tables.DataTable):
name = tables.Column(
"name",
verbose_name=_("Name"),
link="horizon:mistral:actions:detail"
)
is_system = tables.Column(
"is_system",
verbose_name=_("Is System"),
filters=[utils.booleanfield]
)
tags = tables.Column(
tags_to_string,
verbose_name=_("Tags")
)
inputs = tables.Column(
cut,
verbose_name=_("Input")
)
created = tables.Column(
"created_at",
verbose_name=_("Created"),
filters=(
filters.parse_isotime,
filters.timesince_or_never
)
)
updated = tables.Column(
"updated_at",
verbose_name=_("Updated"),
filters=(
filters.parse_isotime,
filters.timesince_or_never
)
)
class Meta(object):
name = "actions"
verbose_name = _("Actions")
table_actions = (
CreateAction,
UpdateAction,
DeleteAction,
tables.FilterAction,
)
row_actions = (RunAction, DeleteAction)
mistral-dashboard-19.0.0/mistraldashboard/actions/templates/ 0000775 0000000 0000000 00000000000 14575054011 0024164 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/actions/templates/actions/ 0000775 0000000 0000000 00000000000 14575054011 0025624 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/actions/templates/actions/_create.html 0000664 0000000 0000000 00000001140 14575054011 0030110 0 ustar 00root root 0000000 0000000 {% 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 %}
mistral-dashboard-19.0.0/mistraldashboard/actions/templates/actions/_run.html 0000664 0000000 0000000 00000000317 14575054011 0027456 0 ustar 00root root 0000000 0000000 {% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
{% trans "Description:" %}
{% trans "From here you can run an action." %}
{% endblock %}
mistral-dashboard-19.0.0/mistraldashboard/actions/templates/actions/_update.html 0000664 0000000 0000000 00000001140 14575054011 0030127 0 ustar 00root root 0000000 0000000 {% 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 %}
mistral-dashboard-19.0.0/mistraldashboard/actions/templates/actions/create.html 0000664 0000000 0000000 00000000265 14575054011 0027760 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Action" %}{% endblock %}
{% block main %}
{% include 'mistral/actions/_create.html' %}
{% endblock %}
mistral-dashboard-19.0.0/mistraldashboard/actions/templates/actions/detail.html 0000664 0000000 0000000 00000002517 14575054011 0027761 0 ustar 00root root 0000000 0000000
{% 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 %}
mistral-dashboard-19.0.0/mistraldashboard/actions/templates/actions/index.html 0000664 0000000 0000000 00000000356 14575054011 0027625 0 ustar 00root root 0000000 0000000 {% 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 %} mistral-dashboard-19.0.0/mistraldashboard/actions/templates/actions/run.html 0000664 0000000 0000000 00000000376 14575054011 0027324 0 ustar 00root root 0000000 0000000 {% 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 %}
mistral-dashboard-19.0.0/mistraldashboard/actions/templates/actions/update.html 0000664 0000000 0000000 00000000265 14575054011 0027777 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Action" %}{% endblock %}
{% block main %}
{% include 'mistral/actions/_update.html' %}
{% endblock %}
mistral-dashboard-19.0.0/mistraldashboard/actions/tests.py 0000664 0000000 0000000 00000006042 14575054011 0023704 0 ustar 00root root 0000000 0000000 # 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)
mistral-dashboard-19.0.0/mistraldashboard/actions/urls.py 0000664 0000000 0000000 00000002055 14575054011 0023527 0 ustar 00root root 0000000 0000000 # 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'),
]
mistral-dashboard-19.0.0/mistraldashboard/actions/views.py 0000664 0000000 0000000 00000012023 14575054011 0023673 0 ustar 00root root 0000000 0000000 # 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
mistral-dashboard-19.0.0/mistraldashboard/api.py 0000664 0000000 0000000 00000027761 14575054011 0021666 0 ustar 00root root 0000000 0000000 # Copyright 2014 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import itertools
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from horizon.utils import functions as utils
from horizon.utils import memoized
from mistralclient.api import client as mistral_client
from mistraldashboard.handle_errors import handle_errors
from openstack_dashboard.api import base
SERVICE_TYPE = 'workflowv2'
@memoized.memoized
def mistralclient(request):
return mistral_client.client(
username=request.user.username,
auth_token=request.user.token.id,
project_id=request.user.tenant_id,
# We can't use auth_url param in here if we config
# and use keystone federation
mistral_url=base.url_for(request, 'workflowv2'),
# Todo: add SECONDARY_ENDPOINT_TYPE support
endpoint_type=getattr(
settings,
'OPENSTACK_ENDPOINT_TYPE',
'internalURL'
),
service_type=SERVICE_TYPE,
# We should not treat definition as file path or uri otherwise
# we allow access to contents in internal servers
enforce_raw_definition=False
)
@handle_errors(_("Unable to retrieve list"), [])
def pagination_list(entity, request, marker='', sort_keys='', sort_dirs='asc',
paginate=False, reversed_order=False, selector=None):
"""Retrieve a listing of specific entity and handles pagination.
:param entity: Requested entity (String)
:param request: Request data
:param marker: Pagination marker for large data sets: entity id
:param sort_keys: Columns to sort results by
:param sort_dirs: Sorting Directions (asc/desc). Default:asc
:param paginate: If true will perform pagination based on settings.
Default:False
:param reversed_order: flag to reverse list. Default:False
:param selector: additional selector to allow further server filtering
"""
limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
page_size = utils.get_page_size(request)
if paginate:
request_size = page_size + 1
else:
request_size = limit
if reversed_order:
sort_dirs = 'desc' if sort_dirs == 'asc' else 'asc'
api = mistralclient(request)
entities_iter = (
getattr(api, entity).list(
selector,
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs
) if selector else (
getattr(api, entity).list(
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs
)
)
)
has_prev_data = has_more_data = False
if paginate:
entities = list(itertools.islice(entities_iter, request_size))
# first and middle page condition
if len(entities) > page_size:
entities.pop(-1)
has_more_data = True
# middle page condition
if marker is not None:
has_prev_data = True
# first page condition when reached via prev back
elif reversed_order and marker is not None:
has_more_data = True
# last page condition
elif marker is not None:
has_prev_data = True
# restore the original ordering here
if reversed_order:
entities = sorted(entities,
key=lambda ent:
(getattr(ent, sort_keys) or '').lower(),
reverse=(sort_dirs == 'desc')
)
else:
entities = list(entities_iter)
return entities, has_more_data, has_prev_data
def execution_create(request, **data):
"""Creates new execution."""
return mistralclient(request).executions.create(**data)
def execution_get(request, execution_id):
"""Get specific execution.
:param execution_id: Execution ID
"""
return mistralclient(request).executions.get(execution_id)
def execution_update(request, execution_id, field, value):
"""update specific execution field, either state or description.
:param request: Request data
:param execution_id: Execution ID
:param field: flag - either Execution state or description
:param value: new update value
"""
if field == "state":
return mistralclient(request).\
executions.update(execution_id, value)
elif field == "description":
return mistralclient(request).\
executions.update(execution_id, None, value)
def execution_delete(request, execution_name):
"""Delete execution.
:param execution_name: Execution name
"""
return mistralclient(request).executions.delete(execution_name)
@handle_errors(_("Unable to retrieve tasks."), [])
def task_list(request, execution_id=None):
"""Returns all tasks.
:param execution_id: Workflow execution ID associated with list of tasks
"""
return mistralclient(request).tasks.list(execution_id)
def task_get(request, task_id=None):
"""Get specific task.
:param task_id: Task ID
"""
return mistralclient(request).tasks.get(task_id)
@handle_errors(_("Unable to retrieve workflows"), [])
def workflow_list(request):
"""Returns all workflows."""
return mistralclient(request).workflows.list()
def workflow_get(request, workflow_name):
"""Get specific workflow.
:param workflow_name: Workflow name
"""
return mistralclient(request).workflows.get(workflow_name)
def workflow_create(request, workflows_definition):
"""Create workflow.
:param workflows_definition: Workflows definition
"""
return mistralclient(request).workflows.create(workflows_definition)
def workflow_validate(request, workflow_definition):
"""Validate workflow.
:param workflow_definition: Workflow definition
"""
return mistralclient(request).workflows.validate(workflow_definition)
def workflow_delete(request, workflow_name):
"""Delete workflow.
:param workflow_name: Workflow name
"""
return mistralclient(request).workflows.delete(workflow_name)
def workflow_update(request, workflows_definition):
"""Update workflow.
:param workflows_definition: Workflows definition
"""
return mistralclient(request).workflows.update(workflows_definition)
@handle_errors(_("Unable to retrieve workbooks."), [])
def workbook_list(request):
"""Returns all workbooks."""
return mistralclient(request).workbooks.list()
def workbook_get(request, workbook_name):
"""Get specific workbook.
:param workbook_name: Workbook name
"""
return mistralclient(request).workbooks.get(workbook_name)
def workbook_create(request, workbook_definition):
"""Create workbook.
:param workbook_definition: Workbook definition
"""
return mistralclient(request).workbooks.create(workbook_definition)
def workbook_validate(request, workbook_definition):
"""Validate workbook.
:param workbook_definition: Workbook definition
"""
return mistralclient(request).workbooks.validate(workbook_definition)
def workbook_delete(request, workbook_name):
"""Delete workbook.
:param workbook_name: Workbook name
"""
return mistralclient(request).workbooks.delete(workbook_name)
def workbook_update(request, workbook_definition):
"""Update workbook.
:param workbook_definition: Workbook definition
"""
return mistralclient(request).workbooks.update(workbook_definition)
@handle_errors(_("Unable to retrieve actions."), [])
def action_list(request):
"""Returns all actions."""
return mistralclient(request).actions.list()
def action_get(request, action_name):
"""Get specific action.
:param action_name: Action name
"""
return mistralclient(request).actions.get(action_name)
def action_create(request, action_definition):
"""Create action.
:param action_definition: Action definition
"""
return mistralclient(request).actions.create(action_definition)
def action_update(request, action_definition):
"""Update action.
:param action_definition: Action definition
"""
return mistralclient(request).actions.update(action_definition)
def action_run(request, action_name, input, params):
"""Run specific action execution.
:param action_name: Action name
:param input: input
:param params: params
"""
return mistralclient(request).action_executions.create(
action_name,
input,
**params
)
def action_delete(request, action_name):
"""Delete action.
:param action_name: Action name
"""
return mistralclient(request).actions.delete(action_name)
@handle_errors(_("Unable to retrieve action executions list"), [])
def action_executions_list(request, task_execution_id=None):
"""Returns all actions executions.
:param request: Request data
:param task_execution_id: (Optional) Task Execution ID to filter by
"""
return mistralclient(request).action_executions.list(task_execution_id)
@handle_errors(_("Unable to retrieve action execution"), [])
def action_execution_get(request, action_execution_id):
"""Get specific action execution.
:param action_execution_id: Action Execution ID
"""
return mistralclient(request).action_executions.get(action_execution_id)
@handle_errors(_("Unable to delete action execution/s"), [])
def action_execution_delete(request, action_execution_id):
"""Delete action execution.
:param action_execution_id: Action execution ID
"""
return mistralclient(request).action_executions.delete(action_execution_id)
def action_execution_update(request, id, state=None, output=None):
"""Update action execution output and or state.
:param id: action execution id
:param output: action execution output
:param state: action execution state
"""
return mistralclient(request).action_executions.update(id, state, output)
@handle_errors(_("Unable to retrieve cron trigger list"), [])
def cron_trigger_list(request):
"""Returns all cron triggers.
:param request: Request data
"""
return mistralclient(request).cron_triggers.list()
@handle_errors(_("Unable to retrieve cron trigger"), [])
def cron_trigger_get(request, cron_trigger_name):
"""Get specific cron trigger.
:param request: Request data
:param cron_trigger_name: Cron trigger name
"""
return mistralclient(request).cron_triggers.get(cron_trigger_name)
@handle_errors(_("Unable to delete cron trigger/s"), [])
def cron_trigger_delete(request, cron_trigger_name):
"""Delete Cron Trigger.
:param request: Request data
:param cron_trigger_name: Cron Trigger name
"""
return mistralclient(request).cron_triggers.delete(cron_trigger_name)
def cron_trigger_create(
request,
cron_trigger_name,
workflow_ID,
workflow_input,
workflow_params,
pattern,
first_time,
count
):
"""Create Cron Trigger.
:param request: Request data
:param cron_trigger_name: Cron Trigger name
:param workflow_ID: Workflow ID
:param workflow_input: Workflow input
:param workflow_params: Workflow params <* * * * *>
:param pattern: <* * * * *>
:param first_time:
Date and time of the first execution
:param count: Number of wanted executions
"""
return mistralclient(request).cron_triggers.create(
cron_trigger_name,
workflow_ID,
workflow_input,
workflow_params,
pattern,
first_time,
count
)
mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/ 0000775 0000000 0000000 00000000000 14575054011 0023375 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/__init__.py 0000664 0000000 0000000 00000000000 14575054011 0025474 0 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/forms.py 0000664 0000000 0000000 00000016065 14575054011 0025105 0 ustar 00root root 0000000 0000000 # 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
mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/panel.py 0000664 0000000 0000000 00000001471 14575054011 0025051 0 ustar 00root root 0000000 0000000 # 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)
mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/tables.py 0000664 0000000 0000000 00000006173 14575054011 0025230 0 ustar 00root root 0000000 0000000 # Copyright 2016 - Nokia.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from mistraldashboard import api
from mistraldashboard.default.utils import humantime
class CreateCronTrigger(tables.LinkAction):
name = "create"
verbose_name = _("Create Cron Trigger")
url = "horizon:mistral:cron_triggers:create"
classes = ("ajax-modal",)
icon = "plus"
class DeleteCronTrigger(tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
u"Delete Cron Trigger",
u"Delete Cron Triggers",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
u"Deleted Cron Trigger",
u"Deleted Cron Triggers",
count
)
def delete(self, request, cron_trigger_name):
api.cron_trigger_delete(request, cron_trigger_name)
class WorkflowColumn(tables.Column):
def get_link_url(self, datum):
workflow_url = "horizon:mistral:workflows:detail"
obj_id = datum.workflow_name
return reverse(workflow_url, args=[obj_id])
class CronTriggersTable(tables.DataTable):
id = tables.Column(
"id",
verbose_name=_("ID"),
link="horizon:mistral:cron_triggers:detail"
)
name = tables.Column(
"name",
verbose_name=_("Name")
)
workflow_name = WorkflowColumn(
"workflow_name",
verbose_name=_("Workflow"),
link=True
)
pattern = tables.Column(
"pattern",
verbose_name=_("Pattern"),
)
next_execution_time = tables.Column(
"next_execution_time",
verbose_name=_("Next Execution Time"),
)
remaining_executions = tables.Column(
"remaining_executions",
verbose_name=_("Remaining Executions"),
)
first_execution_time = tables.Column(
"first_execution_time",
verbose_name=_("First Execution Time"),
)
created_at = tables.Column(
"created_at",
verbose_name=_("Created at"),
filters=[humantime]
)
updated_at = tables.Column(
"updated_at",
verbose_name=_("Updated at"),
filters=[humantime]
)
def get_object_id(self, datum):
return datum.name
class Meta(object):
name = "cron trigger"
verbose_name = _("Cron Trigger")
table_actions = (
tables.FilterAction,
CreateCronTrigger,
DeleteCronTrigger
)
row_actions = (DeleteCronTrigger,)
mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/templates/ 0000775 0000000 0000000 00000000000 14575054011 0025373 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/templates/cron_triggers/ 0000775 0000000 0000000 00000000000 14575054011 0030242 5 ustar 00root root 0000000 0000000 mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/templates/cron_triggers/_create.html 0000664 0000000 0000000 00000002712 14575054011 0032534 0 ustar 00root root 0000000 0000000 {% 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 %}
mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/templates/cron_triggers/create.html 0000664 0000000 0000000 00000000224 14575054011 0032371 0 ustar 00root root 0000000 0000000 {% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% block main %}
{% include 'mistral/cron_triggers/_create.html' %}
{% endblock %}
mistral-dashboard-19.0.0/mistraldashboard/cron_triggers/templates/cron_triggers/detail.html 0000664 0000000 0000000 00000006504 14575054011 0032377 0 ustar 00root root 0000000 0000000
{% extends 'mistral/default/base.html' %}
{% load i18n %}
{% block title %}{% trans "Cron Trigger Details" %}{% endblock %}
{% block page_header %}
{% trans "Cron Trigger Details" %}
-
Cron Triggers
-
{{ cron_trigger.name }}
{% 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"}}
{% endblock %}