Horizon implementation for jobs api endpoint

Implements: blueprint freezer-api-web-ui

Change-Id: I8339d4b319f85964d33a2ab5d5c5e3669ca55f1c
This commit is contained in:
Memo Garcia 2015-07-01 20:11:03 +01:00
parent a7470fd5dc
commit 206a0ec9b7
53 changed files with 2016 additions and 1660 deletions

View File

@ -1 +0,0 @@
__author__ = 'jonas'

View File

@ -1,73 +0,0 @@
# 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 tables
from horizon.utils import functions as utils
from horizon_web_ui.freezer_ui.django_utils import timestamp_to_string
class ActionsTable(tables.DataTable):
METADATA_LOADED_CHOICES = (
(False, None),
(True, True),
)
STATUS_DISPLAY = (
('pending', 'Pending'),
('started', 'Started'),
('abort_req', 'Abort Requested'),
('aborting', 'Aborting'),
('aborted', 'Aborted'),
('success', 'Success'),
('fail', 'Failed')
)
TYPE_DISPLAY = (
('restore', 'Restore'),
('backup', 'Backup (Unscheduled)')
)
client_id = tables.Column("client_id", verbose_name=_("Client Id"))
type = tables.Column('action', verbose_name=_("Type"),
display_choices=TYPE_DISPLAY)
description = tables.Column("description", verbose_name=_("Description"))
status = tables.Column('status',
verbose_name=_("Status"),
display_choices=STATUS_DISPLAY)
created = tables.Column('time_created', verbose_name=_("Created"),
filters=(timestamp_to_string,))
started = tables.Column('time_started', verbose_name=_("Started"),
filters=(timestamp_to_string,))
ended = tables.Column('time_ended', verbose_name=_("Ended"),
filters=(timestamp_to_string,))
def get_object_id(self, action):
return action.id
def __init__(self, *args, **kwargs):
super(ActionsTable, self).__init__(*args, **kwargs)
if 'offset' in self.request.GET:
self.offset = self.request.GET['offset']
else:
self.offset = 0
def get_pagination_string(self):
page_size = utils.get_page_size(self.request)
return "=".join(['offset', str(self.offset + page_size)])
class Meta(object):
name = "jobs"
verbose_name = _("Jobs")

View File

