UI: Cron trigger create modal
* Added cron trigger create modal Screenshots: http://pasteboard.co/1q5HRneL.png Partially implements blueprint: mistral-dashboard-cron-trigger-screen Change-Id: I80449534b5e1ce35b9f60b1d3160550933297345
This commit is contained in:
parent
57e26272a5
commit
10a31bdb7a
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2014 - StackStorm, Inc.
|
||||
#
|
||||
# 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)
|
||||
|
||||
|
||||
@handle_errors(_("Unable to retrieve workflows."), [])
|
||||
@handle_errors(_("Unable to retrieve workflows"), [])
|
||||
def workflow_list(request):
|
||||
"""Returns all workflows."""
|
||||
|
||||
@ -293,6 +291,21 @@ def action_update(request, 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.
|
||||
|
||||
@ -334,16 +347,35 @@ def cron_trigger_delete(request, cron_trigger_name):
|
||||
return mistralclient(request).cron_triggers.delete(cron_trigger_name)
|
||||
|
||||
|
||||
def action_run(request, action_name, input, params):
|
||||
"""Run specific action execution.
|
||||
def cron_trigger_create(
|
||||
request,
|
||||
cron_trigger_name,
|
||||
workflow_ID,
|
||||
workflow_input,
|
||||
workflow_params,
|
||||
pattern,
|
||||
first_time,
|
||||
count
|
||||
):
|
||||
"""Create Cron Trigger.
|
||||
|
||||
:param action_name: Action name
|
||||
:param input: input
|
||||
:param params: params
|
||||
: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 <YYYY-MM-DD HH:MM>
|
||||
:param count: Number of wanted executions <integer>
|
||||
"""
|
||||
|
||||
return mistralclient(request).action_executions.create(
|
||||
action_name,
|
||||
input,
|
||||
**params
|
||||
return mistralclient(request).cron_triggers.create(
|
||||
cron_trigger_name,
|
||||
workflow_ID,
|
||||
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 - Alcatel-Lucent.
|
||||
# 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.
|
||||
|
@ -1,6 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 - Alcatel-Lucent.
|
||||
# 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.
|
||||
@ -25,6 +23,14 @@ 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):
|
||||
@ -101,5 +107,9 @@ class CronTriggersTable(tables.DataTable):
|
||||
class Meta(object):
|
||||
name = "cron trigger"
|
||||
verbose_name = _("Cron Trigger")
|
||||
table_actions = (tables.FilterAction, DeleteCronTrigger)
|
||||
table_actions = (
|
||||
tables.FilterAction,
|
||||
CreateCronTrigger,
|
||||
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 - Alcatel-Lucent.
|
||||
# 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.
|
||||
@ -25,4 +23,7 @@ urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
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 - Alcatel-Lucent.
|
||||
# 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.
|
||||
@ -20,9 +18,11 @@ from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views import generic
|
||||
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
|
||||
from mistraldashboard import api
|
||||
from mistraldashboard.cron_triggers import forms as mistral_forms
|
||||
from mistraldashboard.cron_triggers.tables import CronTriggersTable
|
||||
|
||||
|
||||
@ -49,6 +49,22 @@ class OverviewView(generic.TemplateView):
|
||||
return context
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
template_name = 'mistral/cron_triggers/create.html'
|
||||
modal_header = _("Create Cron Trigger")
|
||||
form_id = "create_cron_trigger"
|
||||
form_class = mistral_forms.CreateForm
|
||||
submit_label = _("Create Cron Trigger")
|
||||
submit_url = reverse_lazy("horizon:mistral:cron_triggers:create")
|
||||
success_url = reverse_lazy('horizon:mistral:cron_triggers:index')
|
||||
page_title = _("Create Cron Trigger")
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(CreateView, self).get_form_kwargs()
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = CronTriggersTable
|
||||
template_name = 'mistral/cron_triggers/index.html'
|
||||
|
@ -49,3 +49,15 @@ def prettyprint(x):
|
||||
|
||||
return render_to_string("mistral/default/_prettyprint.html",
|
||||
{"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…
x
Reference in New Issue
Block a user