Add flavor saving
Properly save the flavor of the role. Also bring back the autosave. Also add menu for non-drag-and-drop adding. Change-Id: Ie977aa2c1eb23b26589a881b65db6840046b5c59
This commit is contained in:
parent
05e505c5c6
commit
7d8210b65a
@ -2,3 +2,6 @@ PANEL = 'overview'
|
||||
PANEL_DASHBOARD = 'infrastructure'
|
||||
PANEL_GROUP = 'infrastructure'
|
||||
ADD_PANEL = 'tuskar_boxes.overview.panel.Overview'
|
||||
ADD_INSTALLED_APPS = [
|
||||
'tuskar_boxes',
|
||||
]
|
||||
|
59
tuskar_boxes/overview/forms.py
Normal file
59
tuskar_boxes/overview/forms.py
Normal file
@ -0,0 +1,59 @@
|
||||
# -*- coding: utf8 -*-
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
import django.forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import horizon.exceptions
|
||||
from tuskar_ui.infrastructure.overview import forms
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EditPlan(forms.EditPlan):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditPlan, self).__init__(*args, **kwargs)
|
||||
self.fields.update(self._role_flavor_fields(self.plan))
|
||||
|
||||
def _role_flavor_fields(self, plan):
|
||||
fields = {}
|
||||
for role in plan.role_list:
|
||||
field = django.forms.CharField(
|
||||
label=_("Flavor for {0}").format(role.name),
|
||||
initial=role.flavor(plan).name if role.flavor(plan) else '',
|
||||
required=False,
|
||||
widget=django.forms.HiddenInput(attrs={
|
||||
'class': "boxes-flavor",
|
||||
}),
|
||||
)
|
||||
field.role = role
|
||||
fields['%s-flavor' % role.id] = field
|
||||
return fields
|
||||
|
||||
|
||||
def handle(self, request, data):
|
||||
result = super(EditPlan, self).handle(request, data)
|
||||
parameters = dict(
|
||||
(field.role.flavor_parameter_name, data[name])
|
||||
for (name, field) in self.fields.items() if name.endswith('-flavor')
|
||||
)
|
||||
try:
|
||||
self.plan = self.plan.patch(request, self.plan.uuid, parameters)
|
||||
except Exception as e:
|
||||
horizon.exceptions.handle(request, _("Unable to update the plan."))
|
||||
LOG.exception(e)
|
||||
return False
|
||||
return result
|
@ -14,6 +14,7 @@
|
||||
|
||||
from tuskar_ui import api
|
||||
from tuskar_ui.infrastructure.overview import views
|
||||
from tuskar_boxes.overview import forms
|
||||
|
||||
|
||||
def flavor_nodes(request, flavor):
|
||||
@ -27,25 +28,38 @@ def flavor_nodes(request, flavor):
|
||||
]):
|
||||
yield node
|
||||
|
||||
|
||||
class IndexView(views.IndexView):
|
||||
template_name = "tuskar_boxes/overview/index.html"
|
||||
form_class = forms.EditPlan
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super(IndexView, self).get_context_data(*args, **kwargs)
|
||||
flavors = api.flavor.Flavor.list(self.request)
|
||||
flavors.sort(key=lambda np: (np.vcpus, np.ram, np.disk))
|
||||
for role in context['roles']:
|
||||
flavor = role['role'].flavor(context['plan'])
|
||||
role['flavor_name'] = flavor.name if flavor else ''
|
||||
context['flavors'] = []
|
||||
for flavor in flavors:
|
||||
nodes = [{
|
||||
'role': '',
|
||||
} for node in flavor_nodes(self.request, flavor)]
|
||||
roles = [role for role in context['roles']
|
||||
if role['flavor_name'] == flavor.name]
|
||||
flavor = {
|
||||
'name': flavor.name,
|
||||
'vcpus': flavor.vcpus,
|
||||
'ram': flavor.ram,
|
||||
'disk': flavor.disk,
|
||||
'nodes': nodes,
|
||||
'roles': roles,
|
||||
}
|
||||
if nodes: # Don't list empty flavors
|
||||
if nodes or roles: # Don't list empty flavors
|
||||
context['flavors'].append(flavor)
|
||||
context['free_roles'] = [role for role in context['roles']
|
||||
if not role['flavor_name']]
|
||||
if not context['stack']:
|
||||
for role in context['roles']:
|
||||
role['flavor_field'] = context['form'][role['id'] + '-flavor']
|
||||
return context
|
||||
|
@ -3,14 +3,14 @@
|
||||
{% load form_helpers %}
|
||||
|
||||
<h4>{% trans "Available Deployment Roles" %}</h4>
|
||||
<form method="POST" action="." class="boxes-form">
|
||||
<form method="POST" action="." class="deployment-roles-form">
|
||||
{% csrf_token %}
|
||||
{% include 'horizon/common/_form_errors.html' with form=form %}
|
||||
|
||||
<div class="boxes-available-roles">
|
||||
{% for role in roles %}{% spaceless %}
|
||||
{% for role in free_roles %}{% spaceless %}
|
||||
<div class="boxes-role-{{ role.name|slugify }} boxes-role" data-name="{{ role.name|slugify }}">
|
||||
{{ role.field|add_bootstrap_class }}
|
||||
{{ role.flavor_field|add_bootstrap_class }}
|
||||
{{ role.name|title }}
|
||||
</div>
|
||||
{% endspaceless %}{% endfor %}
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
<h4>{% trans "Available Node Profiles" %}</h4>
|
||||
{% for flavor in flavors %}
|
||||
<div class="boxes-flavor">
|
||||
<div class="boxes-flavor" data-flavor="{{ flavor.name }}">
|
||||
<p>
|
||||
<strong>Node Profile:</strong>
|
||||
<i>{{ flavor.name }}</i>
|
||||
@ -29,9 +29,24 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-5">
|
||||
<div class="boxes-drop-roles">
|
||||
{% for role in flavor.roles %}{% spaceless %}
|
||||
<div class="boxes-role-{{ role.name|slugify }} boxes-role" data-name="{{ role.name|slugify }}">
|
||||
{{ role.field|add_bootstrap_class }}
|
||||
{{ role.flavor_field|add_bootstrap_class }}
|
||||
{{ role.name|title }}
|
||||
</div>
|
||||
{% endspaceless %}{% endfor %}
|
||||
</div>
|
||||
<div class="boxes-drop">
|
||||
<i class="fa fa-plus"></i>
|
||||
<div><i class="fa fa-plus"></i><br>
|
||||
{% trans "enroll a deployment role" %}</div>
|
||||
<div class="boxes-roles-menu btn-group-vertical">
|
||||
{% for role in roles %}
|
||||
<div class="btn" data-role="{{ role.name }}">
|
||||
{{ role.name|title }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-7 boxes-nodes">
|
||||
@ -91,7 +106,7 @@
|
||||
$('div.boxes-role').draggable({
|
||||
revert: 'invalid',
|
||||
helper: 'clone',
|
||||
stack: '.boxes-roles',
|
||||
zIndex: 1000,
|
||||
opacity: 0.5
|
||||
});
|
||||
$('div.boxes-drop').droppable({
|
||||
@ -101,7 +116,11 @@
|
||||
tolerance: 'touch',
|
||||
drop: function (ev, ui) {
|
||||
ui.draggable.appendTo($(this).prev('.boxes-drop-roles'));
|
||||
ui.draggable.find('input.number-picker').val(1);
|
||||
var $count = ui.draggable.find('input.number-picker');
|
||||
if (+$count.val() < 1) { $count.val(1); }
|
||||
ui.draggable.find('input.boxes-flavor'
|
||||
).val($(this).closest('.boxes-flavor').data('flavor'));
|
||||
$count.trigger('change');
|
||||
window.setTimeout(update_boxes, 0);
|
||||
}
|
||||
});
|
||||
@ -112,17 +131,41 @@
|
||||
tolerance: 'touch',
|
||||
drop: function (ev, ui) {
|
||||
ui.draggable.appendTo(this);
|
||||
ui.draggable.find('input.number-picker').val(0);
|
||||
ui.draggable.find('input.boxes-flavor').val('');
|
||||
ui.draggable.find('input.number-picker').trigger('change').val(0);
|
||||
window.setTimeout(update_boxes, 0);
|
||||
}
|
||||
});
|
||||
|
||||
update_boxes();
|
||||
$('input.number-picker').change(update_boxes);
|
||||
|
||||
$('.boxes-roles-menu').hide().parent().click(function () {
|
||||
$(this).find('.boxes-roles-menu').toggle('fold', 100);
|
||||
});
|
||||
$('.boxes-roles-menu .btn').click(function () {
|
||||
var name = $(this).data('role');
|
||||
var $drop = $(this).closest('.boxes-drop').prev('.boxes-drop-roles');
|
||||
var $role = $('.boxes-role[data-name="' + name + '"]');
|
||||
var $count = $role.find('input.number-picker');
|
||||
var $flavor = $role.find('input.boxes-flavor');
|
||||
$role.appendTo($drop);
|
||||
if (+$count.val() < 1) { $count.val(1); }
|
||||
$flavor.val($drop.closest('.boxes-flavor').data('flavor'));
|
||||
$count.trigger('change');
|
||||
window.setTimeout(update_boxes, 0);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.boxes-roles-menu {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 48px;
|
||||
}
|
||||
.boxes-node {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
@ -136,7 +179,7 @@
|
||||
padding: 20px 4px 0 4px;
|
||||
}
|
||||
.boxes-available-roles {
|
||||
border-radius: 2px;
|
||||
border-radius: 4px;
|
||||
background: #eee;
|
||||
border: 1px dashed #666;
|
||||
min-height: 42px;
|
||||
@ -145,20 +188,20 @@
|
||||
padding: 4px 0 0 4px;
|
||||
}
|
||||
.boxes-role {
|
||||
display: inline-block;
|
||||
padding: 6px;
|
||||
border: 1px solid;
|
||||
width: 180px;
|
||||
cursor: move;
|
||||
border-radius: 2px;
|
||||
margin: 0 4px 4px 0;
|
||||
background-color: #fce94f;
|
||||
border-color: #edd400;
|
||||
color: #c4a000;
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
.boxes-available-roles .boxes-role {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
margin: 0 4px 4px 0;
|
||||
}
|
||||
.boxes-available-roles .boxes-role .form-control {
|
||||
display: none;
|
||||
@ -184,12 +227,11 @@
|
||||
color: #5c3566;
|
||||
}
|
||||
.boxes-drop {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
border: 1px dashed;
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
border-radius: 4px;
|
||||
background-color: #eee;
|
||||
border-color: #666;
|
||||
color: #444;
|
||||
|
Loading…
Reference in New Issue
Block a user