@ -1,22 +0,0 @@
# 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 horizon_web_ui.freezer_ui.actions import views
urlpatterns = patterns(
'',
url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -1,34 +0,0 @@
# 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 tables
from horizon_web_ui.freezer_ui.actions import tables as actions_tables
from horizon_web_ui.freezer_ui.api import api as freezer_api
class IndexView(tables.DataTableView):
name = _("Jobs")
slug = "actions"
table_class = actions_tables.ActionsTable
template_name = ("freezer_ui/actions/index.html")
def has_more_data(self, table):
return self._has_more
def get_data(self):
backups, self._has_more = freezer_api.actions_list(
self.request,
offset=self.table.offset)
return backups

View File

@ -17,8 +17,8 @@
from django.conf import settings
import warnings
import freezer.apiclient.client
from horizon_web_ui.freezer_ui.utils import create_dict_action
from horizon.utils import functions as utils
from horizon.utils.memoized import memoized # noqa
@ -51,19 +51,19 @@ class Dict2Object(object):
class Action(Dict2Object):
nested_dict = 'job'
nested_dict = 'job_action'
@property
def id(self):
return self.action_id
return self.job_id
class Configuration(Dict2Object):
nested_dict = 'config_file'
class Job(Dict2Object):
nested_dict = 'job_actions'
@property
def id(self):
return self.config_id
return self.job_id
class Backup(Dict2Object):
@ -81,17 +81,13 @@ class Client(Dict2Object):
def id(self):
return self.client_id
@property
def name(self):
return self.client_id
class ConfigClient(object):
def __init__(self, name, last_backup):
self.id = name
self.name = name
self.last_backup = last_backup
class ActionJob(object):
def __init__(self, job_id, action_id, action, backup_name):
self.job_id = job_id
self.action_id = action_id
self.action = action
self.backup_name = backup_name
@memoized
@ -125,188 +121,145 @@ def _freezerclient(request):
endpoint=api_url)
def configuration_create(request, name=None, container_name=None,
src_file=None, levels=None, optimize=None,
compression=None, encryption_password=None,
clients=[], start_datetime=None, interval=None,
exclude=None, log_file=None, proxy=None,
max_priority=False):
"""Create a new configuration file """
data = {
"name": name,
"container_name": container_name,
"src_file": src_file,
"levels": levels,
"optimize": optimize,
"compression": compression,
"encryption_password": encryption_password,
"clients": clients,
"start_datetime": start_datetime,
"interval": interval,
"exclude": exclude,
"log_file": log_file,
"proxy": proxy,
"max_priority": max_priority
def job_create(request, context):
"""Create a new job file """
job = create_dict_action(**context)
job['description'] = job.pop('description', None)
job['client_id'] = job.pop('client_id', None)
schedule = {
'end_datetime': job.pop('end_datetime', None),
'interval': job.pop('interval', None),
'start_datetime': job.pop('start_datetime', None),
}
return _freezerclient(request).configs.create(data)
job['job_schedule'] = schedule
job['job_actions'] = []
return _freezerclient(request).jobs.create(job)
def configuration_update(request, config_id=None, name=None,
src_file=None, levels=None, optimize=None,
compression=None, encryption_password=None,
clients=[], start_datetime=None, interval=None,
exclude=None, log_file=None, proxy=None,
max_priority=False, container_name=None,):
"""Update a new configuration file """
data = {
"name": name,
"container_name": container_name,
"src_file": src_file,
"levels": levels,
"optimize": optimize,
"compression": compression,
"encryption_password": encryption_password,
"clients": clients,
"start_datetime": start_datetime,
"interval": interval,
"exclude": exclude,
"log_file": log_file,
"proxy": proxy,
"max_priority": max_priority
def job_edit(request, context):
"""Edit an existing job file, but leave the actions to actions_edit"""
job = create_dict_action(**context)
job['description'] = job.pop('description', None)
job['client_id'] = job.pop('client_id', None)
schedule = {
'end_datetime': job.pop('end_datetime', None),
'interval': job.pop('interval', None),
'start_datetime': job.pop('start_datetime', None),
}
return _freezerclient(request).configs.update(config_id, data)
job['job_schedule'] = schedule
job_id = job.pop('original_name', None)
return _freezerclient(request).jobs.update(job_id, job)
def configuration_delete(request, obj_id):
return _freezerclient(request).configs.delete(obj_id)
def job_delete(request, obj_id):
return _freezerclient(request).jobs.delete(obj_id)
def configuration_clone(request, config_id):
config_file = _freezerclient(request).configs.get(config_id)
data = config_file[0]['config_file']
data['name'] = '{0}_clone'.format(data['name'])
return _freezerclient(request).configs.create(data)
def job_clone(request, job_id):
job_file = _freezerclient(request).jobs.get(job_id)
job_file['description'] = \
'{0}_clone'.format(job_file['description'])
job_file.pop('job_id', None)
job_file.pop('_version', None)
return _freezerclient(request).jobs.create(job_file)
def configuration_get(request, config_id):
config_file = _freezerclient(request).configs.get(config_id)
if config_file:
return [Configuration(data) for data in config_file]
def job_get(request, job_id):
job_file = _freezerclient(request).jobs.get(job_id)
if job_file:
job_item = [job_file]
job = [Job(data) for data in job_item]
return job
return []
def configuration_list(request):
configurations = _freezerclient(request).configs.list()
configurations = [Configuration(data) for data in configurations]
return configurations
def job_list(request):
jobs = _freezerclient(request).jobs.list_all()
jobs = [Job(data) for data in jobs]
return jobs
def clients_in_config(request, config_id):
configuration = configuration_get(request, config_id)
clients = []
last_backup = None
clients_dict = [c.get_dict() for c in configuration]
for client in clients_dict:
for client_id in client['config_file']['clients']:
backups, has_more = backups_list(request, text_match=client_id)
backups = [Backup(data) for data in backups]
backups = [b.get_dict() for b in backups]
for backup in backups:
last_backup = backup.data_dict['backup_metadata']['timestamp']
clients.append(ConfigClient(client_id, last_backup))
return clients
def action_create(request, context):
"""Create a new action for a job """
action = {}
if context['max_retries']:
action['max_retries'] = context.pop('max_retries')
if context['max_retries_interval']:
action['max_retries_interval'] = context.pop('max_retries_interval')
if context['mandatory']:
action['mandatory'] = context.pop('mandatory')
job_id = context.pop('original_name')
job_action = create_dict_action(**context)
action['freezer_action'] = job_action
action_id = _freezerclient(request).actions.create(action)
action['action_id'] = action_id
job = _freezerclient(request).jobs.get(job_id)
job['job_actions'].append(action)
return _freezerclient(request).jobs.update(job_id, job)
def client_list(request, limit=20):
clients = _freezerclient(request).registration.list(limit=limit)
def action_list(request):
actions = _freezerclient(request).actions.list()
actions = [Action(data) for data in actions]
return actions
def actions_in_job(request, job_id):
job = _freezerclient(request).jobs.get(job_id)
actions = []
try:
job_id = job['job_id']
actions = [ActionJob(job_id,
action['action_id'],
action['freezer_action']['action'],
action['freezer_action']['backup_name'])
for action in job['job_actions']]
except Exception:
warnings.warn('No more actions in your job')
return actions
def action_get(request, action_id):
action = _freezerclient(request).actions.get(action_id)
return action
def action_update(request, context):
job_id = context.pop('original_name')
action_id = context.pop('action_id')
job = _freezerclient(request).jobs.get(job_id)
for a in job['job_actions']:
if a['action_id'] == action_id:
if context['max_retries']:
a['max_retries'] = context.pop('max_retries')
if context['max_retries_interval']:
a['max_retries_interval'] = \
context.pop('max_retries_interval')
if context['mandatory']:
a['mandatory'] = context.pop('mandatory')
updated_action = create_dict_action(**context)
a['freezer_action'].update(updated_action)
return _freezerclient(request).jobs.update(job_id, job)
def action_delete(request, ids):
action_id, job_id = ids.split('===')
job = _freezerclient(request).jobs.get(job_id)
for action in job['job_actions']:
if action['action_id'] == action_id:
job['job_actions'].remove(action)
return _freezerclient(request).jobs.update(job_id, job)
def client_list(request):
clients = _freezerclient(request).registration.list()
clients = [Client(client) for client in clients]
return clients
def backups_list(request, offset=0, time_after=None, time_before=None,
text_match=None):
page_size = utils.get_page_size(request)
search = {}
if time_after:
search['time_after'] = time_after
if time_before:
search['time_before'] = time_before
if text_match:
search['match'] = [
{
"_all": text_match,
}
]
backups = _freezerclient(request).backups.list(
limit=page_size + 1,
offset=offset,
search=search)
if len(backups) > page_size:
backups.pop()
has_more = True
else:
has_more = False
# Wrap data in object for easier handling
backups = [Backup(data) for data in backups]
return backups, has_more
def backup_get(request, backup_id):
data = _freezerclient(request).backups.get(backup_id)
if data:
return Backup(data[0])
def restore_action_create(request,
backup_id,
destination_client_id,
destination_path,
description=None,
dry_run=False,
max_prio=False):
c = _freezerclient(request)
backup = c.backups.get(backup_id)[0]
action = {
"job": {
"action": "restore",
"container_name": backup['backup_metadata']['container'],
"restore-abs-path": destination_path,
"backup-name": backup['backup_metadata']['backup_name'],
"restore-from-host": backup['backup_metadata']['host_name'],
"max_cpu_priority": max_prio,
"dry_run": dry_run
},
"description": description,
"client_id": destination_client_id
}
c.actions.create(action)
def actions_list(request, offset=0):
page_size = utils.get_page_size(request)
actions = _freezerclient(request).actions.list(
limit=page_size + 1,
offset=offset)
if len(actions) > page_size:
actions.pop()
has_more = True
else:
has_more = False
# Wrap data in object for easier handling
actions = [Action(data['action']) for data in actions]
return actions, has_more

View File

@ -1,3 +0,0 @@
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

View File

@ -1,107 +0,0 @@
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# 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.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from horizon import forms
from horizon import workflows
import horizon_web_ui.freezer_ui.api.api as freezer_api
LOG = logging.getLogger(__name__)
class DestinationAction(workflows.MembershipAction):
path = forms.CharField(label=_("Destination Path"),
initial='/home/',
help_text="The path in which the backup should be "
"restored",
required=True)
backup_id = forms.CharField(widget=forms.HiddenInput())
def clean(self):
if 'client' in self.request.POST:
self.cleaned_data['client'] = self.request.POST['client']
else:
raise ValidationError('Client is required')
return self.cleaned_data
class Meta(object):
name = _("Destination")
slug = "destination"
class Destination(workflows.Step):
template_name = 'freezer_ui/backups/restore.html'
action_class = DestinationAction
contributes = ('client', 'path', 'backup_id')
def has_required_fields(self):
return True
class OptionsAction(workflows.Action):
description = forms.CharField(widget=forms.Textarea,
label="Description",
required=False,
help_text="Free text description of this "
"restore.")
dry_run = forms.BooleanField(label=_("Dry Run"),
required=False)
max_prio = forms.BooleanField(label=_("Max Process Priority"),
required=False)
class Meta(object):
name = _("Options")
class Options(workflows.Step):
action_class = OptionsAction
contributes = ('description', 'dry_run', 'max_prio')
after = Destination
class ConfigureBackups(workflows.Workflow):
slug = "restore"
name = _("Restore")
success_url = "horizon:freezer_ui:backups:index"
success_message = "Restore job successfully queued. It will get " \
"executed soon."
wizard = False
default_steps = (Destination, Options)
def __init__(self, *args, **kwargs):
super(ConfigureBackups, self).__init__(*args, **kwargs)
pass
def handle(self, request, data):
freezer_api.restore_action_create(
request,
backup_id=data['backup_id'],
destination_client_id=data['client'],
destination_path=data['path'],
description=data['description'],
dry_run=data['dry_run'],
max_prio=data['max_prio']
)
return True

View File

@ -1,111 +0,0 @@
# 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 import safestring
from django.utils.translation import ugettext_lazy as _
from horizon import tables
from horizon.utils import functions as utils
from horizon_web_ui.freezer_ui.django_utils import timestamp_to_string
class Restore(tables.LinkAction):
name = "restore"
verbose_name = _("Restore")
classes = ("ajax-modal", "btn-launch")
ajax = True
def get_link_url(self, datum=None):
return reverse("horizon:freezer_ui:backups:restore",
kwargs={'backup_id': datum.id})
def allowed(self, request, instance):
return True # is_loaded(instance)
class BackupFilter(tables.FilterAction):
filter_type = "server"
filter_choices = (("before", "Created before", True),
("after", "Created after", True),
("between", "Created between", True),
("contains", "Contains text", True))
def icons(backup):
result = []
placeholder = '<i class="fa fa-fw"></i>'
level_txt = "Level: {} ({} backup) out of {}".format(
backup.level, "Full" if backup.level == 0 else "Incremental",
backup.max_level)
result.append(
'<i class="fa fa-fw fa-custom-number" title="{}">{}</i>'.format(
level_txt, backup.level))
if backup.encrypted:
result.append(
'<i class="fa fa-lock fa-fw" title="Backup is encrypted"></i>')
else:
result.append(placeholder)
if int(backup.total_broken_links) > 0:
result.append(
'<i class="fa fa-chain-broken fa-fw" title="There are {} broken '
'links in this backup"></i>'.format(backup.total_broken_links))
else:
result.append(placeholder)
if backup.excluded_files:
result.append(
'<i class="fa fa-minus-square fa-fw" title="{} files have been exc'
'luded from this backup"></i>'.format(len(backup.excluded_files)))
else:
result.append(placeholder)
return safestring.mark_safe("".join(result))
def backup_detail_view(backup):
return reverse("horizon:freezer_ui:backups:detail", args=[backup.id])
class BackupsTable(tables.DataTable):
backup_name = tables.Column('backup_name', verbose_name=_("Backup Name"),
link=backup_detail_view)
host_name = tables.Column('host_name', verbose_name=_("Host Name"))
created_by = tables.Column("user_name", verbose_name=_("Created By"))
created = tables.Column("timestamp",
verbose_name=_("Created At"),
filters=[timestamp_to_string])
icons = tables.Column(icons, verbose_name='Info')
def __init__(self, *args, **kwargs):
super(BackupsTable, self).__init__(*args, **kwargs)
if 'offset' in self.request.GET:
self.offset = self.request.GET['offset']
else:
self.offset = 0
def get_object_id(self, backup):
return backup.id
def get_pagination_string(self):
page_size = utils.get_page_size(self.request)
return "=".join(['offset', str(self.offset + page_size)])
class Meta(object):
name = "vms"
verbose_name = _("Backups")
row_actions = (Restore,)
table_actions = (BackupFilter, )
multi_select = False

View File

@ -1,17 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Backups" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Backups") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
<pre>{{ data }}</pre>
</div>
</div>
{% endblock %}

View File

@ -1,17 +0,0 @@
{% extends 'base.html' %}
{% block css %}
{% include "_stylesheets.html" %}
<link href='{{ STATIC_URL }}freezer/css/freezer.css' type='text/css' media='screen' rel='stylesheet' />
{% endblock %}
{% load i18n %}
{% block title %}{% trans "VMs" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Backups") %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,46 +0,0 @@
<noscript><h3>{{ step }}</h3></noscript>
<div class="row" ng-controller="DestinationCtrl">
<div class="col-sm-12">
<table class="table table-bordered table-striped">
<thead>
<tr class="table_caption">
<th class="table_header" colspan="3" data-column="0">
<div class="table_actions clearfix">
<div class="table_search">
<input class="form-control" ng-model="query">
</div>
</div>
</th>
</tr>
<tr ng-hide="filtered.length <= 0">
<th class="multi_select_column"></th>
<th>Hostname</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="client in filtered = (clients | filter: { client : { $ : query } } | limitTo:10)">
<td class="multi_select_column">
<input type="radio" name="client" value="{$ client['client']['client_id'] $}">
</td>
<td>{$ client['client']['client_id'] $}</td>
<td>{$ client['client']['description'] $}</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3" data-column="0">
{$ filtered.length ? filtered.length : 0 $} out of {$ clients.length $} displayed. Use the filter field to limit the number of results.
</td>
</tr>
</tfoot>
</table>
</div>
<div class="col-sm-6">
{% include "horizon/common/_form_fields.html" %}
{{ table.render }}
</div>
<div class="col-sm-12">
{{ step.get_help_text }}
</div>
</div>

View File

@ -1,26 +0,0 @@
# 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 horizon_web_ui.freezer_ui.backups import views
urlpatterns = patterns(
'',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<backup_id>[^/]*)$', views.DetailView.as_view(), name='detail'),
url(r'^restore/(?P<backup_id>.*)$',
views.RestoreView.as_view(),
name='restore'),
)

View File

@ -1,141 +0,0 @@
# 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 datetime
import pprint
import time
from django.core.urlresolvers import reverse
from django.template.defaultfilters import date as django_date
from django.utils.translation import ugettext_lazy as _
from django.views import generic
import parsedatetime as pdt
from horizon import exceptions
from horizon import messages
from horizon import tables
from horizon import workflows
from horizon_web_ui.freezer_ui.backups import tables as freezer_tables
import horizon_web_ui.freezer_ui.api.api as freezer_api
import restore_workflow
class IndexView(tables.DataTableView):
name = _("Backups")
slug = "backups"
table_class = freezer_tables.BackupsTable
template_name = ("freezer_ui/backups/index.html")
def has_more_data(self, table):
return self._has_more
def get_data(self):
filter = self.get_filters(self.request,
self.table.get_filter_field(),
self.table.get_filter_string())
backups, self._has_more = freezer_api.backups_list(
self.request,
offset=self.table.offset,
time_after=filter['from'],
time_before=filter['to'],
text_match=filter['contains']
)
return backups
def get_filters(self, request, filter_field, filter_string):
cal = pdt.Calendar()
filters = {}
filters['from'] = None
filters['to'] = None
filters['contains'] = None
if filter_field == 'between':
result_range = cal.nlp(filter_string)
if result_range and len(result_range) == 2:
filters['from'] = int(
time.mktime(result_range[0][0].timetuple()))
filters['to'] = int(
time.mktime(result_range[1][0].timetuple()))
else:
messages.warning(
request,
"Please enter two dates. E.g: '01/01/2014 - 05/09/2015'.")
elif filter_field in ['before', 'after']:
result, what = cal.parse(filter_string)
if what == 0:
messages.warning(
self.table.request,
"Please enter a date/time. E.g: '01/01/2014 12pm' or '1 we"
"ek ago'.")
else:
field = 'to' if filter_field == 'before' else 'from'
dt = datetime.datetime(*result[:6])
if what == 1: # a date without time
# use .date() to remove time part
filters[field] = int(time.mktime(dt.date().timetuple()))
elif what in [2, 3]: # date and time or time with current date
filters[field] = int(time.mktime(dt.timetuple()))
else:
raise Exception(
'Unknown result when parsing date: {}'.format(what))
elif filter_field == 'contains':
filters['contains'] = filter_string.lower()
return filters
class DetailView(generic.TemplateView):
template_name = 'freezer_ui/backups/detail.html'
def get_context_data(self, **kwargs):
backup = freezer_api.get_backup(self.request, kwargs['backup_id'])
return {'data': pprint.pformat(backup.data_dict)}
class RestoreView(workflows.WorkflowView):
workflow_class = restore_workflow.ConfigureBackups
def get_object(self, *args, **kwargs):
id = self.kwargs['backup_id']
try:
return freezer_api.get_backup(self.request, id)
except Exception:
redirect = reverse("horizon:freezer_ui:backups:index")
msg = _('Unable to retrieve details.')
exceptions.handle(self.request, msg, redirect=redirect)
def is_update(self):
return 'name' in self.kwargs and bool(self.kwargs['name'])
def get_workflow_name(self):
backup = freezer_api.backup_get(self.request, self.kwargs['backup_id'])
backup_date = datetime.datetime.fromtimestamp(int(backup.timestamp))
backup_date_str = django_date(backup_date, 'SHORT_DATETIME_FORMAT')
return "Restore '{}' from {}".format(
backup.backup_name, backup_date_str)
def get_initial(self):
return {"backup_id": self.kwargs['backup_id']}
def get_workflow(self, *args, **kwargs):
workflow = super(RestoreView, self).get_workflow(*args, **kwargs)
workflow.name = self.get_workflow_name()
return workflow

View File

@ -1 +0,0 @@
__author__ = 'jonas'

View File

@ -1,28 +0,0 @@
# Copyright 2012 Nebula, Inc.
#
# 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 browsers
from horizon_web_ui.freezer_ui.configurations import tables
class ContainerBrowser(browsers.ResourceBrowser):
name = "backup_configuration"
verbose_name = _("Backup Configuration")
navigation_table_class = tables.BackupConfigsTable
content_table_class = tables.InstancesTable
navigable_item_name = _("Backup Configuration")
navigation_kwarg_name = "name"

View File

@ -1,3 +0,0 @@
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

View File

@ -1,24 +0,0 @@
# 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 horizon_web_ui.freezer_ui import dashboard
class BackupConfigsPanel(horizon.Panel):
name = _("Configurations")
slug = "configurations"
dashboard.Freezer.register(BackupConfigsPanel)

View File

@ -1,161 +0,0 @@
# 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 datetime
from django import shortcuts
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import messages
from horizon import tables
from horizon.utils.urlresolvers import reverse
import horizon_web_ui.freezer_ui.api.api as freezer_api
from horizon_web_ui.freezer_ui.django_utils import timestamp_to_string
def format_last_backup(last_backup):
last_backup_ts = datetime.datetime.fromtimestamp(last_backup)
ten_days_later = last_backup_ts + datetime.timedelta(days=10)
today = datetime.datetime.today()
if last_backup is None:
colour = 'red'
icon = 'fire'
text = 'Never'
elif ten_days_later < today:
colour = 'orange'
icon = 'thumbs-down'
text = timestamp_to_string(last_backup)
else:
colour = 'green'
icon = 'thumbs-up'
text = timestamp_to_string(last_backup)
return safestring.mark_safe(
'<span style="color:{}"><span class="glyphicon glyphicon-{}" aria-hidd'
'en="true"></span> {}</span>'.format(colour, icon, text))
class Restore(tables.Action):
name = "restore"
verbose_name = _("Restore")
def single(self, table, request, instance):
messages.info(request, "Needs to be implemented")
def allowed(self, request, instance):
return True
class DeleteConfig(tables.DeleteAction):
name = "delete"
classes = ("btn-danger",)
icon = "remove"
help_text = _("Delete configurations are not recoverable.")
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Configuration File",
u"Delete Configuration Files",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Configuration File",
u"Deleted Configuration Files",
count
)
def delete(self, request, obj_id):
return freezer_api.configuration_delete(request, obj_id)
class CloneConfig(tables.Action):
name = "clone"
verbose_name = _("Clone")
# classes = ("ajax-modal",)
help_text = _("Clone and edit a configuration file")
def single(self, table, request, obj_id):
freezer_api.configuration_clone(request, obj_id)
return shortcuts.redirect('horizon:freezer_ui:configurations:index')
class EditConfig(tables.LinkAction):
name = "edit"
verbose_name = _("Edit")
classes = ("ajax-modal",)
def get_link_url(self, datum=None):
return reverse("horizon:freezer_ui:configurations:configure",
kwargs={'name': datum.config_id})
def get_backup_configs_link(backup_config):
return reverse('horizon:freezer_ui:configurations:index',
kwargs={'config_id': backup_config.config_id})
class CreateConfig(tables.LinkAction):
name = "create"
verbose_name = _("Create Configuration")
url = "horizon:freezer_ui:configurations:create"
classes = ("ajax-modal",)
icon = "plus"
class BackupConfigsTable(tables.DataTable):
name = tables.Column("name", link=get_backup_configs_link,
verbose_name=_("Configuration Name"))
def get_object_id(self, backup_config):
return backup_config.id
class Meta(object):
name = "backup_configuration"
verbose_name = _("Backup Configurations")
table_actions = (CreateConfig,)
footer = False
multi_select = False
row_actions = (EditConfig,
CloneConfig,
DeleteConfig, )
class ObjectFilterAction(tables.FilterAction):
def allowed(self, request, datum):
return bool(self.table.kwargs['config_id'])
class InstancesTable(tables.DataTable):
client = tables.Column('name', verbose_name=_("Client Name"))
created = tables.Column('last_backup',
filters=(format_last_backup,),
verbose_name=_("Last backup"))
def get_object_id(self, container):
return container.name
class Meta(object):
name = "clients"
verbose_name = _("Clients")
table_actions = (ObjectFilterAction,)
row_actions = (Restore,)
footer = False
multi_select = False

