Remove empty tabs in freezer dashboard

remove empty tab for job edition
remove possibility to add job without interval if start and end date
are provided
show a message when a job is started/stoped

Resolves bug: 1523497

Change-Id: Iff39fc9e813969672eecd48d9084fd100bcf611c
This commit is contained in:
memo 2015-12-07 13:26:50 +00:00
parent f6d896edbf
commit b48b27fb8c
12 changed files with 282 additions and 57 deletions

View File

@ -44,7 +44,7 @@ symbols=no
# --enable=similarities". If you want to run only the classes checker, but have # --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes # no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W" # --disable=W"
disable=E1002,W,C,R,method-hidden disable=E1002,W,C,R,method-hidden,import-error

View File

@ -208,7 +208,6 @@ class ActionConfigurationAction(workflows.Action):
def populate_action_choices(self, request, context): def populate_action_choices(self, request, context):
return [ return [
('', _("Select an action")),
('backup', _("Backup")), ('backup', _("Backup")),
('restore', _("Restore")), ('restore', _("Restore")),
('admin', _("Admin")), ('admin', _("Admin")),

View File

@ -335,7 +335,8 @@ class Action(object):
action['freezer_action'].get('backup_name'), action['freezer_action'].get('backup_name'),
action['freezer_action'].get('path_to_backup') action['freezer_action'].get('path_to_backup')
or action['freezer_action'].get('restore_abs_path'), or action['freezer_action'].get('restore_abs_path'),
action['freezer_action'].get('storage') action['freezer_action'].get('storage'),
mode=action['freezer_action'].get('mode')
) for action in actions] ) for action in actions]
def get(self, job_id, json=False): def get(self, job_id, json=False):

View File

@ -19,6 +19,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy from django.utils.translation import ungettext_lazy
from horizon import tables from horizon import tables
from horizon import messages
from horizon.utils.urlresolvers import reverse from horizon.utils.urlresolvers import reverse
import disaster_recovery.api.api as freezer_api import disaster_recovery.api.api as freezer_api
@ -93,7 +94,7 @@ class EditJob(tables.LinkAction):
icon = "pencil" icon = "pencil"
def get_link_url(self, datum=None): def get_link_url(self, datum=None):
return reverse("horizon:disaster_recovery:jobs:configure", return reverse("horizon:disaster_recovery:jobs:edit_job",
kwargs={'job_id': datum.job_id}) kwargs={'job_id': datum.job_id})
@ -114,6 +115,7 @@ class StartJob(tables.Action):
def single(self, table, request, job_id): def single(self, table, request, job_id):
freezer_api.Job(request).start(job_id) freezer_api.Job(request).start(job_id)
messages.success(request, _("Job has started"))
return shortcuts.redirect('horizon:disaster_recovery:jobs:index') return shortcuts.redirect('horizon:disaster_recovery:jobs:index')
@ -123,6 +125,7 @@ class StopJob(tables.Action):
def single(self, table, request, job_id): def single(self, table, request, job_id):
freezer_api.Job(request).stop(job_id) freezer_api.Job(request).stop(job_id)
messages.success(request, _("Job has stopped"))
return shortcuts.redirect('horizon:disaster_recovery:jobs:index') return shortcuts.redirect('horizon:disaster_recovery:jobs:index')

View File

@ -10,6 +10,12 @@
} }
</style> </style>
<div class="row">
<div class="col-sm-12">
<p>Drag actions in the order they will be executed on the client.</p>
</div>
</div>
<div class="sortable_lists row"> <div class="sortable_lists row">
<div class="col-md-6"> <div class="col-md-6">

View File

@ -33,6 +33,10 @@ urlpatterns = patterns(
views.JobWorkflowView.as_view(), views.JobWorkflowView.as_view(),
name='configure'), name='configure'),
url(r'^edit/(?P<job_id>[^/]+)?$',
views.EditJobWorkflowView.as_view(),
name='edit_job'),
url(r'^edit_actions/(?P<job_id>[^/]+)?$', url(r'^edit_actions/(?P<job_id>[^/]+)?$',
views.ActionsInJobView.as_view(), views.ActionsInJobView.as_view(),
name='edit_action'), name='edit_action'),

View File

