Task List Panel
Main page allows viewing of all active, approved, completed and canceled tasks can click through to view full task details. Un-approved tasks can be resubmitted with new data, or approved, tasks can be cancelled. Change-Id: I35306fdf8e774d39b34c8c6fa50af5c70e010420
This commit is contained in:
parent
4feb401f9b
commit
113124e14b
|
@ -3,5 +3,6 @@ include setup.py
|
||||||
recursive-include stacktask_ui/content/default/templates *
|
recursive-include stacktask_ui/content/default/templates *
|
||||||
recursive-include stacktask_ui/content/forgotpassword/templates *
|
recursive-include stacktask_ui/content/forgotpassword/templates *
|
||||||
recursive-include stacktask_ui/content/project_users/templates *
|
recursive-include stacktask_ui/content/project_users/templates *
|
||||||
|
recursive-include stacktask_ui/content/tasks/templates *
|
||||||
recursive-include stacktask_ui/content/token/templates *
|
recursive-include stacktask_ui/content/token/templates *
|
||||||
recursive-include stacktask_ui/static *
|
recursive-include stacktask_ui/static *
|
||||||
|
|
|
@ -20,6 +20,8 @@ from six.moves.urllib.parse import urljoin
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from horizon.utils import functions as utils
|
||||||
|
|
||||||
from openstack_dashboard.api import base
|
from openstack_dashboard.api import base
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -29,6 +31,12 @@ USER = collections.namedtuple('User',
|
||||||
TOKEN = collections.namedtuple('Token',
|
TOKEN = collections.namedtuple('Token',
|
||||||
['action'])
|
['action'])
|
||||||
|
|
||||||
|
TASK = collections.namedtuple('Task',
|
||||||
|
['id', 'task_type', 'valid',
|
||||||
|
'request_by', 'request_project',
|
||||||
|
'created_on', 'approved_on', 'page',
|
||||||
|
'completed_on', 'actions', 'status'])
|
||||||
|
|
||||||
|
|
||||||
def _get_endpoint_url(request):
|
def _get_endpoint_url(request):
|
||||||
# If the request is made by an anonymous user, this endpoint request fails.
|
# If the request is made by an anonymous user, this endpoint request fails.
|
||||||
|
@ -230,3 +238,90 @@ def forgotpassword_submit(request, data):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(e)
|
LOG.error(e)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def task_list(request, filters={}, page=1):
|
||||||
|
tasks_per_page = utils.get_page_size(request)
|
||||||
|
tasklist = []
|
||||||
|
prev = more = False
|
||||||
|
try:
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
'X-Auth-Token': request.user.token.id}
|
||||||
|
params = {
|
||||||
|
"filters": json.dumps(filters),
|
||||||
|
"page": page,
|
||||||
|
"tasks_per_page": tasks_per_page
|
||||||
|
}
|
||||||
|
resp = get(request, "tasks", params=params, data=json.dumps({}),
|
||||||
|
headers=headers).json()
|
||||||
|
prev = resp['has_prev']
|
||||||
|
more = resp['has_more']
|
||||||
|
for task in resp['tasks']:
|
||||||
|
tasklist.append(task_obj_get(request, task=task, page=page))
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return tasklist, prev, more
|
||||||
|
|
||||||
|
|
||||||
|
def task_get(request, task_id):
|
||||||
|
# Get a single task
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
'X-Auth-Token': request.user.token.id}
|
||||||
|
|
||||||
|
return get(request, "tasks/%s" % task_id,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
def task_obj_get(request, task_id=None, task=None, page=0):
|
||||||
|
if not task:
|
||||||
|
task = task_get(request, task_id)
|
||||||
|
|
||||||
|
status = "Awaiting Approval"
|
||||||
|
if task['cancelled']:
|
||||||
|
status = "Cancelled"
|
||||||
|
elif task['completed_on']:
|
||||||
|
status = "Completed"
|
||||||
|
elif task['approved_on']:
|
||||||
|
status = "Approved; Incomplete"
|
||||||
|
|
||||||
|
valid = False not in [action['valid'] for
|
||||||
|
action in task['actions']]
|
||||||
|
return TASK(
|
||||||
|
id=task['uuid'],
|
||||||
|
task_type=task['task_type'],
|
||||||
|
valid=valid,
|
||||||
|
request_by=task['keystone_user'].get('username'),
|
||||||
|
request_project=task['keystone_user'].get('project_name'),
|
||||||
|
status=status,
|
||||||
|
created_on=task['created_on'],
|
||||||
|
approved_on=task['approved_on'],
|
||||||
|
completed_on=task['completed_on'],
|
||||||
|
actions=task['actions'],
|
||||||
|
page=page
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def task_cancel(request, task_id):
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
'X-Auth-Token': request.user.token.id}
|
||||||
|
|
||||||
|
return delete(request, "tasks/%s" % task_id,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
def task_approve(request, task_id):
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
'X-Auth-Token': request.user.token.id}
|
||||||
|
|
||||||
|
return post(request, "tasks/%s" % task_id,
|
||||||
|
data=json.dumps({"approved": True}), headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
def task_update(request, task_id, new_data):
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
'X-Auth-Token': request.user.token.id}
|
||||||
|
|
||||||
|
return put(request, "tasks/%s" % task_id,
|
||||||
|
data=new_data, headers=headers)
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Copyright (c) 2016 Catalyst IT 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 ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import messages
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from stacktask_ui.api import stacktask
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateTaskForm(forms.SelfHandlingForm):
|
||||||
|
task_id = forms.CharField(widget=forms.HiddenInput())
|
||||||
|
task_type = forms.CharField(widget=forms.TextInput(
|
||||||
|
attrs={'readonly': True}))
|
||||||
|
task_data = forms.CharField(widget=forms.Textarea)
|
||||||
|
|
||||||
|
def clean_task_data(self):
|
||||||
|
taskdata = self.cleaned_data['task_data']
|
||||||
|
try:
|
||||||
|
json.loads(taskdata)
|
||||||
|
except ValueError:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
"Invalid non-JSON data in Task Data field")
|
||||||
|
|
||||||
|
return taskdata
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
task_id = self.cleaned_data.pop('task_id')
|
||||||
|
try:
|
||||||
|
response = stacktask.task_update(
|
||||||
|
request, task_id, data['task_data'])
|
||||||
|
if response.status_code == 200:
|
||||||
|
messages.success(request, _('Updated task successfully.'))
|
||||||
|
elif response.status_code == 400:
|
||||||
|
messages.error(request, _(response.text))
|
||||||
|
else:
|
||||||
|
messages.error(request, _('Failed to update task.'))
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
messages.error(request, _('Failed to update task.'))
|
||||||
|
return False
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Copyright (c) 2016 Catalyst IT 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 ugettext_lazy as _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
|
||||||
|
class TaskList(horizon.Panel):
|
||||||
|
name = _('Tasks')
|
||||||
|
slug = 'tasks'
|
||||||
|
policy_rules = (("identity", "role:admin"),)
|
|
@ -0,0 +1,184 @@
|
||||||
|
# Copyright (c) 2016 Catalyst IT 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 ugettext_lazy as _
|
||||||
|
from django.utils.translation import ungettext_lazy
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
from stacktask_ui.api import stacktask
|
||||||
|
|
||||||
|
|
||||||
|
class CancelTask(tables.DeleteAction):
|
||||||
|
help_text = _("This will cancel all selected tasks.")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_present(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Cancel Task",
|
||||||
|
u"Cancel Tasks",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_past(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Cancelled Task",
|
||||||
|
u"Cancelled Tasks",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self, request, obj_id):
|
||||||
|
result = stacktask.task_cancel(request, obj_id)
|
||||||
|
if not result or result.status_code != 200:
|
||||||
|
exception = exceptions.NotAvailable()
|
||||||
|
exception._safe_message = False
|
||||||
|
raise exception
|
||||||
|
|
||||||
|
def allowed(self, request, task=None):
|
||||||
|
if task:
|
||||||
|
return not(
|
||||||
|
task.status == "Completed" or task.status == "Cancelled")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class ApproveTask(tables.BatchAction):
|
||||||
|
name = "approve"
|
||||||
|
help_text = _("This will approve all of the selected tasks.")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_present(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Approve Task",
|
||||||
|
u"Approve Tasks",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_past(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Approved Task",
|
||||||
|
u"Approved Tasks",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
def action(self, request, obj_id):
|
||||||
|
result = stacktask.task_approve(request, obj_id)
|
||||||
|
if not result or result.status_code != 200:
|
||||||
|
exception = exceptions.NotAvailable()
|
||||||
|
exception._safe_message = False
|
||||||
|
raise exception
|
||||||
|
|
||||||
|
def allowed(self, request, task=None):
|
||||||
|
if task:
|
||||||
|
return not(
|
||||||
|
task.status == "Completed" or task.status == "Cancelled")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class ReapproveTask(ApproveTask):
|
||||||
|
name = "approve"
|
||||||
|
help_text = _("This will approve all of the selected tasks.")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_present(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Reapprove Task",
|
||||||
|
u"Repprove Tasks",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_past(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Reapproved Task",
|
||||||
|
u"Reapproved Tasks",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateTask(tables.LinkAction):
|
||||||
|
name = "update"
|
||||||
|
verbose_name = _("Update Task")
|
||||||
|
url = "horizon:management:tasks:update"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
|
||||||
|
def allowed(self, request, task=None):
|
||||||
|
if task:
|
||||||
|
return task.status == 'Awaiting Approval'
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def TaskTypeDisplayFilter(task_type):
|
||||||
|
return task_type.replace("_", " ").title()
|
||||||
|
|
||||||
|
|
||||||
|
class TaskTable(tables.DataTable):
|
||||||
|
uuid = tables.Column('id', verbose_name=_('Task ID'),
|
||||||
|
hidden=True)
|
||||||
|
task_type = tables.Column('task_type', verbose_name=_('Task Type'),
|
||||||
|
filters=[TaskTypeDisplayFilter],
|
||||||
|
link="horizon:management:tasks:detail")
|
||||||
|
status = tables.Column('status', verbose_name=_('Status'))
|
||||||
|
request_by = tables.Column('request_by', verbose_name=_('Requestee'))
|
||||||
|
request_project = tables.Column('request_project',
|
||||||
|
verbose_name=_('Request Project'))
|
||||||
|
valid = tables.Column('valid', verbose_name=_("Actions Valid"))
|
||||||
|
created_on = tables.Column('created_on',
|
||||||
|
verbose_name=_('Request Date'))
|
||||||
|
page = tables.Column('page', hidden=True)
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = 'task_table'
|
||||||
|
verbose_name = _('Tasks')
|
||||||
|
table_actions = (CancelTask, ApproveTask)
|
||||||
|
row_actions = (ApproveTask, UpdateTask, CancelTask)
|
||||||
|
prev_pagination_param = pagination_param = 'task_page'
|
||||||
|
|
||||||
|
def get_prev_marker(self):
|
||||||
|
return str(int(self.data[0].page) - 1) if self.data else ''
|
||||||
|
|
||||||
|
def get_marker(self):
|
||||||
|
return str(int(self.data[0].page) + 1) if self.data else ''
|
||||||
|
|
||||||
|
def get_object_display(self, obj):
|
||||||
|
task_type = obj.task_type.replace("_", " ").title()
|
||||||
|
return "%s (%s)" % (task_type, obj.id)
|
||||||
|
|
||||||
|
|
||||||
|
class ApprovedTaskTable(TaskTable):
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = 'approved_table'
|
||||||
|
verbose_name = _('Tasks')
|
||||||
|
table_actions = (CancelTask, ReapproveTask)
|
||||||
|
row_actions = (CancelTask, ReapproveTask)
|
||||||
|
prev_pagination_param = pagination_param = 'approved_page'
|
||||||
|
|
||||||
|
|
||||||
|
class CompletedTaskTable(TaskTable):
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = 'completed_table'
|
||||||
|
verbose_name = _('Tasks')
|
||||||
|
prev_pagination_param = pagination_param = 'completed_page'
|
||||||
|
|
||||||
|
|
||||||
|
class CancelledTaskTable(TaskTable):
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = 'cancelled_table'
|
||||||
|
verbose_name = _('Tasks')
|
||||||
|
prev_pagination_param = pagination_param = 'cancelled_page'
|
|
@ -0,0 +1,134 @@
|
||||||
|
# Copyright (c) 2016 Catalyst IT 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 ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import tabs
|
||||||
|
|
||||||
|
from stacktask_ui.content.tasks import tables as task_tables
|
||||||
|
from stacktask_ui.api import stacktask
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveTaskListTab(tabs.TableTab):
|
||||||
|
table_classes = (task_tables.TaskTable,)
|
||||||
|
template_name = 'horizon/common/_detail_table.html'
|
||||||
|
page_title = _("Active Tasks")
|
||||||
|
name = _('Active')
|
||||||
|
slug = 'active'
|
||||||
|
filters = {'cancelled': {'exact': False},
|
||||||
|
'approved': {'exact': False}}
|
||||||
|
_prev = False
|
||||||
|
_more = False
|
||||||
|
|
||||||
|
def get_task_table_data(self):
|
||||||
|
tasks = []
|
||||||
|
marker = self.request.GET.get(
|
||||||
|
self.table_classes[0]._meta.pagination_param, 1)
|
||||||
|
try:
|
||||||
|
tasks, self._prev, self._more = stacktask.task_list(
|
||||||
|
self.request, filters=self.filters, page=marker)
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(self.request, _('Failed to list tasks.'))
|
||||||
|
return tasks
|
||||||
|
|
||||||
|
def has_prev_data(self, table):
|
||||||
|
return self._prev
|
||||||
|
|
||||||
|
def has_more_data(self, table):
|
||||||
|
return self._more
|
||||||
|
|
||||||
|
|
||||||
|
class ApprovedTaskListTab(ActiveTaskListTab):
|
||||||
|
table_classes = (task_tables.ApprovedTaskTable,)
|
||||||
|
page_title = _("Approved Tasks")
|
||||||
|
name = _('Approved')
|
||||||
|
slug = 'approved'
|
||||||
|
filters = {'cancelled': {'exact': False},
|
||||||
|
'approved': {'exact': True},
|
||||||
|
'completed': {'exact': False}}
|
||||||
|
|
||||||
|
def get_approved_table_data(self):
|
||||||
|
return super(ApprovedTaskListTab, self).get_task_table_data()
|
||||||
|
|
||||||
|
|
||||||
|
class CompletedTaskListTab(ActiveTaskListTab):
|
||||||
|
table_classes = (task_tables.CompletedTaskTable,)
|
||||||
|
page_title = _("Completed Tasks")
|
||||||
|
name = _('Completed')
|
||||||
|
slug = 'completed'
|
||||||
|
filters = {'completed': {'exact': True}}
|
||||||
|
|
||||||
|
def get_completed_table_data(self):
|
||||||
|
return super(CompletedTaskListTab, self).get_task_table_data()
|
||||||
|
|
||||||
|
|
||||||
|
class CancelledTaskListTab(ActiveTaskListTab):
|
||||||
|
table_classes = (task_tables.CancelledTaskTable,)
|
||||||
|
name = _('Cancelled')
|
||||||
|
slug = 'cancelled'
|
||||||
|
filters = {'cancelled': {'exact': True}}
|
||||||
|
|
||||||
|
def get_cancelled_table_data(self):
|
||||||
|
return super(CancelledTaskListTab, self).get_task_table_data()
|
||||||
|
|
||||||
|
|
||||||
|
class TaskTabs(tabs.TabGroup):
|
||||||
|
slug = "tasks"
|
||||||
|
tabs = (ActiveTaskListTab, ApprovedTaskListTab, CompletedTaskListTab,
|
||||||
|
CancelledTaskListTab)
|
||||||
|
sticky = True
|
||||||
|
|
||||||
|
def get_selected_tab(self):
|
||||||
|
super(TaskTabs, self).get_selected_tab()
|
||||||
|
if not self._selected:
|
||||||
|
for tab in self.tabs:
|
||||||
|
param = tab.table_classes[0]._meta.pagination_param
|
||||||
|
if self.request.GET.get(param):
|
||||||
|
self._selected = self.get_tab(tab.slug)
|
||||||
|
return self._selected
|
||||||
|
|
||||||
|
|
||||||
|
class TaskOverviewTab(tabs.Tab):
|
||||||
|
name = _("Overview")
|
||||||
|
slug = "overview"
|
||||||
|
template_name = 'management/tasks/_task_detail_overview.html'
|
||||||
|
|
||||||
|
def get_context_data(self, request):
|
||||||
|
print self.tab_group
|
||||||
|
print self.tab_group.kwargs
|
||||||
|
return {"task": self.tab_group.kwargs['task']}
|
||||||
|
|
||||||
|
|
||||||
|
class TaskActionsTab(tabs.Tab):
|
||||||
|
name = _("Actions")
|
||||||
|
slug = "actions"
|
||||||
|
template_name = 'management/tasks/_task_detail_actions.html'
|
||||||
|
|
||||||
|
def get_context_data(self, request):
|
||||||
|
return {"task": self.tab_group.kwargs['task']}
|
||||||
|
|
||||||
|
|
||||||
|
class TaskNotesTab(tabs.Tab):
|
||||||
|
name = _("Action Notes")
|
||||||
|
slug = "notes"
|
||||||
|
template_name = 'management/tasks/_task_detail_notes.html'
|
||||||
|
|
||||||
|
def get_context_data(self, request):
|
||||||
|
return {"task": self.tab_group.kwargs['task']}
|
||||||
|
|
||||||
|
|
||||||
|
class TaskDetailTabs(tabs.DetailTabsGroup):
|
||||||
|
slug = "task_details"
|
||||||
|
tabs = (TaskOverviewTab, TaskActionsTab, TaskNotesTab)
|
|
@ -0,0 +1,17 @@
|
||||||
|
{% load i18n sizeformat %}
|
||||||
|
{% load task_filters %}
|
||||||
|
|
||||||
|
<div class="detail">
|
||||||
|
<h4>{% trans "Actions" %}</h4>
|
||||||
|
<hr class="header_rule">
|
||||||
|
{% for action in task.actions %}
|
||||||
|
<h5>{{ action.action_name }}</h5>
|
||||||
|
<hr class="header_rule">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>{% trans "Valid" %}</dt>
|
||||||
|
<dd>{{ action.valid }}</dd>
|
||||||
|
<dt>{% trans "Data" %}</dt>
|
||||||
|
<dd><pre>{{ action.data|pretty_json }}</pre></dd>
|
||||||
|
</dl>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% load i18n sizeformat %}
|
||||||
|
{% load task_filters %}
|
||||||
|
|
||||||
|
<div class="detail">
|
||||||
|
<h4>{% trans "Action Notes" %}</h4>
|
||||||
|
<hr class="header_rule">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
{% for action, notes in task.action_notes.iteritems %}
|
||||||
|
<dt>{{ action }}</dt>
|
||||||
|
{% for note in notes %}
|
||||||
|
<dd>{{ note }}</dd>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
|
@ -0,0 +1,45 @@
|
||||||
|
{% load i18n sizeformat %}
|
||||||
|
{% load task_filters %}
|
||||||
|
|
||||||
|
<div class="detail">
|
||||||
|
<h4>{% trans "Task Details" %}</h4>
|
||||||
|
<hr class="header_rule">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>{% trans "Task Type" %}</dt>
|
||||||
|
<dd>{{ task.task_type }}</dd>
|
||||||
|
<dt>{% trans "ID" %}</dt>
|
||||||
|
<dd>{{ task.uuid }}</dd>
|
||||||
|
<dt>{% trans "Cancelled" %}</dt>
|
||||||
|
<dd>{{ task.cancelled }}</dd>
|
||||||
|
<dt>{% trans "Approved" %}</dt>
|
||||||
|
<dd>{{ task.approved }}</dd>
|
||||||
|
<dt>{% trans "Completed" %}</dt>
|
||||||
|
<dd>{{ task.completed }}</dd>
|
||||||
|
{% if task.approved_by %}
|
||||||
|
<dt>{% trans "Approved By" %}</dt>
|
||||||
|
<dd>{{ task.approved_by.username }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
<dt>{% trans "Requested on" %}</dt>
|
||||||
|
<dd>{{ task.created_on }}</dd>
|
||||||
|
<dt>{% trans "Approved on" %}</dt>
|
||||||
|
<dd>{% if task.approved_on %}{{ task.approved_on}}{% else %}-{% endif %}</dd>
|
||||||
|
<dt>{% trans "Completed on" %}</dt>
|
||||||
|
<dd>{% if task.completed_on %}{{ task.completed_on}}{% else %}-{% endif %}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
|
||||||
|
{% if task.keystone_user %}
|
||||||
|
<h4>{% trans "Requestee" %}</h4>
|
||||||
|
<hr class="header_rule">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>{% trans "Username" %}</dt>
|
||||||
|
<dd>{{ task.keystone_user.username }}</dd>
|
||||||
|
<dt>{% trans "Project" %}</dt>
|
||||||
|
<dd>{{ task.keystone_user.project_name }}</dd>
|
||||||
|
<dt>{% trans "Roles" %}</dt>
|
||||||
|
<dd>{{ task.keystone_user.roles|pretty_list }}</dd>
|
||||||
|
<dt>{% trans "IP Address" %}</dt>
|
||||||
|
<dd>{{ task.ip_address }}</dd>
|
||||||
|
</dl>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal_id %}update_user_form{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
{% include "horizon/common/_form_fields.html" %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Tasks" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{{ tab_group.render }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Update User" %}{% endblock %}
|
||||||
|
{% block main %}
|
||||||
|
{% include 'management/tasks/_update.html' %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,18 @@
|
||||||
|
from django import template
|
||||||
|
import json
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def pretty_json(value):
|
||||||
|
return json.dumps(value, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def pretty_list(value):
|
||||||
|
return ', '.join(value)
|
||||||
|
|
||||||
|
|
||||||
|
register.filter('pretty_json', pretty_json)
|
||||||
|
register.filter('pretty_list', pretty_list)
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Copyright (c) 2016 Catalyst IT 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.conf.urls import patterns
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from stacktask_ui.content.tasks import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns(
|
||||||
|
'',
|
||||||
|
url(r'^(?P<task_id>[^/]+)/$',
|
||||||
|
views.TaskDetailView.as_view(),
|
||||||
|
name='detail'),
|
||||||
|
url(r'^(?P<task_id>[^/]+)/update/$',
|
||||||
|
views.UpdateTaskView.as_view(), name='update'),
|
||||||
|
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||||
|
)
|
|
@ -0,0 +1,108 @@
|
||||||
|
# Copyright (c) 2016 Catalyst IT 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.core.urlresolvers import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import tabs
|
||||||
|
|
||||||
|
from horizon.utils import memoized
|
||||||
|
|
||||||
|
from stacktask_ui.content.tasks import tables as task_tables
|
||||||
|
from stacktask_ui.content.tasks import forms as task_forms
|
||||||
|
from stacktask_ui.content.tasks import tabs as task_tabs
|
||||||
|
from stacktask_ui.api import stacktask
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(tabs.TabbedTableView):
|
||||||
|
tab_group_class = task_tabs.TaskTabs
|
||||||
|
template_name = 'management/tasks/index.html'
|
||||||
|
redirect_url = 'horizon:management:tasks:index'
|
||||||
|
page_title = _("Tasks")
|
||||||
|
|
||||||
|
|
||||||
|
class TaskDetailView(tabs.TabView):
|
||||||
|
tab_group_class = task_tabs.TaskDetailTabs
|
||||||
|
template_name = 'horizon/common/_detail.html'
|
||||||
|
redirect_url = 'horizon:management:tasks:index'
|
||||||
|
page_title = "{{ task.task_type }}"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(TaskDetailView, self).get_context_data(**kwargs)
|
||||||
|
task = self.get_data()
|
||||||
|
context["task"] = task
|
||||||
|
context["url"] = reverse(self.redirect_url)
|
||||||
|
context["actions"] = self._get_actions(
|
||||||
|
stacktask.task_obj_get(self.request, task=task))
|
||||||
|
return context
|
||||||
|
|
||||||
|
def _get_actions(self, task):
|
||||||
|
table = task_tables.TaskTable(self.request)
|
||||||
|
return table.render_row_actions(task)
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_data(self):
|
||||||
|
return stacktask.task_get(self.request, self.kwargs['task_id']).json()
|
||||||
|
|
||||||
|
def get_tabs(self, request, *args, **kwargs):
|
||||||
|
task = self.get_data()
|
||||||
|
return self.tab_group_class(request, task=task, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateTaskView(forms.ModalFormView):
|
||||||
|
form_class = task_forms.UpdateTaskForm
|
||||||
|
form_id = "update_user_form"
|
||||||
|
modal_header = _("Update Task")
|
||||||
|
submit_label = _("Update")
|
||||||
|
submit_url = 'horizon:management:tasks:update'
|
||||||
|
template_name = 'management/tasks/update.html'
|
||||||
|
context_object_name = 'project_users'
|
||||||
|
success_url = "horizon:management:tasks:detail"
|
||||||
|
page_title = _("Update Task")
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(self.success_url,
|
||||||
|
args=(self.kwargs['task_id'],))
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_object(self):
|
||||||
|
try:
|
||||||
|
return stacktask.task_get(self.request,
|
||||||
|
self.kwargs['task_id']).json()
|
||||||
|
except Exception:
|
||||||
|
msg = _('Unable to retrieve user.')
|
||||||
|
url = reverse('horizon:management:tasks:index')
|
||||||
|
exceptions.handle(self.request, msg, redirect=url)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(UpdateTaskView, self).get_context_data(**kwargs)
|
||||||
|
context['task'] = self.get_object()
|
||||||
|
args = (self.kwargs['task_id'],)
|
||||||
|
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
task = self.get_object()
|
||||||
|
task_data = {}
|
||||||
|
for data in [action['data'] for action in task['actions']]:
|
||||||
|
task_data.update(data)
|
||||||
|
data = {'task_id': self.kwargs['task_id'],
|
||||||
|
'task_type': task['task_type'],
|
||||||
|
'task_data': json.dumps(task_data, indent=4),
|
||||||
|
}
|
||||||
|
return data
|
|
@ -0,0 +1,13 @@
|
||||||
|
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||||
|
PANEL = 'tasks'
|
||||||
|
# The slug of the dashboard the PANEL associated with. Required.
|
||||||
|
PANEL_DASHBOARD = 'management'
|
||||||
|
# The slug of the panel group the PANEL is associated with.
|
||||||
|
PANEL_GROUP = 'default'
|
||||||
|
|
||||||
|
# Python panel class of the PANEL to be added.
|
||||||
|
ADD_PANEL = 'stacktask_ui.content.tasks.panel.TaskList'
|
||||||
|
|
||||||
|
ADD_INSTALLED_APPS = [
|
||||||
|
'stacktask_ui.content.tasks'
|
||||||
|
]
|
Loading…
Reference in New Issue