View File

@ -1,11 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Backup Configurations" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Backup Configurations") %}
{% endblock page_header %}
{% block main %}
{{ backup_configuration_browser.render }}
{% endblock %}

View File

@ -1,40 +0,0 @@
# Copyright 2014 Hewlett-Packard
#
# 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.
class Configuration(object):
def __init__(self, data_dict):
self.data_dict = data_dict
@property
def id(self):
return self.config_id
def __getattr__(self, attr):
"""Make data_dict fields available via class interface """
if attr in self.data_dict:
return self.data_dict[attr]
elif attr in self.data_dict['config_file']:
return self.data_dict['config_file'][attr]
else:
return object.__getattribute__(self, attr)
class Client(object):
"""Aggregate clients and metadata """
def __init__(self, client):
self.name = client
self.clients = client
self.client_id = client

View File

@ -1,94 +0,0 @@
# 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.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import workflows
from horizon import browsers
from horizon import exceptions
import horizon_web_ui.freezer_ui.api.api as freezer_api
import horizon_web_ui.freezer_ui.configurations.browsers as project_browsers
import workflows.configure as configure_workflow
LOG = logging.getLogger(__name__)
class ConfigureWorkflowView(workflows.WorkflowView):
workflow_class = configure_workflow.ConfigureBackups
def get_object(self, *args, **kwargs):
config_id = self.kwargs['name']
try:
return freezer_api.configuration_get(self.request, config_id)[0]
except Exception:
redirect = reverse("horizon:freezer_ui:configurations:index")
msg = _('Unable to retrieve details.')
exceptions.handle(self.request, msg, redirect=redirect)
def is_update(self):
return 'name' in self.kwargs and bool(self.kwargs['name'])
def get_initial(self):
initial = super(ConfigureWorkflowView, self).get_initial()
if self.is_update():
initial.update({'original_name': None})
config = self.get_object()
initial['name'] = config.name
initial['container_name'] = config.container_name
initial['config_id'] = config.config_id
initial['src_file'] = config.src_file
initial['levels'] = config.levels
initial['optimize'] = config.optimize
initial['compression'] = config.compression
initial['encryption_password'] = config.encryption_password
initial['start_datetime'] = config.start_datetime
initial['interval'] = config.interval
initial['exclude'] = config.exclude
initial['log_file'] = config.log_file
initial['encryption_password'] = config.encryption_password
initial['proxy'] = config.proxy
initial['max_priority'] = config.max_priority
initial['clients'] = config.clients
initial['original_name'] = config.config_id
initial.update({'original_name': config.config_id})
return initial
class BackupConfigsView(browsers.ResourceBrowserView):
browser_class = project_browsers.ContainerBrowser
template_name = "freezer_ui/configurations/browser.html"
def get_backup_configuration_data(self):
configurations = []
try:
configurations = freezer_api.configuration_list(self.request)
except Exception:
msg = _('Unable to retrieve configuration file list.')
exceptions.handle(self.request, msg)
return configurations
def get_clients_data(self):
configuration = []
try:
if self.kwargs['config_id']:
configuration = freezer_api.clients_in_config(
self.request, self.kwargs['config_id'])
except Exception:
msg = _('Unable to retrieve instances for this configuration.')
exceptions.handle(self.request, msg)
return configuration

View File

@ -1,315 +0,0 @@
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# 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.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_variables # noqa
from horizon import exceptions
from horizon import forms
from horizon import workflows
import horizon_web_ui.freezer_ui.api.api as freezer_api
LOG = logging.getLogger(__name__)
class BackupConfigurationAction(workflows.Action):
original_name = forms.CharField(
widget=forms.HiddenInput(),
required=False)
name = forms.CharField(
label=_("Configuration Name"),
required=True)
container_name = forms.CharField(
label=_("Swift Container Name"),
required=True)
mode = forms.ChoiceField(
help_text="Choose what you want to backup",
required=True)
src_file = forms.CharField(
label=_("Source File/Directory"),
help_text="The file or directory you want to back up to Swift",
required=True)
def populate_mode_choices(self, request, context):
return [
('fs', _("File system")),
('snapshot', _("Snapshot")),
('mongo', _("MongoDB")),
('mysql', _("MySQL")),
('mssql', _("Microsoft SQL Server")),
('elastic', _("ElasticSearch")),
('postgres', _("Postgres")),
]
class Meta(object):
name = _("Backup")
class BackupConfiguration(workflows.Step):
action_class = BackupConfigurationAction
contributes = ('mode',
'name',
'container_name',
'src_file',
'original_name')
class OptionsConfigurationAction(workflows.Action):
levels = forms.IntegerField(
label=_("Number of incremental backups"),
initial=0,
min_value=0,
required=False,
help_text="Set the backup level used with tar"
" to implement incremental backup. "
"If a level 1 is specified but no "
"level 0 is already available, a "
"level 0 will be done and "
"subsequently backs to level 1. "
"Default 0 (No Incremental)")
optimize = forms.ChoiceField(
choices=[('speed', _("Speed (tar)")),
('bandwith', "Bandwith/Space (rsync)")],
help_text="",
label='Optimize for...',
required=False)
compression = forms.ChoiceField(
choices=[('gzip', _("Minimum Compression (GZip/Zip)")),
('bzip', _("Medium Compression (BZip2")),
('xz', _("Maximum Compression (XZ)"))],
help_text="",
label='Compression Level',
required=False)
encryption_password = forms.CharField(
label=_("Encryption Password"), # encryption key
widget=forms.PasswordInput(),
help_text="",
required=False)
class Meta(object):
name = _("Options")
class OptionsConfiguration(workflows.Step):
action_class = OptionsConfigurationAction
contributes = ('levels',
'optimize',
'compression',
'encryption_password',)
class ClientsConfigurationAction(workflows.MembershipAction):
def __init__(self, request, *args, **kwargs):
super(ClientsConfigurationAction, self).__init__(request,
*args,
**kwargs)
err_msg_configured = 'Unable to retrieve list of configured clients.'
err_msg_all = 'Unable to retrieve list of clients.'
default_role_field_name = self.get_default_role_field_name()
self.fields[default_role_field_name] = forms.CharField(required=False)
self.fields[default_role_field_name].initial = 'member'
field_name = self.get_member_field_name('member')
self.fields[field_name] = forms.MultipleChoiceField(required=False)
all_clients = []
try:
all_clients = freezer_api.client_list(request)
except Exception:
exceptions.handle(request, err_msg_all)
clients = [(c.client_id, c.name) for c in all_clients]
self.fields[field_name].choices = clients
if request.method == 'POST':
return
initial_clients = []
try:
original_name = args[0].get('original_name', None)
if original_name:
configured_clients = \
freezer_api.clients_in_config(request, original_name)
initial_clients = [client.id for client in configured_clients]
except Exception:
exceptions.handle(request, err_msg_configured)
self.fields[field_name].initial = initial_clients
class Meta(object):
name = _("Clients")
slug = "configure_clients"
class ClientsConfiguration(workflows.UpdateMembersStep):
action_class = ClientsConfigurationAction
help_text = _(
"Select the clients that will be backed up using this configuration.")
available_list_title = _("All Clients")
members_list_title = _("Selected Clients")
no_available_text = _("No clients found.")
no_members_text = _("No clients selected.")
show_roles = False
contributes = ("clients",)
def contribute(self, data, context):
if data:
member_field_name = self.get_member_field_name('member')
context['clients'] = data.get(member_field_name, [])
return context
class SchedulingConfigurationAction(workflows.Action):
start_datetime = forms.CharField(
label=_("Start Date and Time"),
required=False,
help_text=_("Set a start date and time for backups"))
interval = forms.CharField(
label=_("Interval"),
required=False,
help_text=_("Repeat this configuration in an interval. e.g. 24 hours"))
class Meta(object):
name = _("Scheduling")
class SchedulingConfiguration(workflows.Step):
action_class = SchedulingConfigurationAction
contributes = ('start_datetime',
'interval',)
class AdvancedConfigurationAction(workflows.Action):
exclude = forms.CharField(
label=_("Exclude Files"),
help_text="Exclude files, given as a PATTERN.Ex:"
" --exclude '*.log' will exclude any "
"file with name ending with .log. "
"Default no exclude",
required=False)
log_file = forms.CharField(
label=_("Log File Path"),
help_text="Set log file. By default logs to "
"/var/log/freezer.log If that file "
"is not writable, freezer tries to "
"log to ~/.freezer/freezer.log",
required=False)
proxy = forms.CharField(
label=_("Proxy URL"),
help_text="Enforce proxy that alters system "
"HTTP_PROXY and HTTPS_PROXY",
widget=forms.URLInput(),
required=False)
max_priority = forms.BooleanField(
label=_("Max Priority"),
help_text="Set the cpu process to the "
"highest priority (i.e. -20 "
"on Linux) and real-time for "
"I/O. The process priority "
"will be set only if nice and "
"ionice are installed Default "
"disabled. Use with caution.",
widget=forms.CheckboxInput(),
required=False)
class Meta(object):
name = _("Advanced Configuration")
class AdvancedConfiguration(workflows.Step):
action_class = AdvancedConfigurationAction
contributes = ('exclude',
'log_file',
'proxy',
'max_priority')
class ConfigureBackups(workflows.Workflow):
slug = "configuration"
name = _("Configuration")
finalize_button_name = _("Save")
success_message = _('Configuration file saved correctly.')
failure_message = _('Unable to save configuration file.')
success_url = "horizon:freezer_ui:configurations:index"
default_steps = (BackupConfiguration,
OptionsConfiguration,
ClientsConfiguration,
SchedulingConfiguration,
AdvancedConfiguration)
@sensitive_variables('encryption_password',
'confirm_encryption_password')
def handle(self, request, context):
try:
if context['original_name'] == '':
freezer_api.configuration_create(
request,
name=context['name'],
container_name=context['container_name'],
src_file=context['src_file'],
levels=context['levels'], # if empty save 0 not null
optimize=context['optimize'],
compression=context['compression'],
encryption_password=context['encryption_password'],
clients=context['clients'], # save the name of the client
start_datetime=context['start_datetime'],
interval=context['interval'],
exclude=context['exclude'],
log_file=context['log_file'],
proxy=context['proxy'],
max_priority=context['max_priority'],
)
else:
freezer_api.configuration_update(
request,
config_id=context['original_name'],
name=context['name'],
container_name=context['container_name'],
src_file=context['src_file'],
levels=context['levels'], # if empty save 0 not null
optimize=context['optimize'],
compression=context['compression'],
encryption_password=context['encryption_password'],
clients=context['clients'], # save the name of the client
start_datetime=context['start_datetime'],
interval=context['interval'],
exclude=context['exclude'],
log_file=context['log_file'],
proxy=context['proxy'],
max_priority=context['max_priority'],
)
return True
except Exception:
exceptions.handle(request)
return False

