Merge "UI: Cron trigger create modal"
This commit is contained in:
commit
e9fa1ef7e5
@ -1,5 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 - StackStorm, Inc.
|
# Copyright 2014 - StackStorm, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -155,7 +153,7 @@ def task_get(request, task_id=None):
|
|||||||
return mistralclient(request).tasks.get(task_id)
|
return mistralclient(request).tasks.get(task_id)
|
||||||
|
|
||||||
|
|
||||||
@handle_errors(_("Unable to retrieve workflows."), [])
|
@handle_errors(_("Unable to retrieve workflows"), [])
|
||||||
def workflow_list(request):
|
def workflow_list(request):
|
||||||
"""Returns all workflows."""
|
"""Returns all workflows."""
|
||||||
|
|
||||||
@ -293,6 +291,21 @@ def action_update(request, action_definition):
|
|||||||
return mistralclient(request).actions.update(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):
|
def action_delete(request, action_name):
|
||||||
"""Delete action.
|
"""Delete action.
|
||||||
|
|
||||||
@ -334,16 +347,35 @@ def cron_trigger_delete(request, cron_trigger_name):
|
|||||||
return mistralclient(request).cron_triggers.delete(cron_trigger_name)
|
return mistralclient(request).cron_triggers.delete(cron_trigger_name)
|
||||||
|
|
||||||
|
|
||||||
def action_run(request, action_name, input, params):
|
def cron_trigger_create(
|
||||||
"""Run specific action execution.
|
request,
|
||||||
|
cron_trigger_name,
|
||||||
|
workflow_ID,
|
||||||
|
workflow_input,
|
||||||
|
workflow_params,
|
||||||
|
pattern,
|
||||||
|
first_time,
|
||||||
|
count
|
||||||
|
):
|
||||||
|
"""Create Cron Trigger.
|
||||||
|
|
||||||
:param action_name: Action name
|
:param request: Request data
|
||||||
:param input: input
|
:param cron_trigger_name: Cron Trigger name
|
||||||
:param params: params
|
: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 <YYYY-MM-DD HH:MM>
|
||||||
|
:param count: Number of wanted executions <integer>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return mistralclient(request).action_executions.create(
|
return mistralclient(request).cron_triggers.create(
|
||||||
action_name,
|
cron_trigger_name,
|
||||||
input,
|
workflow_ID,
|
||||||
**params
|
workflow_input,
|
||||||
|
workflow_params,
|
||||||
|
pattern,
|
||||||
|
first_time,
|
||||||
|
count
|
||||||
)
|
)
|
||||||
|
207
mistraldashboard/cron_triggers/forms.py
Normal file
207
mistraldashboard/cron_triggers/forms.py
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
# 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 ugettext_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-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-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-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-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']
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
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
|
||||||
|
|
||||||
|
finally:
|
||||||
|
pass
|
@ -1,6 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright 2016 - Nokia.
|
||||||
#
|
|
||||||
# Copyright 2016 - Alcatel-Lucent.
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright 2016 - Nokia.
|
||||||
#
|
|
||||||
# Copyright 2016 - Alcatel-Lucent.
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -25,6 +23,14 @@ from mistraldashboard import api
|
|||||||
from mistraldashboard.default.utils import humantime
|
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):
|
class DeleteCronTrigger(tables.DeleteAction):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def action_present(count):
|
def action_present(count):
|
||||||
@ -101,5 +107,9 @@ class CronTriggersTable(tables.DataTable):
|
|||||||
class Meta(object):
|
class Meta(object):
|
||||||
name = "cron trigger"
|
name = "cron trigger"
|
||||||
verbose_name = _("Cron Trigger")
|
verbose_name = _("Cron Trigger")
|
||||||
table_actions = (tables.FilterAction, DeleteCronTrigger)
|
table_actions = (
|
||||||
|
tables.FilterAction,
|
||||||
|
CreateCronTrigger,
|
||||||
|
DeleteCronTrigger
|
||||||
|
)
|
||||||
row_actions = (DeleteCronTrigger,)
|
row_actions = (DeleteCronTrigger,)
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
||||||
|
{% block modal-body-right %}
|
||||||
|
<h3>{% trans "Description" %}:</h3>
|
||||||
|
<p>
|
||||||
|
{% blocktrans %}
|
||||||
|
Cron Trigger is an object allowing to run workflow on a schedule.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% 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 %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% trans "For more info" %}:
|
||||||
|
<ul class="list">
|
||||||
|
<li>
|
||||||
|
<a href="http://docs.openstack.org/developer/mistral/developer/webapi/v2.html#cron-triggers"
|
||||||
|
title="{% trans "Mistral Cron Trigger" %}" target="_blank">
|
||||||
|
{% trans "Mistral Cron Trigger" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://en.wikipedia.org/wiki/Cron"
|
||||||
|
title="Wikipedia" target="_blank">
|
||||||
|
http://en.wikipedia.org/wiki/Cron
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
<strong class="block">
|
||||||
|
{% trans "Please note" %}:
|
||||||
|
</strong>
|
||||||
|
<br/>
|
||||||
|
{% blocktrans %}
|
||||||
|
Name, Workflow ID and Pattern are mandatory fields.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include 'mistral/cron_triggers/_create.html' %}
|
||||||
|
{% endblock %}
|
@ -1,6 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright 2016 - Nokia.
|
||||||
#
|
|
||||||
# Copyright 2016 - Alcatel-Lucent.
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -25,4 +23,7 @@ urlpatterns = patterns(
|
|||||||
'',
|
'',
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||||
url(CRON_TRIGGERS % 'detail', views.OverviewView.as_view(), name='detail'),
|
url(CRON_TRIGGERS % 'detail', views.OverviewView.as_view(), name='detail'),
|
||||||
|
url(r'^create$',
|
||||||
|
views.CreateView.as_view(),
|
||||||
|
name='create'),
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright 2016 - Nokia.
|
||||||
#
|
|
||||||
# Copyright 2016 - Alcatel-Lucent.
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -20,9 +18,11 @@ from django.core.urlresolvers import reverse_lazy
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
|
from horizon import forms
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
|
|
||||||
from mistraldashboard import api
|
from mistraldashboard import api
|
||||||
|
from mistraldashboard.cron_triggers import forms as mistral_forms
|
||||||
from mistraldashboard.cron_triggers.tables import CronTriggersTable
|
from mistraldashboard.cron_triggers.tables import CronTriggersTable
|
||||||
|
|
||||||
|
|
||||||
@ -49,6 +49,22 @@ class OverviewView(generic.TemplateView):
|
|||||||
return context
|
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):
|
class IndexView(tables.DataTableView):
|
||||||
table_class = CronTriggersTable
|
table_class = CronTriggersTable
|
||||||
template_name = 'mistral/cron_triggers/index.html'
|
template_name = 'mistral/cron_triggers/index.html'
|
||||||
|
@ -49,3 +49,15 @@ def prettyprint(x):
|
|||||||
|
|
||||||
return render_to_string("mistral/default/_prettyprint.html",
|
return render_to_string("mistral/default/_prettyprint.html",
|
||||||
{"full": full, "short": short})
|
{"full": full, "short": short})
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
.list{
|
||||||
|
list-style: inherit;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user