@ -16,7 +16,8 @@ from horizon import browsers
from horizon import workflows from horizon import workflows
import workflows.create as configure_workflow import workflows.create as configure_workflow
import workflows.actions as actions_workflow import workflows.update_job as update_job_workflow
import workflows.update_actions as update_actions_workflow
import disaster_recovery.api.api as freezer_api import disaster_recovery.api.api as freezer_api
import disaster_recovery.jobs.browsers as project_browsers import disaster_recovery.jobs.browsers as project_browsers
@ -65,8 +66,31 @@ class JobWorkflowView(workflows.WorkflowView):
return initial return initial
class EditJobWorkflowView(workflows.WorkflowView):
workflow_class = update_job_workflow.UpdateJob
@shield("Unable to get job", redirect="jobs:index")
def get_object(self):
return freezer_api.Job(self.request).get(self.kwargs['job_id'])
def is_update(self):
return 'job_id' in self.kwargs and bool(self.kwargs['job_id'])
@shield("Unable to get job", redirect="jobs:index")
def get_initial(self):
initial = super(EditJobWorkflowView, self).get_initial()
if self.is_update():
initial.update({'job_id': None})
job = freezer_api.Job(self.request).get(self.kwargs['job_id'],
json=True)
initial.update(**job)
initial.update(**job['job_schedule'])
return initial
class ActionsInJobView(workflows.WorkflowView): class ActionsInJobView(workflows.WorkflowView):
workflow_class = actions_workflow.ConfigureActions workflow_class = update_actions_workflow.UpdateActions
@shield("Unable to get job", redirect="jobs:index") @shield("Unable to get job", redirect="jobs:index")
def get_object(self): def get_object(self):

View File