View File

@ -15,17 +15,17 @@ from django.utils.translation import ugettext_lazy as _
import horizon
class Mygroup(horizon.PanelGroup):
slug = "mygroup"
name = _("Freezer")
panels = ('overview', 'configurations', 'backups', 'actions')
class FreezerDR(horizon.PanelGroup):
slug = "freezerdr"
name = _("Backup and Restore")
panels = ('jobs',)
class Freezer(horizon.Dashboard):
name = _("Backup Restore DR")
name = _("Disaster Recovery")
slug = "freezer_ui"
panels = (Mygroup,)
default_panel = 'overview'
panels = (FreezerDR,)
default_panel = 'jobs'
horizon.register(Freezer)

View File

@ -2,7 +2,7 @@
# 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
# 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
@ -11,15 +11,14 @@
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
import horizon_web_ui.freezer_ui.dashboard as dashboard
from horizon import browsers
from horizon_web_ui.freezer_ui.jobs import tables
class BackupsPanel(horizon.Panel):
name = _("Backups")
slug = "backups"
dashboard.Freezer.register(BackupsPanel)
class ContainerBrowser(browsers.ResourceBrowser):
name = "backup_configuration"
verbose_name = _("Job Configuration")
navigation_table_class = tables.JobsTable
content_table_class = tables.ActionsTable
navigable_item_name = _("Jobs")
navigation_kwarg_name = "name"

View File

@ -2,7 +2,7 @@
# 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
# 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
@ -13,13 +13,12 @@
from django.utils.translation import ugettext_lazy as _
import horizon
from horizon_web_ui.freezer_ui import dashboard
class ActionsPanel(horizon.Panel):
class JobsPanel(horizon.Panel):
name = _("Jobs")
slug = "actions"
slug = "jobs"
dashboard.Freezer.register(ActionsPanel)
dashboard.Freezer.register(JobsPanel)

226
freezer_ui/jobs/tables.py Normal file
View File

@ -0,0 +1,226 @@
# 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 datetime
from django import shortcuts
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import messages
from horizon import tables
from horizon.utils.urlresolvers import reverse
import horizon_web_ui.freezer_ui.api.api as freezer_api
from horizon_web_ui.freezer_ui.django_utils import timestamp_to_string
def format_last_backup(last_backup):
last_backup_ts = datetime.datetime.fromtimestamp(last_backup)
ten_days_later = last_backup_ts + datetime.timedelta(days=10)
today = datetime.datetime.today()
if last_backup is None:
colour = 'red'
icon = 'fire'
text = 'Never'
elif ten_days_later < today:
colour = 'orange'
icon = 'thumbs-down'
text = timestamp_to_string(last_backup)
else:
colour = 'green'
icon = 'thumbs-up'
text = timestamp_to_string(last_backup)
return safestring.mark_safe(
'<span style="color:{}"><span class="glyphicon glyphicon-{}" aria-hidd'
'en="true"></span> {}</span>'.format(colour, icon, text))
class Restore(tables.Action):
name = "restore"
verbose_name = _("Restore")
def single(self, table, request, instance):
messages.info(request, "Needs to be implemented")
def allowed(self, request, instance):
return True
class DeleteJob(tables.DeleteAction):
name = "delete"
classes = ("btn-danger",)
icon = "remove"
help_text = _("Delete jobs is not recoverable.")
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Job File",
u"Delete Job Files",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Job File",
u"Deleted Job Files",
count
)
def delete(self, request, obj_id):
return freezer_api.job_delete(request, obj_id)
class CloneJob(tables.Action):
name = "clone"
verbose_name = _("Clone Job")
help_text = _("Clone and edit a job file")
def single(self, table, request, obj_id):
freezer_api.job_clone(request, obj_id)
return shortcuts.redirect('horizon:freezer_ui:jobs:index')
class EditJob(tables.LinkAction):
name = "edit"
verbose_name = _("Edit Job")
classes = ("ajax-modal",)
icon = "pencil"
def get_link_url(self, datum=None):
return reverse("horizon:freezer_ui:jobs:configure",
kwargs={'backup_name': datum.job_id})
def get_backup_configs_link(backup_config):
return reverse('horizon:freezer_ui:jobs:index',
kwargs={'job_id': backup_config.job_id})
class CreateJob(tables.LinkAction):
name = "create"
verbose_name = _("Create Job")
url = "horizon:freezer_ui:jobs:create"
classes = ("ajax-modal",)
icon = "plus"
class CreateAction(tables.LinkAction):
name = "create_action"
verbose_name = _("Create Action")
url = "horizon:freezer_ui:jobs:create_action"
classes = ("ajax-modal",)
icon = "plus"
def get_link_url(self, datum=None):
return reverse("horizon:freezer_ui:jobs:create_action",
kwargs={'job_id': datum.job_id})
class JobsTable(tables.DataTable):
job_name = tables.Column("description",
link=get_backup_configs_link,
verbose_name=_("Job Name"))
result = tables.Column("result",
verbose_name=_("Last Result"))
def get_object_id(self, backup_config):
return backup_config.id
class Meta(object):
name = "jobs"
verbose_name = _("Jobs")
table_actions = (CreateJob,)
footer = False
multi_select = False
row_actions = (CreateAction,
EditJob,
CloneJob,
DeleteJob,)
class DeleteAction(tables.DeleteAction):
name = "delete"
classes = ("btn-danger",)
icon = "remove"
help_text = _("Delete actions is not recoverable.")
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Action",
u"Delete Action",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted action File",
u"Deleted action Files",
count
)
def delete(self, request, obj_id):
freezer_api.action_delete(request, obj_id)
return reverse("horizon:freezer_ui:jobs:index")
class EditAction(tables.LinkAction):
name = "edit"
verbose_name = _("Edit")
classes = ("ajax-modal",)
icon = "pencil"
def get_link_url(self, datum=None):
# this is used to pass to values as an url
# TODO: look for a way to improve this
ids = '{0}==={1}'.format(datum.action_id, datum.job_id)
return reverse("horizon:freezer_ui:jobs:create_action",
kwargs={'job_id': ids})
class DeleteMultipleActions(DeleteAction):
name = "delete_multiple_actions"
class ObjectFilterAction(tables.FilterAction):
def allowed(self, request, datum):
return bool(self.table.kwargs['job_id'])
class ActionsTable(tables.DataTable):
action_name = tables.Column('action',
verbose_name=_("Action"))
backup_name = tables.Column('backup_name',
verbose_name=_("Action Name"))
def get_object_id(self, container):
# this is used to pass to values as an url
# TODO: look for a way to improve this
ids = '{0}==={1}'.format(container.action_id, container.job_id)
return ids
class Meta(object):
name = "status"
verbose_name = _("Status")
table_actions = (ObjectFilterAction,)
row_actions = (EditAction,
DeleteAction,)
footer = False
multi_select = True

View File

@ -0,0 +1,7 @@
{% load i18n horizon humanize %}
{% block help_message %}
{% endblock %}
<!-- Jquery code to hide freezer inputs -->
<script type='text/javascript' src='{{ STATIC_URL }}freezer/js/freezer.actions.action.js'></script>

View File

@ -0,0 +1,7 @@
{% load i18n horizon humanize %}
{% block help_message %}
{% endblock %}
<!-- Jquery code to hide freezer inputs -->
<script type='text/javascript' src='{{ STATIC_URL }}freezer/js/freezer.jobs.actions.action.js'></script>

View File

@ -0,0 +1,7 @@
{% load i18n horizon humanize %}
{% block help_message %}
{% endblock %}
<!-- Jquery code to hide freezer inputs -->
<script type='text/javascript' src='{{ STATIC_URL }}freezer/js/freezer.actions.advanced.js'></script>

View File

@ -0,0 +1,21 @@
{% load i18n horizon humanize %}
{% block help_message %}
<h4>{% blocktrans %}Start and End Date Time{% endblocktrans %}</h4>
<p>{% blocktrans %}Set a start date and time to execute jobs in ISO format:{% endblocktrans %}</p>
<ul>
<li>YYYY-MM-DDThh:mm:ss</li>
</ul>
<h4>{% blocktrans %}Interval{% endblocktrans %}</h4>
<p>{% blocktrans %}Set the interval in the following format:{% endblocktrans %}</p>
<ul>
<li>continuous</li>
<li>N weeks</li>
<li>N days</li>
<li>N hours</li>
<li>N minutes</li>
<li>N seconds</li>
</ul>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% load i18n horizon humanize %}
{% block help_message %}
{% endblock %}
<!-- Jquery code to hide freezer inputs -->
<script type='text/javascript' src='{{ STATIC_URL }}freezer/js/freezer.actions.snapshot.js'></script>

View File

