mistral-dashboard: added action executions screens
* Added 3 new action executions screens: index, overview, and update dialog * Added "type" attribute to task screens, followed by a corresponding link * Fixed breadcrumbs issue in detail screens following Horizon Newton change * Added state label design to detail screens * Added design to boolean fields * Screenshots: http://pho.to/AXApU Implements blueprint: mistral-dashboard-action-execution-screens Implements blueprint: dashboard-detail-screens-improvements Partially implements blueprint: refactor-execution-link-in-task-executions-screens Closes-Bug: #1642246 Change-Id: I4c270b2b23d548d4e1cb7e8507e804e44e27c88f
This commit is contained in:
parent
1665e7c8a7
commit
2a54532154
0
mistraldashboard/action_executions/__init__.py
Normal file
0
mistraldashboard/action_executions/__init__.py
Normal file
102
mistraldashboard/action_executions/forms.py
Normal file
102
mistraldashboard/action_executions/forms.py
Normal file
@ -0,0 +1,102 @@
|
||||
# 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 ugettext_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', _('<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', _('<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'],
|
||||
)
|
28
mistraldashboard/action_executions/panel.py
Normal file
28
mistraldashboard/action_executions/panel.py
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
from mistraldashboard import dashboard
|
||||
|
||||
|
||||
class Tasks(horizon.Panel):
|
||||
name = _("Action Executions")
|
||||
slug = 'action_executions'
|
||||
|
||||
|
||||
dashboard.MistralDashboard.register(Tasks)
|
182
mistraldashboard/action_executions/tables.py
Normal file
182
mistraldashboard/action_executions/tables.py
Normal file
@ -0,0 +1,182 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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.core.urlresolvers import reverse
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
from mistraldashboard import api
|
||||
import mistraldashboard.default.SmartCell as SmartCell
|
||||
from mistraldashboard.default import utils
|
||||
|
||||
SmartCell.init()
|
||||
|
||||
|
||||
class UpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, id):
|
||||
try:
|
||||
instance = api.action_execution_get(request, id)
|
||||
except Exception as e:
|
||||
msg = _("Unable to get action execution by ID %(id)s: %(e)s.") % {
|
||||
'id': id, 'e': str(e)
|
||||
}
|
||||
exceptions.handle(request, msg)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class DeleteActionExecution(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Action Execution",
|
||||
u"Delete Action Executions",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_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")
|
||||
)
|
||||
input = tables.Column(
|
||||
"",
|
||||
verbose_name=_("Input"),
|
||||
empty_value=_("View"),
|
||||
link="horizon:mistral:action_executions:input",
|
||||
link_classes=("ajax-modal",)
|
||||
)
|
||||
output = tables.Column(
|
||||
"",
|
||||
verbose_name=_("Output"),
|
||||
empty_value=_("View"),
|
||||
link="horizon:mistral:action_executions:output",
|
||||
link_classes=("ajax-modal",)
|
||||
)
|
||||
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)
|
@ -0,0 +1,17 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>
|
||||
{% trans "Enter new output and or state to update the corresponding Action Execution." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "For more info refer to:" %}
|
||||
<br/>
|
||||
<a href="http://docs.openstack.org/developer/mistral/developer/webapi/v2.html#action-executions" target="_blank">
|
||||
{% trans "Mistral documentation - Action Executions" %}
|
||||
</a>
|
||||
|
||||
</p>
|
||||
{% endblock %}
|
@ -0,0 +1,96 @@
|
||||
<span class="mistral-wrapper detail-screen">
|
||||
{% extends 'mistral/default/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Action Execution Details" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<h1>
|
||||
{% trans "Action Execution Details" %}
|
||||
</h1>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<div class="detail">
|
||||
<h4>{% trans "Overview" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ action_execution.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ action_execution.id }}</dd>
|
||||
{% if action_execution.description %}
|
||||
<dt>{% trans "Description" %}</dt>
|
||||
<dd>{{ action_execution.description }}</dd>
|
||||
{% endif %}
|
||||
<div class="line-space">
|
||||
<dt>{% trans "State" %}</dt>
|
||||
<dd>{{ action_execution.state }}</dd>
|
||||
</div>
|
||||
{% if action_execution.state_info %}
|
||||
<dt>{% trans "State Info" %}</dt>
|
||||
<dd>{{ action_execution.state_info }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Accepted" %}</dt>
|
||||
<dd>{{ action_execution.accepted }}</dd>
|
||||
<dt>{% trans "Tags" %}</dt>
|
||||
<dd>{{ action_execution.tags }}</dd>
|
||||
<br/>
|
||||
<dt>{% trans "Creation Date" %}</dt>
|
||||
<dd>{{ action_execution.created_at|parse_isotime}}</dd>
|
||||
<dt>{% trans "Time Since Created" %}</dt>
|
||||
<dd>{{ action_execution.created_at|parse_isotime|timesince }}</dd>
|
||||
<br/>
|
||||
<dt>{% trans "Update Date" %}</dt>
|
||||
<dd>{{ action_execution.updated_at|parse_isotime}}</dd>
|
||||
<dt>{% trans "Time Since Updated" %}</dt>
|
||||
<dd>{{ action_execution.updated_at|parse_isotime|timesince }}</dd>
|
||||
<br/>
|
||||
<dt>{% trans "Input" %}</dt>
|
||||
<dd>{{ action_execution.input }}</dd>
|
||||
<dt>{% trans "Output" %}</dt>
|
||||
<dd>{{ action_execution.output }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{% if action_execution.workflow_url %}
|
||||
<div class="detail">
|
||||
<h4>{% trans "Workflow Execution" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>
|
||||
<a href="{{ action_execution.workflow_url }}"
|
||||
title="{{action_execution.workflow_name}} {% trans "overview" %}">
|
||||
{{ action_execution.workflow_name }}
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if action_execution.task_execution_url %}
|
||||
<div class="detail">
|
||||
<h4>{% trans "Task Execution" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>
|
||||
<a href="{{ action_execution.task_execution_url }}"
|
||||
title="{{action_execution.task_name}} {% trans "overview" %}">
|
||||
{{ action_execution.task_name }}
|
||||
</a>
|
||||
</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>
|
||||
<a href="{{ action_execution.task_execution_url }}"
|
||||
title="{{ action_execution.task_execution_id }} {% trans "overview" %}">
|
||||
{{ action_execution.task_execution_id }}
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
</span>
|
@ -0,0 +1,13 @@
|
||||
{% 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:") %}
|
||||
<h3>
|
||||
{{ action_execution_id }}
|
||||
</h3>
|
||||
{% endblock page_header %}
|
@ -0,0 +1,9 @@
|
||||
{% 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 %}
|
@ -0,0 +1,11 @@
|
||||
{% 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 %}
|
35
mistraldashboard/action_executions/urls.py
Normal file
35
mistraldashboard/action_executions/urls.py
Normal file
@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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.conf.urls import url # noqa
|
||||
|
||||
from mistraldashboard.action_executions import views
|
||||
|
||||
ACTION_EXECUTIONS = r'^(?P<action_execution_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(ACTION_EXECUTIONS % 'detail', views.OverviewView.as_view(),
|
||||
name='detail'),
|
||||
url(ACTION_EXECUTIONS % 'input', views.CodeView.as_view(),
|
||||
{'column': 'input'}, name='input'),
|
||||
url(ACTION_EXECUTIONS % 'output', views.CodeView.as_view(),
|
||||
{'column': 'output'}, name='output'),
|
||||
url(ACTION_EXECUTIONS % 'update', views.UpdateView.as_view(),
|
||||
name='update'),
|
||||
url(ACTION_EXECUTIONS % 'task', views.FilteredByTaskView.as_view(),
|
||||
name='task')
|
||||
]
|
177
mistraldashboard/action_executions/views.py
Normal file
177
mistraldashboard/action_executions/views.py
Normal file
@ -0,0 +1,177 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views import generic
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
|
||||
from mistraldashboard.action_executions import forms as action_execution_forms
|
||||
from mistraldashboard.action_executions import tables as mistral_tables
|
||||
from mistraldashboard import api
|
||||
from mistraldashboard.default import utils
|
||||
from mistraldashboard import forms as mistral_forms
|
||||
|
||||
|
||||
def get_single_action_execution_data(request, **kwargs):
|
||||
try:
|
||||
action_execution_id = kwargs['action_execution_id']
|
||||
action_execution = api.action_execution_get(
|
||||
request,
|
||||
action_execution_id
|
||||
)
|
||||
except Exception:
|
||||
msg = _('Unable to get action execution "%s".') % action_execution_id
|
||||
redirect = reverse('horizon:mistral:action_execution:index')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
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)
|
||||
if action_execution.accepted:
|
||||
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):
|
||||
try:
|
||||
task_id = self.kwargs['action_execution_id']
|
||||
data = api.action_executions_list(self.request, task_id)
|
||||
except Exception:
|
||||
msg = (
|
||||
_('Unable to get action execution by task id "%s".') % task_id
|
||||
)
|
||||
redirect = reverse('horizon:mistral:action_executions:index')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
return data
|
@ -19,6 +19,7 @@ from horizon import tables
|
||||
from horizon.utils import filters
|
||||
|
||||
from mistraldashboard import api
|
||||
from mistraldashboard.default import utils
|
||||
|
||||
|
||||
class CreateAction(tables.LinkAction):
|
||||
@ -92,7 +93,8 @@ class ActionsTable(tables.DataTable):
|
||||
)
|
||||
is_system = tables.Column(
|
||||
"is_system",
|
||||
verbose_name=_("Is System")
|
||||
verbose_name=_("Is System"),
|
||||
filters=[utils.booleanfield]
|
||||
)
|
||||
tags = tables.Column(
|
||||
tags_to_string,
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
<span class="mistral-wrapper detail-screen">
|
||||
{% extends 'mistral/default/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Action Definition" %}{% endblock %}
|
||||
|
||||
@ -31,8 +32,7 @@
|
||||
<dd>{{ action.description }}</dd>
|
||||
<dt>{% trans "Definition" %}</dt>
|
||||
<dd><pre>{{ action.definition }}</pre></dd>
|
||||
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
</span>
|
||||
|
@ -24,6 +24,7 @@ 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):
|
||||
@ -112,6 +113,14 @@ class DetailView(generic.TemplateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
action = self.get_data(self.request, **kwargs)
|
||||
if action.is_system:
|
||||
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
|
||||
|
@ -330,6 +330,49 @@ def action_delete(request, 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)
|
||||
|
||||
|
||||
@handle_errors(_("Unable to update action execution"), [])
|
||||
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.
|
||||
|
@ -17,7 +17,7 @@
|
||||
</p>
|
||||
<p>
|
||||
{% trans "For more info" %}:
|
||||
<ul class="list">
|
||||
<ul class="mistral-wrapper list">
|
||||
<li>
|
||||
<a href="http://docs.openstack.org/developer/mistral/developer/webapi/v2.html#cron-triggers"
|
||||
title="{% trans "Mistral Cron Trigger" %}" target="_blank">
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
<span class="mistral-wrapper detail-screen">
|
||||
{% extends 'mistral/default/base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Cron Trigger Details" %}{% endblock %}
|
||||
@ -8,7 +9,7 @@
|
||||
{% trans "Cron Trigger Details" %}
|
||||
</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href={{ cron_trigger.list_url }} title="Cron Triggers">
|
||||
<li><a href="{{ cron_trigger.list_url }}" title="Cron Triggers">
|
||||
Cron Triggers
|
||||
</a>
|
||||
</li>
|
||||
@ -97,3 +98,4 @@
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</span>
|
@ -44,6 +44,12 @@ class OverviewView(generic.TemplateView):
|
||||
args=[cron_trigger.workflow_name]
|
||||
)
|
||||
cron_trigger.list_url = reverse_lazy(self.list_url)
|
||||
breadcrumb = [(cron_trigger.name, reverse(
|
||||
'horizon:mistral:cron_triggers:detail',
|
||||
args=[cron_trigger.name]
|
||||
))]
|
||||
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
context['cron_trigger'] = cron_trigger
|
||||
|
||||
return context
|
||||
|
@ -26,10 +26,11 @@ class MistralDashboard(horizon.Dashboard):
|
||||
'default',
|
||||
'workbooks',
|
||||
'workflows',
|
||||
'actions',
|
||||
'executions',
|
||||
'tasks',
|
||||
'actions',
|
||||
'cron_triggers'
|
||||
'action_executions',
|
||||
'cron_triggers',
|
||||
)
|
||||
default_panel = 'default'
|
||||
roles = ('admin',)
|
||||
|
@ -0,0 +1,4 @@
|
||||
<span class="boolfield">
|
||||
<i class="{{ type.color }} {{ type.icon }}" aria-hidden="true"></i>
|
||||
{{ bool }}
|
||||
</span>
|
@ -26,6 +26,17 @@ TYPES = {
|
||||
'RUNNING': 'label-info',
|
||||
}
|
||||
|
||||
BOOLEAN_FIELD = {
|
||||
'True': {
|
||||
'color': 'green',
|
||||
'icon': 'fa fa-check'
|
||||
},
|
||||
'False': {
|
||||
'color': 'red',
|
||||
'icon': 'fa fa-remove'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def label(x):
|
||||
return render_to_string("mistral/default/_label.html",
|
||||
@ -33,6 +44,15 @@ def label(x):
|
||||
"type": TYPES.get(x)})
|
||||
|
||||
|
||||
def booleanfield(x):
|
||||
# todo: check undefined instead of the if blocks in view
|
||||
# todo: check the red version
|
||||
|
||||
return render_to_string("mistral/default/_booleanfield.html",
|
||||
{"bool": str(x),
|
||||
"type": BOOLEAN_FIELD.get(str(x))})
|
||||
|
||||
|
||||
def humantime(x):
|
||||
return render_to_string("mistral/default/_humantime.html",
|
||||
{"datetime": iso8601.parse_date(x)})
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
<span class="mistral-wrapper detail-screen">
|
||||
{% extends 'mistral/default/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Execution Details" %}{% endblock %}
|
||||
|
||||
@ -22,7 +23,7 @@
|
||||
<dd>{{ execution.description }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "State" %}</dt>
|
||||
<dd>{{ execution.state }}</dd>
|
||||
<dd class="line-space">{{ execution.state }}</dd>
|
||||
{% if execution.state_info %}
|
||||
<dt>{% trans "State Info" %}</dt>
|
||||
<dd>{{ execution.state_info }}</dd>
|
||||
@ -66,4 +67,5 @@
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
</span>
|
@ -25,7 +25,7 @@ from horizon import forms
|
||||
from horizon import tables
|
||||
|
||||
from mistraldashboard import api
|
||||
from mistraldashboard.default.utils import prettyprint
|
||||
from mistraldashboard.default import utils
|
||||
from mistraldashboard.executions import forms as m_forms
|
||||
from mistraldashboard.executions import tables as mistral_tables
|
||||
from mistraldashboard import forms as mistral_forms
|
||||
@ -160,10 +160,18 @@ class DetailView(generic.TemplateView):
|
||||
|
||||
execution.workflow_url = reverse(self.workflow_url,
|
||||
args=[execution.workflow_name])
|
||||
execution.input = prettyprint(execution.input)
|
||||
execution.output = prettyprint(execution.output)
|
||||
execution.params = prettyprint(execution.params)
|
||||
execution.input = utils.prettyprint(execution.input)
|
||||
execution.output = utils.prettyprint(execution.output)
|
||||
execution.params = utils.prettyprint(execution.params)
|
||||
execution.state = utils.label(execution.state)
|
||||
task.url = reverse(self.task_url, args=[execution.id])
|
||||
|
||||
breadcrumb = [(execution.id, reverse(
|
||||
'horizon:mistral:executions:detail',
|
||||
args=[execution.id]
|
||||
))]
|
||||
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
context['execution'] = execution
|
||||
context['task'] = task
|
||||
|
||||
@ -189,10 +197,13 @@ class CodeView(forms.ModalFormView):
|
||||
io = {}
|
||||
if column == 'input':
|
||||
io['name'] = _('Input')
|
||||
io['value'] = execution.input = prettyprint(execution.input)
|
||||
io['value'] = execution.input = utils.prettyprint(execution.input)
|
||||
elif column == 'output':
|
||||
io['name'] = _('Output')
|
||||
io['value'] = execution.output = prettyprint(execution.output)
|
||||
io['value'] = execution.output = utils.prettyprint(
|
||||
execution.output
|
||||
)
|
||||
|
||||
context['io'] = io
|
||||
|
||||
return context
|
||||
|
@ -1,4 +1,4 @@
|
||||
.list{
|
||||
.mistral-wrapper.list{
|
||||
list-style: inherit;
|
||||
}
|
||||
|
||||
@ -9,3 +9,43 @@
|
||||
.mistral-wrapper #actions a.btn{
|
||||
width:initial;
|
||||
}
|
||||
|
||||
.mistral-wrapper.detail-screen .page-breadcrumb ol li{
|
||||
max-width: inherit;
|
||||
}
|
||||
|
||||
.mistral-wrapper.detail-screen .page-breadcrumb li:last-child{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.mistral-wrapper .navbar-brand{
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.boolfield{
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.boolfield i{
|
||||
padding-right: .2em;
|
||||
}
|
||||
|
||||
.boolfield i.green{
|
||||
color: green;
|
||||
}
|
||||
|
||||
.boolfield i.red{
|
||||
color: red;
|
||||
}
|
||||
|
||||
.line-space{
|
||||
margin: .3em 0;
|
||||
}
|
||||
|
||||
.line-space dd{
|
||||
display:inline-block;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -14,6 +14,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template.defaultfilters import title
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
@ -42,6 +44,21 @@ class UpdateRow(tables.Row):
|
||||
return instance
|
||||
|
||||
|
||||
class TypeColumn(tables.Column):
|
||||
def get_link_url(self, datum):
|
||||
obj_id = datum.id
|
||||
url = ""
|
||||
action_execution_url = "horizon:mistral:action_executions:task"
|
||||
if datum.type == "ACTION":
|
||||
url = action_execution_url
|
||||
# todo: add missing link to workflow execution
|
||||
# once available in python mistral client API
|
||||
# elif datum.type = "WORKFLOW":
|
||||
# url= "horizon:mistral:workflow:task"
|
||||
|
||||
return reverse(url, args=[obj_id])
|
||||
|
||||
|
||||
class TaskTable(tables.DataTable):
|
||||
|
||||
def getHoverHelp(data):
|
||||
@ -70,6 +87,12 @@ class TaskTable(tables.DataTable):
|
||||
verbose_name=_("Workflow Execution ID"),
|
||||
link="horizon:mistral:executions:detail_task_id"
|
||||
)
|
||||
type = TypeColumn(
|
||||
"type",
|
||||
verbose_name=_("Type"),
|
||||
filters=[title],
|
||||
link=True
|
||||
)
|
||||
result = tables.Column(
|
||||
"",
|
||||
verbose_name=_("Result"),
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
<span class="mistral-wrapper detail-screen">
|
||||
{% extends 'mistral/default/base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Task Details" %}{% endblock %}
|
||||
@ -18,12 +19,21 @@
|
||||
<dd>{{ task.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ task.id }}</dd>
|
||||
<dt>{% trans "Type" %}</dt>
|
||||
<dd>
|
||||
<a href="{{ task.type_url }}"
|
||||
title="{{task.type |title}} {% trans "list of the corresponding task" %}">
|
||||
{{ task.type |title }}
|
||||
</a>
|
||||
</dd>
|
||||
{% if task.state_info %}
|
||||
<dt>{% trans "State Info" %}</dt>
|
||||
<dd>{{ task.state_info }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "State" %}</dt>
|
||||
<dd>{{ task.state }}</dd>
|
||||
<div class="line-space">
|
||||
<dt>{% trans "State" %}</dt>
|
||||
<dd>{{ task.state }}</dd>
|
||||
</div>
|
||||
<br/>
|
||||
<dt>{% trans "Creation Date" %}</dt>
|
||||
<dd>{{ task.created_at|parse_isotime }}</dd>
|
||||
@ -63,3 +73,4 @@
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</span>
|
@ -25,7 +25,8 @@ from horizon import forms
|
||||
from horizon import tables
|
||||
|
||||
from mistraldashboard import api
|
||||
from mistraldashboard.default.utils import prettyprint
|
||||
from mistraldashboard.default import utils
|
||||
|
||||
from mistraldashboard import forms as mistral_forms
|
||||
from mistraldashboard.tasks import tables as mistral_tables
|
||||
|
||||
@ -63,6 +64,7 @@ class OverviewView(generic.TemplateView):
|
||||
page_title = _("Task Details")
|
||||
workflow_url = 'horizon:mistral:workflows:detail'
|
||||
execution_url = 'horizon:mistral:executions:detail'
|
||||
action_execution_url = 'horizon:mistral:action_executions:task'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(OverviewView, self).get_context_data(**kwargs)
|
||||
@ -71,8 +73,18 @@ class OverviewView(generic.TemplateView):
|
||||
args=[task.workflow_name])
|
||||
task.execution_url = reverse(self.execution_url,
|
||||
args=[task.workflow_execution_id])
|
||||
task.result = prettyprint(task.result)
|
||||
task.published = prettyprint(task.published)
|
||||
task.result = utils.prettyprint(task.result)
|
||||
task.published = utils.prettyprint(task.published)
|
||||
task.state = utils.label(task.state)
|
||||
if task.type and task.type == "ACTION":
|
||||
task.type_url = reverse(self.action_execution_url, args=[task.id])
|
||||
|
||||
breadcrumb = [(task.id, reverse(
|
||||
'horizon:mistral:tasks:detail',
|
||||
args=[task.id]
|
||||
))]
|
||||
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
context['task'] = task
|
||||
|
||||
return context
|
||||
@ -95,10 +107,10 @@ class CodeView(forms.ModalFormView):
|
||||
|
||||
if column == 'result':
|
||||
io['name'] = _('Result')
|
||||
io['value'] = task.result = prettyprint(task.result)
|
||||
io['value'] = task.result = utils.prettyprint(task.result)
|
||||
elif column == 'published':
|
||||
io['name'] = _('Published')
|
||||
io['value'] = task.published = prettyprint(task.published)
|
||||
io['value'] = task.published = utils.prettyprint(task.published)
|
||||
|
||||
context['io'] = io
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
<span class="mistral-wrapper detail-screen">
|
||||
{% extends 'mistral/default/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Workbook Definition" %}{% endblock %}
|
||||
|
||||
@ -11,3 +12,4 @@
|
||||
<pre>{{ definition }}</pre>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</span>
|
||||
|
@ -43,6 +43,12 @@ class DetailView(generic.TemplateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
workbook = self.get_data(self.request, **kwargs)
|
||||
breadcrumb = [(workbook.name, reverse(
|
||||
'horizon:mistral:workbooks:detail',
|
||||
args=[workbook.name]
|
||||
))]
|
||||
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
context['definition'] = workbook.definition
|
||||
|
||||
return context
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
<span class="mistral-wrapper detail-screen">
|
||||
{% extends 'mistral/default/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Workflow Definition" %}{% endblock %}
|
||||
|
||||
@ -11,3 +12,4 @@
|
||||
<pre>{{ definition }}</pre>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</span>
|
@ -43,10 +43,17 @@ class DetailView(generic.TemplateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
workflow = self.get_data(self.request, **kwargs)
|
||||
breadcrumb = [(workflow.name, reverse(
|
||||
'horizon:mistral:workflows:detail',
|
||||
args=[workflow.name]
|
||||
))]
|
||||
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
context['definition'] = (
|
||||
workflow.definition or
|
||||
'This workflow was created as part of workbook %s'
|
||||
% workflow.name.split('.')[0])
|
||||
|
||||
return context
|
||||
|
||||
def get_data(self, request, **kwargs):
|
||||
|
Loading…
Reference in New Issue
Block a user