@ -141,12 +141,24 @@ class InfoConfigurationAction(workflows.Action):
return False return False
def _check_start_datetime(self, cleaned_data): def _check_start_datetime(self, cleaned_data):
if cleaned_data.get('start_datetime') and not \ if cleaned_data.get('schedule_start_date') and not \
self._validate_iso_format( self._validate_iso_format(
cleaned_data.get('schedule_start_date')): cleaned_data.get('schedule_start_date')):
msg = _("Start date time is not in ISO format.") msg = _("Start date time is not in ISO format.")
self._errors['schedule_start_date'] = self.error_class([msg]) self._errors['schedule_start_date'] = self.error_class([msg])
if (cleaned_data.get('schedule_start_date') and
cleaned_data.get('schedule_end_date')) and\
not cleaned_data.get('schedule_interval'):
msg = _("Please provide this value.")
self._errors['schedule_interval'] = self.error_class([msg])
if (cleaned_data.get('schedule_end_date') and
not cleaned_data.get('schedule_start_date')) and\
not cleaned_data.get('schedule_interval'):
msg = _("Please provide this value.")
self._errors['schedule_start_date'] = self.error_class([msg])
def _check_end_datetime(self, cleaned_data): def _check_end_datetime(self, cleaned_data):
if cleaned_data.get('end_datetime') and not \ if cleaned_data.get('end_datetime') and not \
self._validate_iso_format( self._validate_iso_format(

View File

@ -0,0 +1,65 @@
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
#
# 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 logging
from django import shortcuts
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import workflows
import disaster_recovery.api.api as freezer_api
LOG = logging.getLogger(__name__)
class ActionsConfigurationAction(workflows.Action):
actions = forms.CharField(
required=False)
job_id = forms.CharField(
required=False)
class Meta(object):
name = _("Actions")
slug = "actions"
help_text_template = "disaster_recovery/jobs" \
"/_actions.html"
class ActionsConfiguration(workflows.Step):
action_class = ActionsConfigurationAction
contributes = ('actions', 'job_id')
class UpdateActions(workflows.Workflow):
slug = "update_actions"
name = _("Update Actions")
finalize_button_name = _("Save")
success_message = _('Actions updated correctly.')
failure_message = _('Unable to update actions.')
success_url = "horizon:disaster_recovery:jobs:index"
default_steps = (ActionsConfiguration,)
def handle(self, request, context):
try:
if context['job_id'] != '':
freezer_api.Job(request).update(context['job_id'], context)
return shortcuts.redirect('horizon:disaster_recovery:jobs:index')
except Exception:
exceptions.handle(request)
return False

View File

@ -0,0 +1,122 @@
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
#
# 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 logging
import datetime
from django import shortcuts
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import workflows
import disaster_recovery.api.api as freezer_api
LOG = logging.getLogger(__name__)
class InfoConfigurationAction(workflows.Action):
description = forms.CharField(
label=_("Job Name"),
help_text=_("Set a name for this job"),
required=True)
job_id = forms.CharField(
widget=forms.HiddenInput(),
required=False)
schedule_start_date = forms.CharField(
label=_("Start Date and Time"),
required=False)
schedule_interval = forms.CharField(
label=_("Interval"),
required=False,
help_text=_("Repeat this configuration in a minutes interval."))
schedule_end_date = forms.CharField(
label=_("End Date and Time"),
required=False)
def __init__(self, request, context, *args, **kwargs):
self.request = request
self.context = context
super(InfoConfigurationAction, self).__init__(
request, context, *args, **kwargs)
def clean(self):
cleaned_data = super(InfoConfigurationAction, self).clean()
self._check_start_datetime(cleaned_data)
self._check_end_datetime(cleaned_data)
return cleaned_data
def _validate_iso_format(self, start_date):
try:
return datetime.datetime.strptime(
start_date, "%Y-%m-%dT%H:%M:%S")
except ValueError:
return False
def _check_start_datetime(self, cleaned_data):
if cleaned_data.get('start_datetime') and not \
self._validate_iso_format(
cleaned_data.get('schedule_start_date')):
msg = _("Start date time is not in ISO format.")
self._errors['schedule_start_date'] = self.error_class([msg])
def _check_end_datetime(self, cleaned_data):
if cleaned_data.get('end_datetime') and not \
self._validate_iso_format(
cleaned_data.get('schedule_end_date')):
msg = _("End date time is not in ISO format.")
self._errors['schedule_end_date'] = self.error_class([msg])
class Meta(object):
name = _("Job Info")
slug = "info"
help_text_template = "disaster_recovery/jobs" \
"/_scheduling.html"
class InfoConfiguration(workflows.Step):
action_class = InfoConfigurationAction
contributes = ('description',
'job_id',
'actions',
'schedule_start_date',
'schedule_interval',
'schedule_end_date')
class UpdateJob(workflows.Workflow):
slug = "update_job"
name = _("Update Job")
finalize_button_name = _("Save")
success_message = _('Job created correctly.')
failure_message = _('Unable to created job.')
success_url = "horizon:disaster_recovery:jobs:index"
default_steps = (InfoConfiguration,)
def handle(self, request, context):
try:
if context['job_id'] != '':
freezer_api.Job(request).update(context['job_id'], context)
return shortcuts.redirect('horizon:disaster_recovery:jobs:index')
except Exception:
exceptions.handle(request)
return False

View File

@ -75,31 +75,33 @@ function showRestoreOptions() {
$("#id_storage").closest(".form-group").show(); $("#id_storage").closest(".form-group").show();
} }
function showNovaOptions() {
$("#id_mode").closest(".form-group").show();
$("#id_nova_inst_id").closest(".form-group").show();
$("#id_backup_name").closest(".form-group").show();
$("#id_container").closest(".form-group").show();
}
function showCinderOptions() {
$("#id_mode").closest(".form-group").show();
$("#id_cinder_vol_id").closest(".form-group").show();
$("#id_backup_name").closest(".form-group").show();
$("#id_container").closest(".form-group").show();
}
function showSSHOptions() { function showSSHOptions() {
$("#id_ssh_key").closest(".form-group").show(); $("#id_ssh_key").closest(".form-group").show();
$("#id_ssh_username").closest(".form-group").show(); $("#id_ssh_username").closest(".form-group").show();
$("#id_ssh_host").closest(".form-group").show(); $("#id_ssh_host").closest(".form-group").show();
} }
hideEverything(); function triggerChanges() {
$("#id_action").trigger('change');
$("#id_mode").trigger('change');
$("#id_storage").trigger('change');
}
function hideModeOptions() {
$("#id_cinder_vol_id").closest(".form-group").hide();
$("#id_nova_inst_id").closest(".form-group").hide();
$("#id_mysql_conf").closest(".form-group").hide();
$("#id_sql_server_conf").closest(".form-group").hide();
}
function hideSSHOptions() {
$("#id_ssh_key").closest(".form-group").hide();
$("#id_ssh_username").closest(".form-group").hide();
$("#id_ssh_host").closest(".form-group").hide();
}
$("#id_action").change(function () { $("#id_action").change(function () {
// Update the inputs according freezer action // Update the inputs according freezer action
if ($("#id_action").val() === 'backup') { if ($("#id_action").val() === 'backup') {
hideEverything(); hideEverything();
showBackupOptions(); showBackupOptions();
@ -117,22 +119,18 @@ $("#id_action").change(function () {
$("#id_storage").change(function () { $("#id_storage").change(function () {
// Update the inputs according freezer storage backend // Update the inputs according freezer storage backend
if ($("#id_storage").val() === 'swift') { if ($("#id_storage").val() === 'swift') {
hideEverything(); //hideEverything();
showBackupOptions(); showBackupOptions();
("#id_mode").closest(".form-group").show(); hideSSHOptions();
} else if ($("#id_storage").val() === 'ssh') { } else if ($("#id_storage").val() === 'ssh') {
hideEverything(); //hideEverything();
showBackupOptions(); showBackupOptions();
$("#id_mode").closest(".form-group").show();
showSSHOptions(); showSSHOptions();
} else if ($("#id_storage").val() === 'local') { } else if ($("#id_storage").val() === 'local') {
hideEverything(); //hideEverything();
showBackupOptions(); showBackupOptions();
$("#id_mode").closest(".form-group").show(); hideSSHOptions();
} else {
hideEverything();
} }
}); });
@ -141,37 +139,27 @@ $("#id_mode").change(function () {
// Update the inputs according freezer mode // Update the inputs according freezer mode
if ($("#id_action").val() === 'backup') { if ($("#id_action").val() === 'backup') {
if ($("#id_mode").val() === 'fs') { if ($("#id_mode").val() === 'fs') {
hideEverything(); hideModeOptions();
showBackupOptions();
$("#id_advanced_configuration").closest(".form-group").show();
} else if ($("#id_mode").val() === 'mysql') { } else if ($("#id_mode").val() === 'mysql') {
hideEverything(); hideModeOptions();
showBackupOptions();
$("#id_mysql_conf").closest(".form-group").show(); $("#id_mysql_conf").closest(".form-group").show();
$("#id_sql_server_conf").closest(".form-group").hide();
$("#id_advanced_configuration").closest(".form-group").show();
} else if ($("#id_mode").val() === 'mssql') { } else if ($("#id_mode").val() === 'mssql') {
hideEverything(); hideModeOptions();
showBackupOptions();
$("#id_sql_server_conf").closest(".form-group").show(); $("#id_sql_server_conf").closest(".form-group").show();
$("#id_mysql_conf").closest(".form-group").hide();
$("#id_advanced_configuration").closest(".form-group").show();
} else if ($("#id_mode").val() === 'mongo') { } else if ($("#id_mode").val() === 'mongo') {
hideEverything(); hideModeOptions();
showBackupOptions();
$("#id_sql_server_conf").closest(".form-group").hide();
$("#id_mysql_conf").closest(".form-group").hide();
$("#id_advanced_configuration").closest(".form-group").show();
} else if ($("#id_mode").val() === 'cinder') { } else if ($("#id_mode").val() === 'cinder') {
hideEverything(); hideModeOptions();
showCinderOptions(); $("#id_cinder_vol_id").closest(".form-group").show();
$("#id_cinder_vol_id").closest(".form-group").show().addClass("required");
$("#id_advanced_configuration").closest(".form-group").show();
} else if ($("#id_mode").val() === 'nova') { } else if ($("#id_mode").val() === 'nova') {
hideEverything(); hideModeOptions();
showNovaOptions(); $("#id_nova_inst_id").closest(".form-group").show();
$("#id_nova_inst_id").closest(".form-group").show().addClass("required");
$("#id_advanced_configuration").closest(".form-group").show();
} }
} }
}); });
$(function () {
hideEverything();
triggerChanges();
});

View File

@ -14,7 +14,7 @@ var parent = $(".sortable_lists").parent();
parent.removeClass("col-sm-6"); parent.removeClass("col-sm-6");
parent.addClass("col-sm-12"); parent.addClass("col-sm-12");
var siblings = parent.siblings(); var siblings = parent.siblings();
siblings.remove(); siblings.hide();
$("form").submit(function (event) { $("form").submit(function (event) {
@ -29,6 +29,7 @@ $("form").submit(function (event) {
var job_id = $('#id_job_id').val(); var job_id = $('#id_job_id').val();
function get_url() { function get_url() {
var url = $(location).attr("origin"); var url = $(location).attr("origin");
url += '/disaster_recovery/api/actions/job/'; url += '/disaster_recovery/api/actions/job/';