@ -1,11 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "VMs" %}{% endblock %}
{% block title %}{% trans "Job Configurations" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Jobs") %}
{% include "horizon/common/_page_header.html" with title=_("Job Configurations") %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{{ backup_configuration_browser.render }}
{% endblock %}

View File

@ -0,0 +1,21 @@
{% load i18n horizon humanize %}
{% block help_message %}
<h4>{% blocktrans %}Start and End Date Time{% endblocktrans %}</h4>
<p>{% blocktrans %}Set a start date and time to execute jobs in ISO format:{% endblocktrans %}</p>
<ul>
<li>YYYY-MM-DDThh:mm:ss</li>
</ul>
<h4>{% blocktrans %}Interval{% endblocktrans %}</h4>
<p>{% blocktrans %}Set the interval in the following format:{% endblocktrans %}</p>
<ul>
<li>continuous</li>
<li>N weeks</li>
<li>N days</li>
<li>N hours</li>
<li>N minutes</li>
<li>N seconds</li>
</ul>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% load i18n horizon humanize %}
{% block help_message %}
{% endblock %}
<!-- Jquery code to hide freezer inputs -->
<script type='text/javascript' src='{{ STATIC_URL }}freezer/js/freezer.actions.snapshot.js'></script>

View File

@ -13,22 +13,25 @@
from django.conf.urls import patterns
from django.conf.urls import url
from horizon_web_ui.freezer_ui.configurations import views
from horizon_web_ui.freezer_ui.jobs import views
urlpatterns = patterns(
'',
# url(r'^$', views.BackupConfigsView.as_view(), name='test'),
url(r'^(?P<config_id>[^/]+)?$',
views.BackupConfigsView.as_view(),
url(r'^(?P<job_id>[^/]+)?$',
views.JobsView.as_view(),
name='index'),
url(r'^create/$',
views.ConfigureWorkflowView.as_view(),
views.JobWorkflowView.as_view(),
name='create'),
url(r'^configure/(?P<name>[^/]+)?$',
views.ConfigureWorkflowView.as_view(),
url(r'^create_action/(?P<job_id>[^/]+)?$',
views.ActionWorkflowView.as_view(),
name='create_action'),
url(r'^configure/(?P<backup_name>[^/]+)?$',
views.JobWorkflowView.as_view(),
name='configure'),
)

133
freezer_ui/jobs/views.py Normal file
View File

@ -0,0 +1,133 @@
# 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.core.urlresolvers import reverse_lazy
from horizon import workflows
from horizon import browsers
from horizon import exceptions
import horizon_web_ui.freezer_ui.api.api as freezer_api
import horizon_web_ui.freezer_ui.jobs.browsers as project_browsers
import workflows.configure as configure_workflow
import workflows.action as action_workflow
from horizon_web_ui.freezer_ui.utils import create_dict_action
class JobWorkflowView(workflows.WorkflowView):
workflow_class = configure_workflow.ConfigureJob
def get_object(self, *args, **kwargs):
job_id = self.kwargs['backup_name']
try:
return freezer_api.job_get(self.request, job_id)
except Exception:
redirect = reverse("horizon:freezer_ui:jobs:index")
msg = _('Unable to retrieve details.')
exceptions.handle(self.request, msg, redirect=redirect)
def is_update(self):
return 'backup_name' in self.kwargs and \
bool(self.kwargs['backup_name'])
def get_initial(self):
initial = super(JobWorkflowView, self).get_initial()
if self.is_update():
initial.update({'original_name': None})
job = self.get_object()[0]
d = job.get_dict()
schedule = create_dict_action(**d['job_schedule'])
initial.update(**schedule)
info = {k: v for k, v in d.items()
if not k == 'job_schedule'}
initial.update(**info)
initial.update({'original_name': d.get('job_id', None)})
return initial
class JobsView(browsers.ResourceBrowserView):
browser_class = project_browsers.ContainerBrowser
template_name = "freezer_ui/jobs/browser.html"
def get_jobs_data(self):
jobs = []
try:
jobs = freezer_api.job_list(self.request)
except Exception:
msg = _('Unable to retrieve job file list.')
exceptions.handle(self.request, msg)
return jobs
def get_status_data(self):
job = []
try:
if self.kwargs['job_id']:
job = freezer_api.actions_in_job(
self.request, self.kwargs['job_id'])
except Exception:
msg = _('Unable to retrieve instances for this job.')
exceptions.handle(self.request, msg)
return job
class ActionWorkflowView(workflows.WorkflowView):
workflow_class = action_workflow.ConfigureAction
success_url = reverse_lazy("horizon:freezer_ui:jobs:index")
def get_context_data(self, **kwargs):
context = super(ActionWorkflowView, self).get_context_data(**kwargs)
job_id = self.kwargs['job_id']
context['job_id'] = job_id
return context
def get_object(self, *args, **kwargs):
ids = self.kwargs['job_id']
try:
action_id, job_id = ids.split('===')
except ValueError:
action_id = None
job_id = self.kwargs['job_id']
try:
return freezer_api.job_get(self.request, job_id)
except Exception:
redirect = reverse("horizon:freezer_ui:jobs:index")
msg = _('Unable to retrieve details.')
exceptions.handle(self.request, msg, redirect=redirect)
def is_update(self):
return 'job_id' in self.kwargs and \
bool(self.kwargs['job_id'])
def get_initial(self, **kwargs):
initial = super(ActionWorkflowView, self).get_initial()
try:
action_id, job_id = self.kwargs['job_id'].split('===')
except ValueError:
job_id = self.kwargs['job_id']
action_id = None
if self.is_update():
initial.update({'original_name': None})
job = self.get_object()[0]
d = job.get_dict()
for action in d['job_actions']:
if action['action_id'] == action_id:
actions = create_dict_action(**action)
rules = {k: v for k, v in action.items()
if not k == 'freezer_action'}
initial.update(**actions['freezer_action'])
initial.update(**rules)
initial.update({'original_name': job.id})
return initial

View File

@ -0,0 +1,580 @@
# 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 workflows
from horizon import exceptions
from horizon import forms
import horizon_web_ui.freezer_ui.api.api as freezer_api
class ActionConfigurationAction(workflows.Action):
action = forms.ChoiceField(
help_text=_("Set the action to be taken"),
required=True)
mode = forms.ChoiceField(
help_text=_("Choose what you want to backup"),
required=False)
original_name = forms.CharField(
widget=forms.HiddenInput(),
required=False)
action_id = forms.CharField(
widget=forms.HiddenInput(),
required=False)
backup_name = forms.CharField(
label=_("Backup Name"),
required=False)
mysql_conf = forms.CharField(
label=_("MySQL Configuration File"),
help_text=_("Set the path where the MySQL configuration file "
"is on the file system "),
required=False)
sql_server_conf = forms.CharField(
label=_("SQL Server Configuration File"),
help_text=_("Set the path where the SQL Server configuration file"
" is on the file system"),
required=False)
path_to_backup = forms.CharField(
label=_("Source File/Directory"),
help_text=_("The file or directory you want to back up."),
required=False)
container = forms.CharField(
label=_("Swift Container Name"),
required=False)
restore_abs_path = forms.CharField(
label=_("Restore Absolute Path"),
help_text=_("Set the absolute path where you"
" want your data restored."),
required=False)
restore_from_host = forms.CharField(
label=_("Restore From Host"),
help_text=_("Set the hostname used to identify the"
" data you want to restore from."
" If you want to restore data in the same"
" host where the backup was executed just"
" type from your shell: '$ hostname' and"
" the output is the value that needs to"
" be passed to this option. Mandatory"
" with Restore"),
required=False)
restore_from_date = forms.CharField(
label=_("Restore From Date"),
help_text=_("Set the absolute path where you want "
"your data restored.Please provide "
"datetime in format 'YYYY-MM-DDThh:mm:ss' "
"i.e. '1979-10-03T23:23:23'. Make sure the "
"'T' is between date and time"),
required=False)
cinder_vol_id = forms.CharField(
label=_("Cinder Volume ID"),
help_text=_("Id of cinder volume for backup"),
required=False)
nova_inst_id = forms.CharField(
label=_("Nova Volume ID"),
help_text=_("Id of nova instance for backup"),
required=False)
get_object = forms.CharField(
label=_("Get A Single Object"),
help_text=_("The Object name you want to download on "
"the local file system."),
required=False)
dst_file = forms.CharField(
label=_("Destination File"),
help_text=_("The file name used to save the object "
"on your local disk and upload file in swift."),
required=False)
remove_older_than = forms.CharField(
label=_("Remove Older Than"),
help_text=_("Checks in the specified container for"
" object older than the specified days."
"If i.e. 30 is specified, it will remove"
" the remote object older than 30 days."
" Default False (Disabled) The option "
"--remove-older-then is deprecated and "
"will be removed soon"),
required=False)
remove_from_date = forms.CharField(
label=_("Remove From Date"),
help_text=_("Checks the specified container and removes"
" objects older than the provided datetime"
" in the format YYYY-MM-DDThh:mm:ss "
"i.e. 1974-03-25T23:23:23. Make sure the "
"'T' is between date and time "),
required=False)
def clean(self):
cleaned_data = super(ActionConfigurationAction, self).clean()
if cleaned_data.get('action') == 'backup':
self._check_container(cleaned_data)
self._check_backup_name(cleaned_data)
self._check_path_to_backup(cleaned_data)
elif cleaned_data.get('action') == 'restore':
self._check_container(cleaned_data)
self._check_backup_name(cleaned_data)
self._check_restore_abs_path(cleaned_data)
return cleaned_data
def _check_restore_abs_path(self, cleaned_data):
if not cleaned_data.get('restore_abs_path'):
msg = _("You must define a path to restore.")
self._errors['restore_abs_path'] = self.error_class([msg])
def _check_container(self, cleaned_data):
if not cleaned_data.get('container'):
msg = _("You must define a container.")
self._errors['container'] = self.error_class([msg])
def _check_backup_name(self, cleaned_data):
if not cleaned_data.get('backup_name'):
msg = _("You must define an backup name.")
self._errors['backup_name'] = self.error_class([msg])
def _check_path_to_backup(self, cleaned_data):
if not cleaned_data.get('path_to_backup'):
msg = _("You must define a path to backup.")
self._errors['path_to_backup'] = self.error_class([msg])
def populate_mode_choices(self, request, context):
return [
('fs', _("File system")),
('mongo', _("MongoDB")),
('mysql', _("MySQL")),
('mssql', _("Microsoft SQL Server")),
('cinder', _("Cinder")),
('nova', _("Nova")),
]
def populate_action_choices(self, request, context):
return [
('', _("Select an action")),
('backup', _("Backup")),
('restore', _("Restore")),
('admin', _("Admin")),
]
def __init__(self, request, context, *args, **kwargs):
self.request = request
self.context = context
super(ActionConfigurationAction, self).__init__(
request, context, *args, **kwargs)
class Meta(object):
name = _("Action")
help_text_template = "freezer_ui/jobs" \
"/_action.html"
class ActionConfiguration(workflows.Step):
action_class = ActionConfigurationAction
contributes = ('action',
'mode',
'original_name',
'backup_name',
'mysql_conf',
'sql_server_conf',
'path_to_backup',
'container',
'restore_abs_path',
'restore_from_host',
'restore_from_date',
'cinder_vol_id',
'nova_inst_id',
'get_object',
'dst_file',
'remove_older_than',
'remove_from_date',
'original_name',
'action_id')
class SnapshotConfigurationAction(workflows.Action):
use_snapshot = forms.BooleanField(
label=_("Snapshot"),
help_text=_("Use a LVM or Shadow Copy snapshot "
"to have point in time consistent backups"),
widget=forms.CheckboxInput(),
initial=False,
required=False)
is_windows = forms.BooleanField(
label=_("Job For Windows"),
help_text=_("Is this job going to "
"execute on windows?"),
widget=forms.CheckboxInput(),
required=False)
vssadmin = forms.BooleanField(
label=_("VSSAdmin"),
help_text=_("Create a backup using a snapshot on windows "
"using vssadmin. Options are: "
"True and False, default is True"),
widget=forms.CheckboxInput(),
initial=True,
required=False)
lvm_auto_snap = forms.CharField(
label=_("LVM Auto Snapshot"),
help_text=_("Automatically guess the volume group and "
"volume name for given PATH."),
required=False)
lvm_srcvol = forms.CharField(
label=_("Set The Volume For Snapshot"),
help_text=_("Set the lvm volume you want to take a "
"snapshot from. Default no volume"),
required=False)
lvm_snapname = forms.CharField(
label=_("Set A Snapshot Name"),
help_text=_("Set the lvm snapshot name to use. "
"If the snapshot name already exists, "
"the old one will be used a no new one "
"will be created. Default freezer_backup_snap."),
required=False)
lvm_snapsize = forms.CharField(
label=_("Snapshot Size"),
help_text=_("Set the lvm snapshot size when creating "
"a new snapshot. Please add G for Gigabytes "
"or M for Megabytes, i.e. 500M or 8G. Default 5G."),
required=False)
lvm_dirmount = forms.CharField(
label=_("Snapshot Directory"),
help_text=_("Set the directory you want to mount "
"the lvm snapshot to. Default not set"),
required=False)
lvm_volgroup = forms.CharField(
label=_("Volume Group"),
help_text=_("Specify the volume group of your logical volume."
"This is important to mount your snapshot volume."
"Default not set"),
required=False)
class Meta(object):
name = _("Snapshot")
help_text_template = "freezer_ui/jobs" \
"/_snapshot.html"
class SnapshotConfiguration(workflows.Step):
action_class = SnapshotConfigurationAction
contributes = ('use_snapshot',
'is_windows',
'vssadmin',
'lvm_auto_snap',
'lvm_srcvol',
'lvm_snapname',
'lvm_snapsize',
'lvm_dirmount',
'lvm_volgroup',)
class AdvancedConfigurationAction(workflows.Action):
log_file = forms.CharField(
label=_("Log File Path"),
help_text=_("Set log file. By default logs to "
"/var/log/freezer.log If that file "
"is not writable, freezer tries to "
"log to ~/.freezer/freezer.log"),
required=False)
exclude = forms.CharField(
label=_("Exclude Files"),
help_text=_("Exclude files, given as a PATTERN.Ex:"
" '*.log, *.pyc' will exclude any "
"file with name ending with .log. "
"Default no exclude"),
widget=forms.widgets.Textarea(),
required=False)
proxy = forms.CharField(
label=_("Proxy URL"),
help_text=_("Enforce proxy that alters system "
"HTTP_PROXY and HTTPS_PROXY"),
widget=forms.URLInput(),
required=False)
os_auth_ver = forms.ChoiceField(
label=_("OpenStack Authentication Version"),
help_text=_("Swift auth version, could be 1, 2 or 3"),
required=False)
upload_limit = forms.IntegerField(
label=_("Upload Limit"),
help_text=_("Upload bandwidth limit in Bytes per sec."
" Can be invoked with dimensions "
"(10K, 120M, 10G)."),
initial=-1,
min_value=-1,
required=False)
download_limit = forms.IntegerField(
label=_("Download Limit"),
help_text=_("Download bandwidth limit in Bytes per sec. "
"Can be invoked with dimensions"
" (10K, 120M, 10G)."),
initial=-1,
min_value=-1,
required=False)
optimize = forms.ChoiceField(
choices=[('speed', _("Speed (tar)")),
('bandwidth', _("Bandwidth/Space (rsync)"))],
help_text="",
label=_('Optimize For...'),
required=False)
compression = forms.ChoiceField(
choices=[('gzip', _("Minimum Compression (GZip/Zip/Zlib)"))],
help_text="",
label=_('Compression Level'),
required=False)
max_segment_size = forms.IntegerField(
label=_("Maximum Segment Size"),
help_text=_("Set the maximum file chunk size in bytes"
" to upload to swift."
" Default 67108864 bytes (64MB)"),
initial=67108864,
min_value=1,
required=False)
hostname = forms.CharField(
label=_("Hostname"),
help_text=_("Set hostname to execute actions. If you are "
"executing freezer from one host but you want"
" to delete objects belonging to another host "
"then you can set this option that hostname and "
"execute appropriate actions. Default current "
"node hostname."),
required=False)
encryption_password = forms.CharField(
label=_("Encryption Key"),
help_text=_("Set the path where the encryption key"
"is on the file system"),
required=False)
no_incremental = forms.BooleanField(
label=_("No Incremental"),
help_text=_("Disable incremental feature. By default"
" freezer build the meta data even for "
"level 0 backup. By setting this option "
"incremental meta data is not created at all."
" Default disabled"),
widget=forms.CheckboxInput(),
initial=True,
required=False)
max_level = forms.IntegerField(
label=_("Max Level"),
initial=0,
min_value=0,
help_text=_("Set the backup level used with tar to implement"
" incremental backup. If a level 1 is specified "
"but no level 0 is already available, a level 0"
" will be done and subsequently backs to level 1."
" Default 0 (No Incremental)"),
required=False)
always_level = forms.IntegerField(
label=_("Always Level"),
initial=0,
min_value=0,
help_text=_("Set backup maximum level used with tar to"
" implement incremental backup. If a level "
"3 is specified, the backup will be executed "
"from level 0 to level 3 and to that point "
"always a backup level 3 will be executed. "
"It will not restart from level 0. This option "
"has precedence over --max-backup-level. "
"Default False (Disabled)"),
required=False)
restart_always_level = forms.IntegerField(
label=_("Restart Always Level"),
initial=0,
min_value=0,
help_text=_("Restart the backup from level 0 after n days. "
"Valid only if --always-level option if set. "
"If --always-level is used together with "
"--remove-older-then, there might be the "
"chance where the initial level 0 will be "
"removed Default False (Disabled)"),
required=False)
insecure = forms.BooleanField(
label=_("insecure"),
help_text=_("Allow to access swift servers without"
" checking SSL certs."),
widget=forms.CheckboxInput(),
required=False)
dereference_symlink = forms.BooleanField(
label=_("Follow Symlinks"),
help_text=_("Follow hard and soft links and archive "
"and dump the files they refer to. "
"Default False"),
widget=forms.CheckboxInput(),
required=False)
dry_run = forms.BooleanField(
label=_("Dry Run"),
help_text=_("Do everything except writing or "
"removing objects"),
widget=forms.CheckboxInput(),
required=False)
max_priority = forms.BooleanField(
label=_("Max Priority"),
help_text=_("Set the cpu process to the "
"highest priority (i.e. -20 "
"on Linux) and real-time for "
"I/O. The process priority "
"will be set only if nice and "
"ionice are installed Default "
"disabled. Use with caution."),
widget=forms.CheckboxInput(),
required=False)
quiet = forms.BooleanField(
label=_("Quiet"),
help_text=_("Suppress error messages"),
widget=forms.CheckboxInput(),
required=False)
def populate_os_auth_ver_choices(self, request, context):
return [
('2', _("v2")),
('1', _("v1")),
('3', _("v3")),
]
class Meta(object):
name = _("Advanced")
help_text_template = "freezer_ui/jobs" \
"/_advanced.html"
class AdvancedConfiguration(workflows.Step):
action_class = AdvancedConfigurationAction
contributes = ('log_file',
'exclude',
'proxy',
'os_auth_ver',
'upload_limit',
'download_limit',
'optimize',
'compression',
'max_segment_size',
'hostname',
'encryption_password',
'no_incremental',
'max_level',
'always_level',
'restart_always_level',
'insecure',
'dereference_symlink',
'dry_run',
'max_priority',
'quiet',)
class RulesConfigurationAction(workflows.Action):
max_retries = forms.IntegerField(
label=_("Max Retries"),
initial=0,
min_value=0,
help_text=_("In case of error, set the amount"
" of retries for this job"),
required=False)
max_retries_interval = forms.IntegerField(
label=_("Max Retries Interval"),
initial=0,
min_value=0,
help_text=_("Set the interval between intervals "
"for retries in seconds"),
required=False)
mandatory = forms.BooleanField(
label=_("Mandatory"),
help_text=_("Set this job as mandatory"),
widget=forms.CheckboxInput(),
required=False)
class Meta(object):
name = _("Rules")
class RulesConfiguration(workflows.Step):
action_class = RulesConfigurationAction
contributes = ('max_retries',
'max_retries_interval',
'mandatory')
class ConfigureAction(workflows.Workflow):
slug = "action"
name = _("Action Configuration")
finalize_button_name = _("Save")
success_message = _('Action file saved correctly.')
failure_message = _('Unable to save action file.')
success_url = "horizon:freezer_ui:jobs:index"
default_steps = (ActionConfiguration,
SnapshotConfiguration,
RulesConfiguration,
AdvancedConfiguration)
def handle(self, request, context):
try:
if context['is_windows']:
client_os = 'Windows'
else:
client_os = 'Linux'
if context['use_snapshot'] and client_os == 'Windows':
context['vssadmin'] = True
else:
context['vssadmin'] = False
if context['action_id'] == '':
return freezer_api.action_create(request, context)
else:
return freezer_api.action_update(request, context)
except Exception:
exceptions.handle(request)
return False

View File

@ -0,0 +1,231 @@
# 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 datetime
from horizon import exceptions
from horizon import forms
from horizon import workflows
import horizon_web_ui.freezer_ui.api.api as freezer_api
class ClientsConfigurationAction(workflows.Action):
client_id = forms.ChoiceField(
help_text=_("Set the client for this job"),
required=True)
def populate_client_id_choices(self, request, context):
clients = []
try:
clients = freezer_api.client_list(request)
except Exception:
exceptions.handle(request, _('Error getting client list'))
client_id = [(c.client_id, c.hostname) for c in clients]
client_id.insert(0, ('', _('Select A Client')))
return client_id
class Meta(object):
name = _("Clients")
slug = "clients"
class ClientsConfiguration(workflows.Step):
action_class = ClientsConfigurationAction
contributes = ('client_id',)
class ActionsConfigurationAction(workflows.MembershipAction):
def __init__(self, request, *args, **kwargs):
super(ActionsConfigurationAction, self).__init__(request,
*args,
**kwargs)
original_name = args[0].get('original_name', None)
err_msg = _('Unable to retrieve list of actions.')
if original_name:
default_role_field_name = self.get_default_role_field_name()
self.fields[default_role_field_name] = \
forms.CharField(required=False,
widget=forms.HiddenInput())
self.fields[default_role_field_name].initial = 'member'
actions = self.get_member_field_name('member')
self.fields[actions] = \
forms.MultipleChoiceField(required=False,
widget=forms.HiddenInput())
else:
default_role_field_name = self.get_default_role_field_name()
self.fields[default_role_field_name] = \
forms.CharField(required=False)
self.fields[default_role_field_name].initial = 'member'
actions = self.get_member_field_name('member')
self.fields[actions] = forms.MultipleChoiceField(required=False)
all_actions = []
try:
all_actions = freezer_api.action_list(request)
except Exception:
exceptions.handle(request, err_msg)
all_actions = [(a.action_id, a.freezer_action['backup_name'])
for a in all_actions]
self.fields[actions].choices = all_actions
initial_actions = []
if request.method == 'POST':
return
try:
if original_name:
configured_actions = \
freezer_api.actions_in_job(request, original_name)
initial_actions = [a.action_id for a in configured_actions]
except Exception:
exceptions.handle(request, err_msg)
self.fields[actions].initial = initial_actions
class Meta(object):
name = _("Actions")
slug = "selected_actions"
help_text_template = "freezer_ui/jobs" \
"/_actions.html"
class ActionsConfiguration(workflows.UpdateMembersStep):
action_class = ActionsConfigurationAction
help_text = _(
"Select the clients that will be backed up using this configuration.")
available_list_title = _("All Actions")
members_list_title = _("Selected Actions")
no_available_text = _("No actions found.")
no_members_text = _("No actions selected.")
show_roles = False
contributes = ("actions",)
def contribute(self, data, context):
if data:
member_field_name = self.get_member_field_name('member')
context['actions'] = data.get(member_field_name, [])
return context
class SchedulingConfigurationAction(workflows.Action):
start_datetime = forms.CharField(
label=_("Start Date and Time"),
required=False,
help_text=_(""))
interval = forms.CharField(
label=_("Interval"),
required=False,
help_text=_("Repeat this configuration in a minutes interval."))
end_datetime = forms.CharField(
label=_("End Date and Time"),
required=False,
help_text=_(""))
def __init__(self, request, context, *args, **kwargs):
self.request = request
self.context = context
super(SchedulingConfigurationAction, self).__init__(
request, context, *args, **kwargs)
def clean(self):
cleaned_data = super(SchedulingConfigurationAction, 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('start_datetime')):
msg = _("Start date time is not in ISO format.")
self._errors['start_datetime'] = 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('end_datetime')):
msg = _("End date time is not in ISO format.")
self._errors['end_datetime'] = self.error_class([msg])
class Meta(object):
name = _("Scheduling")
slug = "scheduling"
help_text_template = "freezer_ui/jobs" \
"/_scheduling.html"
class SchedulingConfiguration(workflows.Step):
action_class = SchedulingConfigurationAction
contributes = ('start_datetime',
'interval',
'end_datetime')
class InfoConfigurationAction(workflows.Action):
description = forms.CharField(
label=_("Job Name"),
help_text=_("Set a short description for this job"),
required=True)
original_name = forms.CharField(
widget=forms.HiddenInput(),
required=False)
class Meta(object):
name = _("Job Info")
slug = "info"
class InfoConfiguration(workflows.Step):
action_class = InfoConfigurationAction
contributes = ('description',
'original_name')
class ConfigureJob(workflows.Workflow):
slug = "job"
name = _("Job Configuration")
finalize_button_name = _("Save")
success_message = _('Job file saved correctly.')
failure_message = _('Unable to save job file.')
success_url = "horizon:freezer_ui:jobs:index"
default_steps = (InfoConfiguration,
ClientsConfiguration,
SchedulingConfiguration)
def handle(self, request, context):
try:
if context['original_name'] == '':
return freezer_api.job_create(request, context)
else:
return freezer_api.job_edit(request, context)
except Exception:
exceptions.handle(request)
return False

View File

@ -1 +0,0 @@
__author__ = 'jonas'

View File

@ -1,3 +0,0 @@
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

View File

@ -1,24 +0,0 @@
# 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 horizon_web_ui.freezer_ui import dashboard
class OverviewPanel(horizon.Panel):
name = _("Overview")
slug = "overview"
dashboard.Freezer.register(OverviewPanel)

View File

@ -1,112 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Backup Restore DR Overview" %}{% endblock %}
{% block css %}
{% include "_stylesheets.html" %}
<link href='{{ STATIC_URL }}freezer/css/freezer.css' type='text/css' media='screen' rel='stylesheet' />
{% endblock %}
<noscript>
{% trans "This pane needs javascript support." %}
</noscript>
{% block main %}
<div class="quota-dynamic">
<h3 class="quota-heading">{% trans "Overview" %}</h3>
<div class="d3_quota_bar">
<div class="d3_pie_chart_usage" data-used="30"></div>
<strong>{% trans "Nodes without backup in the last week" %}<br />
18 of 50
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart_usage" data-used="70"></div>
<strong>{% trans "Total space used for backups" %}<br />
150gb of 200gb
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart_usage" data-used="0"></div>
<strong>{% trans "Storage price per month" %}<br />
60 dls
</strong>
</div>
</div>
<div class="quota-dynamic">
<h3 class="quota-heading">{% trans "Backup Summary" %}</h3>
<div class="d3_quota_bar">
<div class="d3_pie_chart_usage" data-used="50"></div>
<strong>{% trans "Backups older than 1 week" %}<br />
60 of 120
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart_usage" data-used="7"></div>
<strong>{% trans "Average backup size" %}<br />
7gb for 18 backups
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart_usage" data-used="18"></div>
<strong>{% trans "Average backup time" %}<br />
18 min
</strong>
</div>
</div>
<div class="quota-dynamic">
<h3 class="quota-heading">{% trans "Restore Summary" %}</h3>
<div class="d3_quota_bar">
<div class="d3_pie_chart_usage" data-used="7"></div>
<strong>{% trans "Average restore time" %}<br />
15 min
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart_usage" data-used="0"></div>
<strong>{% trans "Restore ratio success to failure" %}<br />
100% success
</strong>
</div>
</div>
<div class="quota-dynamic">
<h3 class="quota-heading">{% trans "OS Summary" %}</h3>
</div>
<div id='dashboard'>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type='text/javascript' src='{{ STATIC_URL }}freezer/js/dashboard.js'></script>
<script type="text/javascript">
var freqData=[
{State:'Linux',freq:{binary:6902, text:12085, media:500}}
,{State:'Windows',freq:{binary:6500, text:5200, media:100}}
,{State:'Mac OS',freq:{binary:200, text:1000, media:1500}}
];
dashboard('#dashboard',freqData);
</script>
{% endblock %}

View File

@ -1,21 +0,0 @@
# 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.views import generic
class IndexView(generic.TemplateView):
name = _("Overview")
slug = "overview"
template_name = ("freezer_ui/overview/overview.html")

View File

@ -0,0 +1,131 @@
function hideEverything() {
// Common controls
$("#id_backup_name").closest(".form-group").hide();
$("#id_container").closest(".form-group").hide();
$("#id_path_to_backup").closest(".form-group").hide();
// Backup specific controls
$("#id_mysql_conf").closest(".form-group").hide();
$("#id_mode").closest(".form-group").hide();
$("#id_sql_server_conf").closest(".form-group").hide();
$("#id_cinder_vol_id").closest(".form-group").hide();
$("#id_nova_inst_id").closest(".form-group").hide();
// Restore specific controls
$("#id_restore_abs_path").closest(".form-group").hide();
$("#id_restore_from_host").closest(".form-group").hide();
$("#id_restore_from_date").closest(".form-group").hide();
// Admin specific controls
$("#id_remove_older_than").closest(".form-group").hide();
$("#id_remove_from_date").closest(".form-group").hide();
$("#id_get_object").closest(".form-group").hide();
$("#id_dst_file").closest(".form-group").hide();
}
function showAdminOptions() {
$("#id_remove_older_than").closest(".form-group").show();
$("#id_remove_from_date").closest(".form-group").show();
$("#id_get_object").closest(".form-group").show();
$("#id_dst_file").closest(".form-group").show();
}
function showBackupOptions() {
$("#id_is_windows").closest(".form-group").show();
$("#id_mode").closest(".form-group").show();
$("#id_container").closest(".form-group").show();
$("#id_path_to_backup").closest(".form-group").show();
$("#id_backup_name").closest(".form-group").show();
}
function showRestoreOptions() {
$("#id_container").closest(".form-group").show();
$("#id_backup_name").closest(".form-group").show();
$("#id_restore_abs_path").closest(".form-group").show();
$("#id_restore_from_host").closest(".form-group").show();
$("#id_restore_from_date").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();
}
hideEverything();
$("#id_action").change(function() {
// Update the inputs according freezer action
if ($("#id_action").val() == 'backup') {
hideEverything();
showBackupOptions();
}
else if ($("#id_action").val() == 'restore') {
hideEverything();
showRestoreOptions();
}
else if ($("#id_action").val() == 'admin') {
hideEverything();
showAdminOptions();
}
else {
hideEverything();
}
});
$("#id_mode").change(function() {
if ($("#id_action").val() == 'backup') {
if ($("#id_mode").val() == 'fs') {
hideEverything();
showBackupOptions();
$("#id_advanced_configuration").closest(".form-group").show();
}
else if ($("#id_mode").val() == 'mysql') {
hideEverything();
showBackupOptions();
$("#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') {
hideEverything();
showBackupOptions();
$("#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') {
hideEverything();
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') {
hideEverything();
showCinderOptions();
$("#id_cinder_vol_id").closest(".form-group").show().addClass("required");
$("#id_advanced_configuration").closest(".form-group").show();
}
else if ($("#id_mode").val() == 'nova') {
hideEverything();
showNovaOptions();
$("#id_nova_inst_id").closest(".form-group").show().addClass("required");
$("#id_advanced_configuration").closest(".form-group").show();
}
else {
}
}
});

View File

@ -0,0 +1,21 @@
function hideIncrementalOptions() {
$("#id_max_level").closest(".form-group").hide();
$("#id_always_level").closest(".form-group").hide();
$("#id_restart_always_level").closest(".form-group").hide();
}
$("#id_no_incremental").click(function() {
if ($("#id_no_incremental").is(":checked")) {
$("#id_max_level").closest(".form-group").hide();
$("#id_always_level").closest(".form-group").hide();
$("#id_restart_always_level").closest(".form-group").hide();
}
else {
$("#id_max_level").closest(".form-group").show();
$("#id_always_level").closest(".form-group").show();
$("#id_restart_always_level").closest(".form-group").show();
}
});
hideIncrementalOptions();

View File

@ -0,0 +1,81 @@
function hideOptions() {
// Snapshot specific controls
$("#id_is_windows").closest(".form-group").hide();
$("#id_lvm_auto_snap").closest(".form-group").hide();
$("#id_lvm_srcvol").closest(".form-group").hide();
$("#id_lvm_snapname").closest(".form-group").hide();
$("#id_lvm_snapsize").closest(".form-group").hide();
$("#id_lvm_dirmount").closest(".form-group").hide();
$("#id_lvm_volgroup").closest(".form-group").hide();
$("#id_vssadmin").closest(".form-group").hide();
}
function is_windows() {
if ($("#id_is_windows").is(":checked")) {
return true;
}
}
function showWindowsSnapshotOptions() {
$("#id_vssadmin").closest(".form-group").show();
}
function hideWindowsSnapshotOptions() {
$("#id_vssadmin").closest(".form-group").hide();
}
function showLinuxSnapshotOptions() {
$("#id_lvm_auto_snap").closest(".form-group").show();
$("#id_lvm_srcvol").closest(".form-group").show();
$("#id_lvm_snapname").closest(".form-group").show();
$("#id_lvm_snapsize").closest(".form-group").show();
$("#id_lvm_dirmount").closest(".form-group").show();
$("#id_lvm_volgroup").closest(".form-group").show();
}
function hideLinuxSnapshotOptions() {
$("#id_lvm_srcvol").closest(".form-group").hide();
$("#id_lvm_snapname").closest(".form-group").hide();
$("#id_lvm_snapsize").closest(".form-group").hide();
$("#id_lvm_dirmount").closest(".form-group").hide();
$("#id_lvm_volgroup").closest(".form-group").hide();
$("#id_lvm_auto_snap").closest(".form-group").hide();
}
function hideSnapshotOptions() {
hideWindowsSnapshotOptions();
hideLinuxSnapshotOptions();
$("#id_is_windows").closest(".form-group").hide();
}
function showSnapshotOptions() {
$("#id_is_windows").closest(".form-group").show();
if (is_windows()) {
hideLinuxSnapshotOptions();
showWindowsSnapshotOptions();
}
else {
hideWindowsSnapshotOptions();
showLinuxSnapshotOptions();
}
}
hideOptions();
$("#id_use_snapshot").click(function() {
if ($("#id_use_snapshot").is(":checked")) {
showSnapshotOptions();
}
else {
hideSnapshotOptions();
}
});
$("#id_is_windows").click(function() {
if ($("#id_use_snapshot").is(":checked")) {
showSnapshotOptions();
}
else {
hideSnapshotOptions();
}
});

View File

@ -0,0 +1,361 @@
/* Launch Jobs workflow */
function hideEverything() {
// Common controls
$("#id_is_windows").closest(".form-group").hide();
$("#id_backup_name").closest(".form-group").hide();
$("#id_container").closest(".form-group").hide();
$("#id_path_to_backup").closest(".form-group").hide();
$("#id_insecure").closest(".form-group").hide();
$("#id_os_auth_ver").closest(".form-group").hide();
$("#id_dry_run").closest(".form-group").hide();
$("#id_encryption_password").closest(".form-group").hide();
$("#id_exclude").closest(".form-group").hide();
$("#id_log_file").closest(".form-group").hide();
$("#id_proxy").closest(".form-group").hide();
$("#id_max_priority").closest(".form-group").hide();
$("#id_quiet").closest(".form-group").hide();
$("#id_advanced_configuration").closest(".form-group").hide();
$("#id_description").closest(".form-group").hide();
$("#id_client_id").closest(".form-group").hide();
$("#id_tag").closest(".form-group").hide();
// Backup specific controls
$("#id_mysql_conf").closest(".form-group").hide();
$("#id_compression").closest(".form-group").hide();
$("#id_optimize").closest(".form-group").hide();
$("#id_upload_limit").closest(".form-group").hide();
$("#id_dereference_symlink").closest(".form-group").hide();
$("#id_max_segment_size").closest(".form-group").hide();
$("#id_dst_file").closest(".form-group").hide();
$("#id_mode").closest(".form-group").hide();
$("#id_sql_server_conf").closest(".form-group").hide();
$("#id_cinder_vol_id").closest(".form-group").hide();
$("#id_nova_inst_id").closest(".form-group").hide();
$("#id_max_level").closest(".form-group").hide();
$("#id_always_level").closest(".form-group").hide();
$("#id_restart_always_level").closest(".form-group").hide();
$("#id_no_incremental").closest(".form-group").hide();
$("#id_hostname").closest(".form-group").hide();
$("#id_upload").closest(".form-group").hide();
// Snapshot specific controls
$("#id_use_snapshot").closest(".form-group").hide();
$("#id_lvm_auto_snap").closest(".form-group").hide();
$("#id_lvm_srcvol").closest(".form-group").hide();
$("#id_lvm_snapname").closest(".form-group").hide();
$("#id_lvm_snapsize").closest(".form-group").hide();
$("#id_lvm_dirmount").closest(".form-group").hide();
$("#id_lvm_volgroup").closest(".form-group").hide();
$("#id_vssadmin").closest(".form-group").hide();
// Restore specific controls
$("#id_download_limit").closest(".form-group").hide();
$("#id_restore_abs_path").closest(".form-group").hide();
$("#id_restore_from_host").closest(".form-group").hide();
$("#id_restore_from_date").closest(".form-group").hide();
// Admin specific controls
$("#id_remove_older_than").closest(".form-group").hide();
$("#id_remove_from_date").closest(".form-group").hide();
$("#id_get_object").closest(".form-group").hide();
}
function showAdminOptions() {
$("#id_remove_older_than").closest(".form-group").show();
$("#id_remove_from_date").closest(".form-group").show();
$("#id_get_object").closest(".form-group").show();
}
function showBackupOptions() {
$("#id_is_windows").closest(".form-group").show();
$("#id_mode").closest(".form-group").show();
$("#id_upload").closest(".form-group").show();
$("#id_container").closest(".form-group").show();
$("#id_use_snapshot").closest(".form-group").show();
$("#id_path_to_backup").closest(".form-group").show();
$("#id_backup_name").closest(".form-group").show();
$("#id_dereference_symlink").closest(".form-group").show();
$("#id_os_auth_ver").closest(".form-group").show();
$("#id_upload_limit").closest(".form-group").show();
$("#id_max_priority").closest(".form-group").show();
$("#id_quiet").closest(".form-group").show();
$("#id_proxy").closest(".form-group").show();
$("#id_log_file").closest(".form-group").show();
$("#id_exclude").closest(".form-group").show();
$("#id_optimize").closest(".form-group").show();
$("#id_compression").closest(".form-group").show();
$("#id_encryption_password").closest(".form-group").show();
$("#id_max_segment_size").closest(".form-group").show();
$("#id_no_incremental").closest(".form-group").show();
$("#id_hostname").closest(".form-group").show();
$("#id_container").closest(".form-group").addClass("required");
}
function showRestoreOptions() {
$("#id_container").closest(".form-group").show();
$("#id_backup_name").closest(".form-group").show();
$("#id_restore_abs_path").closest(".form-group").show();
$("#id_restore_from_host").closest(".form-group").show();
$("#id_restore_from_date").closest(".form-group").show();
$("#id_os_auth_ver").closest(".form-group").show();
$("#id_download_limit").closest(".form-group").show();
$("#id_max_priority").closest(".form-group").show();
$("#id_quiet").closest(".form-group").show();
$("#id_proxy").closest(".form-group").show();
$("#id_log_file").closest(".form-group").show();
}
function showLinuxSnapshotOptions() {
$("#id_lvm_auto_snap").closest(".form-group").show();
$("#id_lvm_srcvol").closest(".form-group").show();
$("#id_lvm_snapname").closest(".form-group").show();
$("#id_lvm_snapsize").closest(".form-group").show();
$("#id_lvm_dirmount").closest(".form-group").show();
$("#id_lvm_volgroup").closest(".form-group").show();
}
function hideLinuxSnapshotOptions() {
$("#id_lvm_srcvol").closest(".form-group").hide();
$("#id_lvm_snapname").closest(".form-group").hide();
$("#id_lvm_snapsize").closest(".form-group").hide();
$("#id_lvm_dirmount").closest(".form-group").hide();
$("#id_lvm_volgroup").closest(".form-group").hide();
$("#id_lvm_auto_snap").closest(".form-group").hide();
}
function showWindowsSnapshotOptions() {
// TODO: windows doesn't need to display this option
// because is redundant
$("#id_vssadmin").closest(".form-group").show();
}
function hideWindowsSnapshotOptions() {
$("#id_vssadmin").closest(".form-group").hide();
}
function is_windows() {
if ($("#id_is_windows").is(":checked")) {
return true;
}
}
function showSnapshotOptions() {
if (is_windows()) {
hideLinuxSnapshotOptions();
showWindowsSnapshotOptions();
}
else {
hideWindowsSnapshotOptions();
showLinuxSnapshotOptions();
}
}
function hideSnapshotOptions() {
hideWindowsSnapshotOptions();
hideLinuxSnapshotOptions();
}
function showNovaOptions() {
$("#id_client_id").closest(".form-group").show();
$("#id_mode").closest(".form-group").show();
$("#id_proxy").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();
$("#id_nova_inst_id").closest(".form-group").show().addClass("required");
}
function showCinderOptions() {
$("#id_client_id").closest(".form-group").show();
$("#id_mode").closest(".form-group").show();
$("#id_proxy").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();
$("#id_path_to_backup").closest(".form-group").show();
$("#id_cinder_vol_id").closest(".form-group").show().addClass("required");
}
function showIncrementalOptions() {
$("#id_max_level").closest(".form-group").show();
$("#id_always_level").closest(".form-group").show();
$("#id_restart_always_level").closest(".form-group").show();
}
function hideIncrementalOptions() {
$("#id_max_level").closest(".form-group").hide();
$("#id_always_level").closest(".form-group").hide();
$("#id_restart_always_level").closest(".form-group").hide();
$("#id_advanced_configuration").closest(".form-group").show();
}
function showAdvancedConfigurationOptions() {
hideEverything();
showBackupOptions();
$("#id_tag").closest(".form-group").show();
}
function hideAdvancedConfigurationOptions() {
hideEverything();
hideIncrementalOptions();
$("#id_backup_name").closest(".form-group").show();
$("#id_container").closest(".form-group").show();
$("#id_action").closest(".form-group").show();
$("#id_path_to_backup").closest(".form-group").show();
$("#id_advanced_configuration").closest(".form-group").show();
$("#id_mode").closest(".form-group").show();
$("#id_tag").closest(".form-group").hide();
}
function hideRestoreAdvancedConfigurationOptions() {
hideEverything();
$("#id_backup_name").closest(".form-group").show();
$("#id_container").closest(".form-group").show();
$("#id_action").closest(".form-group").show();
$("#id_advanced_configuration").closest(".form-group").show();
$("#id_restore_abs_path").closest(".form-group").show();
$("#id_restore_from_host").closest(".form-group").show();
$("#id_restore_from_date").closest(".form-group").show();
}
function showRestoreAdvancedConfigurationOptions() {
hideEverything();
showRestoreOptions();
}
hideEverything();
$("#id_action").change(function() {
// Update the inputs according freezer action
if ($("#id_action").val() == 'backup') {
hideEverything();
showBackupOptions();
hideAdvancedConfigurationOptions();
$("#id_description").closest(".form-group").show();
$("#id_client_id").closest(".form-group").show();
}
else if ($("#id_action").val() == 'restore') {
hideEverything();
showRestoreOptions();
hideRestoreAdvancedConfigurationOptions();
$("#id_description").closest(".form-group").show();
$("#id_client_id").closest(".form-group").show();
}
else if ($("#id_action").val() == 'admin') {
hideEverything();
showAdminOptions();
$("#id_client_id").closest(".form-group").show();
$("#id_description").closest(".form-group").show();
}
else if ($("#id_action").val() == 'info') {
hideEverything();
}
else {
hideEverything();
}
});
$("#id_use_snapshot").click(function() {
if ($("#id_use_snapshot").is(":checked")) {
showSnapshotOptions();
}
else {
hideSnapshotOptions();
}
});
$("#id_is_windows").click(function() {
if ($("#id_use_snapshot").is(":checked")) {
showSnapshotOptions();
}
else {
hideSnapshotOptions();
}
});
$("#id_mode").change(function() {
if ($("#id_action").val() == 'backup') {
if ($("#id_mode").val() == 'fs') {
hideEverything();
showBackupOptions();
showSnapshotOptions();
$("#id_advanced_configuration").closest(".form-group").show();
}
else if ($("#id_mode").val() == 'mysql') {
hideEverything();
showBackupOptions();
showSnapshotOptions();
$("#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') {
hideEverything();
showBackupOptions();
showSnapshotOptions();
$("#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() == 'cinder') {
hideEverything();
showCinderOptions();
$("#id_cinder_vol_id").closest(".form-group").show().addClass("required");
$("#id_advanced_configuration").closest(".form-group").show();
}
else if ($("#id_mode").val() == 'nova') {
hideEverything();
showNovaOptions();
$("#id_nova_inst_id").closest(".form-group").show().addClass("required");
$("#id_advanced_configuration").closest(".form-group").show();
}
else {
}
}
});
$("#id_upload").change(function() {
if ($("#id_upload").is(":checked")) {
$("#id_container").closest(".form-group").show().addClass("required");
}
else {
$("#id_container").closest(".form-group").hide().removeClass("required");
}
});
$("#id_no_incremental").click(function() {
if ($("#id_no_incremental").is(":checked")) {
hideIncrementalOptions();
}
else {
showIncrementalOptions();
}
});
$("#id_advanced_configuration").click(function() {
if ($("#id_action").val() == 'backup') {
if ($("#id_advanced_configuration").is(":checked")) {
showAdvancedConfigurationOptions();
$("#id_advanced_configuration").closest(".form-group").show();
}
else {
hideAdvancedConfigurationOptions();
}
}
else if ($("#id_action").val() == 'restore') {
if ($("#id_advanced_configuration").is(":checked")) {
showRestoreAdvancedConfigurationOptions();
$("#id_advanced_configuration").closest(".form-group").show();
}
else {
hideRestoreAdvancedConfigurationOptions();
}
}
});

View File

@ -10,13 +10,10 @@
# 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 horizon_web_ui.freezer_ui.overview import views
urlpatterns = patterns(
'',
url(r'^$', views.IndexView.as_view(), name='index'),
)
def create_dict_action(**kwargs):
"""
Create a dict only with values that exists so we avoid send keys with
None values
"""
return {k: v for k, v in kwargs.items() if v}