New node registration and upload dialogs

According to the new wireframes.

The values for autodiscoverable parameters now default to empty values.

Change-Id: I1c6430cc0165c7df612c85c31e8ad17d82870b5c
This commit is contained in:
Radomir Dopieralski 2014-10-07 16:57:14 +02:00
parent 75e1ae05bf
commit db3d7cd35c
16 changed files with 247 additions and 283 deletions

View File

@ -109,7 +109,7 @@ class SelfHandlingFormset(forms.formsets.BaseFormSet):
def handle(self, request, data): def handle(self, request, data):
success = True success = True
for form in self: for form in self:
form_success = form.handle(request, data) form_success = form.handle(request, form.cleaned_data)
if not form_success: if not form_success:
success = False success = False
else: else:

View File

@ -11,7 +11,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import csv import csv
import django.forms import django.forms
@ -24,6 +23,7 @@ import tuskar_ui.forms
CPU_ARCH_CHOICES = [ CPU_ARCH_CHOICES = [
('', _("unspecified")),
('amd64', _("amd64")), ('amd64', _("amd64")),
('x86', _("x86")), ('x86', _("x86")),
('x86_64', _("x86_64")), ('x86_64', _("x86_64")),
@ -36,9 +36,7 @@ DRIVER_CHOICES = [
def get_driver_info_dict(data): def get_driver_info_dict(data):
driver = data['driver'] driver = data['driver']
driver_dict = { driver_dict = {'driver': driver}
'driver': driver
}
if driver == 'ipmi': if driver == 'ipmi':
driver_dict.update( driver_dict.update(
# TODO(rdopieralski) If ipmi_address is no longer required, # TODO(rdopieralski) If ipmi_address is no longer required,
@ -56,18 +54,22 @@ def get_driver_info_dict(data):
return driver_dict return driver_dict
def auto_discover_node(request, kwargs): def create_node(request, data):
node = api.node.Node.create( kwargs = get_driver_info_dict(data)
request, kwargs.update(
**kwargs cpu_arch=data.get('cpu_arch'),
cpus=data.get('cpus'),
memory_mb=data.get('memory_mb'),
local_gb=data.get('local_gb'),
mac_addresses=data['mac_addresses'].split(),
) )
api.node.Node.set_maintenance(request, node = api.node.Node.create(request, **kwargs)
node.uuid, if data.get('do_autodiscovery', False):
True) api.node.Node.set_maintenance(request, node.uuid, True)
api.node.Node.discover(request, [node.uuid]) api.node.Node.discover(request, [node.uuid])
class BaseNodeForm(django.forms.Form): class NodeForm(django.forms.Form):
id = django.forms.IntegerField( id = django.forms.IntegerField(
label="", label="",
required=False, required=False,
@ -139,6 +141,46 @@ class BaseNodeForm(django.forms.Form):
'rows': 2, 'rows': 2,
}), }),
) )
do_autodiscovery = django.forms.BooleanField(
label=_("Discover missing attributes"),
required=False,
)
mac_addresses = tuskar_ui.forms.MultiMACField(
label=_("NIC MAC Addresses"),
required=False,
widget=django.forms.Textarea(attrs={
'placeholder': _('unspecified'),
'rows': '2',
}),
)
cpu_arch = django.forms.ChoiceField(
label=_("Architecture"),
required=False,
choices=CPU_ARCH_CHOICES,
widget=django.forms.Select(
attrs={'placeholder': _('unspecified')}),
)
cpus = django.forms.IntegerField(
label=_("CPUs"),
required=False,
min_value=0,
widget=tuskar_ui.forms.NumberInput(
attrs={'placeholder': _('unspecified')}),
)
memory_mb = django.forms.IntegerField(
label=_("Memory"),
required=False,
min_value=0,
widget=tuskar_ui.forms.NumberInput(
attrs={'placeholder': _('unspecified')}),
)
local_gb = django.forms.IntegerField(
label=_("Local Disk"),
required=False,
min_value=0,
widget=tuskar_ui.forms.NumberInput(
attrs={'placeholder': _('unspecified')}),
)
def get_name(self): def get_name(self):
try: try:
@ -149,22 +191,10 @@ class BaseNodeForm(django.forms.Form):
name = _("Undefined node") name = _("Undefined node")
return name return name
def create_node(self, request, data):
kwargs = get_driver_info_dict(data)
kwargs.update(
cpu_arch=data.get('cpu_arch'),
cpus=data.get('cpus'),
memory_mb=data.get('memory_mb'),
local_gb=data.get('local_gb'),
mac_addresses=data['mac_addresses'].split(),
)
api.node.Node.create(request, **kwargs)
def handle(self, request, data): def handle(self, request, data):
success = True success = True
data = self.cleaned_data
try: try:
self.create_node(request, data) create_node(request, data)
except Exception: except Exception:
success = False success = False
exceptions.handle(request, _('Unable to register node.')) exceptions.handle(request, _('Unable to register node.'))
@ -172,123 +202,68 @@ class BaseNodeForm(django.forms.Form):
# have to unregister nodes, delete ports, etc? # have to unregister nodes, delete ports, etc?
return success return success
def clean_ipmi_username(self):
return self.cleaned_data.get('ipmi_username') or None
class RegisterNodeForm(BaseNodeForm): def clean_ipmi_password(self):
mac_addresses = tuskar_ui.forms.MultiMACField( return self.cleaned_data.get('ipmi_password') or None
label=_("NIC MAC Addresses"),
widget=django.forms.Textarea(attrs={
'class': 'form-control',
'rows': '2',
}),
)
cpu_arch = django.forms.ChoiceField(
label=_("Architecture"),
required=True,
choices=CPU_ARCH_CHOICES,
widget=django.forms.Select(
attrs={'class': 'form-control'}),
)
cpus = django.forms.IntegerField(
label=_("CPUs"),
required=True,
min_value=1,
initial=1,
widget=tuskar_ui.forms.NumberInput(
attrs={'class': 'form-control'}),
)
memory_mb = django.forms.IntegerField(
label=_("Memory"),
required=True,
min_value=1,
initial=1,
widget=tuskar_ui.forms.NumberInput(
attrs={'class': 'form-control'}),
)
local_gb = django.forms.IntegerField(
label=_("Local Disk"),
required=True,
min_value=1,
initial=1,
widget=tuskar_ui.forms.NumberInput(
attrs={'class': 'form-control'}),
)
def clean(self):
class AutoDiscoverNodeForm(BaseNodeForm): cleaned_data = super(NodeForm, self).clean()
mac_addresses = tuskar_ui.forms.MultiMACField( if not cleaned_data.get('do_autodiscovery', False):
label=_("NIC MAC Addresses"), for field_name in [
required=False, 'mac_addresses',
widget=django.forms.Textarea(attrs={ 'cpu_arch',
'class': 'form-control switched', 'cpus',
'data-switch-on': 'driver', 'memory_mb',
'data-driver-pxe_ssh': _("PXE + SSH"), 'local_gb',
'rows': 2, ]:
}), if not cleaned_data.get(field_name):
) self._errors[field_name] = self.error_class([(
u"This field is required "
def create_node(self, request, data): u"when autodiscovery is disabled."
kwargs = get_driver_info_dict(data) )])
kwargs.update( return cleaned_data
mac_addresses=data['mac_addresses'].split(),
)
auto_discover_node(request, kwargs)
class BaseNodeFormset(tuskar_ui.forms.SelfHandlingFormset): class BaseNodeFormset(tuskar_ui.forms.SelfHandlingFormset):
def clean(self): def clean(self):
for form in self: for form in self:
if not form.cleaned_data: if not form.cleaned_data:
raise django.forms.ValidationError( raise django.forms.ValidationError(
_("Please provide node data for all nodes.")) _("Please provide node data for all nodes."))
if not form.cleaned_data.get('ipmi_username'):
form.cleaned_data['ipmi_username'] = None
if not form.cleaned_data.get('ipmi_password'): class UploadNodeForm(forms.SelfHandlingForm):
form.cleaned_data['ipmi_password'] = None csv_file = forms.FileField(label=_("CSV File"), required=True)
def handle(self, request, data):
return True
def get_data(self):
data = []
for row in csv.reader(self.cleaned_data['csv_file']):
driver = row[0].strip()
if driver == 'pxe_ssh':
node = dict(
ssh_address=row[1],
ssh_username=row[2],
ssh_key_contents=row[3],
mac_addresses=row[4],
driver=driver,
do_autodiscovery=True,
)
elif driver == 'ipmi':
node = dict(
ipmi_address=row[1],
ipmi_username=row[2],
ipmi_password=row[3],
driver=driver,
do_autodiscovery=True,
)
data.append(node)
return data
RegisterNodeFormset = django.forms.formsets.formset_factory( RegisterNodeFormset = django.forms.formsets.formset_factory(
RegisterNodeForm, extra=1, NodeForm, extra=1, formset=BaseNodeFormset)
formset=BaseNodeFormset)
AutoDiscoverNodeFormset = django.forms.formsets.formset_factory(
AutoDiscoverNodeForm, extra=1,
formset=BaseNodeFormset)
class AutoDiscoverCSVNodeForm(forms.SelfHandlingForm):
csv_file = forms.FileField(label=_("CSV File"),
required=False)
def handle(self, request, data):
success = True
all_node_data = csv.reader(data['csv_file'])
for node_data in all_node_data:
driver = node_data[0]
kwargs = {
'driver': driver
}
if driver == 'pxe_ssh':
kwargs.update(
ssh_address=node_data[1],
ssh_username=node_data[2],
ssh_key_contents=node_data[3],
mac_addresses=node_data[4].split()
)
else:
kwargs.update(
ipmi_address=node_data[1],
ipmi_username=node_data[2],
ipmi_password=node_data[3],
)
try:
auto_discover_node(request, kwargs)
except Exception:
success = False
exceptions.handle(request, _('Unable to register node.'))
# TODO(tzumainn) If there is a failure between steps, do we
# have to unregister nodes, delete ports, etc?
return success

View File

@ -1,20 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% load url from future %}
{% block form_id %}autodiscover_nodes_form{% endblock %}
{% block form_action %}{% url 'horizon:infrastructure:nodes:auto-discover' %}{% endblock %}
{% block modal_id %}autodiscover_nodes_modal{% endblock %}
{% block modal-header %}{% trans "Auto-Discover Nodes" %}{% endblock %}
{% block modal-body %}
{% include "formset_table/menu_formset.html" with formset=form form_template="infrastructure/nodes/_auto_discover_nodes_formset_form.html" %}
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit"
value="{% trans "Register Nodes" %}" />
<a href="{% url 'horizon:infrastructure:nodes:index' %}"
class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -3,9 +3,10 @@
{% load url from future %} {% load url from future %}
{% block form_id %}autodiscover_csv_nodes_form{% endblock %} {% block form_id %}autodiscover_csv_nodes_form{% endblock %}
{% block form_action %}{% url 'horizon:infrastructure:nodes:auto-discover-csv' %}{% endblock %} {% block form_action %}{% url 'horizon:infrastructure:nodes:register' %}{% endblock %}
{% block modal_id %}autodiscover_csv_nodes_modal{% endblock %} {% block modal_id %}autodiscover_csv_nodes_modal{% endblock %}
{% block modal-header %}{% trans "Auto-Discover Nodes (Upload CSV)" %}{% endblock %} {% block modal-header %}{% trans "Upload Nodes" %}{% endblock %}
{% block form_attrs %}enctype="multipart/form-data"{% endblock %} {% block form_attrs %}enctype="multipart/form-data"{% endblock %}
{% block modal-body %} {% block modal-body %}
@ -13,9 +14,10 @@
{% endblock %} {% endblock %}
{% block modal-footer %} {% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" <button class="btn btn-primary pull-right" type="submit">
value="{% trans "Register Nodes" %}" /> <i class="fa fa-upload"></i>
{% trans "Upload" %}
</button>
<a href="{% url 'horizon:infrastructure:nodes:index' %}" <a href="{% url 'horizon:infrastructure:nodes:index' %}"
class="btn secondary cancel close">{% trans "Cancel" %}</a> class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %} {% endblock %}

View File

@ -1,31 +0,0 @@
<div class="well tab-pane{% if active %} active{% endif %}"
id="tab-{{ form.prefix }}">
<div class="form form-inline"><fieldset>
<div class="row">
<h4>Node Detail</h4>
</div>
<div class="row">
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.driver required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_address %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_username %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_password %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_address %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_username %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_key_contents %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.mac_addresses %}
</div>
</fieldset></div>
</div>
<script type="text/javascript">
(window.$ || window.addHorizonLoadEvent)(function () {
var form_prefix = '{{ form.prefix|escapejs }}';
var $form = $('#tab-' + form_prefix);
var $nav_link = $('a[href="#' + $form.attr('id') + '"]');
var undefined_name = '{{ form.get_name|escapejs }}';
$form.find('input[name$="-ipmi_address"]').change(function () {
$nav_link.html($(this).val() || undefined_name);
});
});
</script>

View File

@ -1,11 +1,14 @@
<div class="well tab-pane{% if active %} active{% endif %}" {% load i18n %}
{% load form_helpers %}
<div class="container-fluid tab-pane{% if active %} active{% endif %}"
id="tab-{{ form.prefix }}"> id="tab-{{ form.prefix }}">
<div class="form form-inline"><fieldset> <div class="form form-inline"><fieldset class="well">
{% include 'horizon/common/_form_errors.html' with form=form %}
<div class="row"> <div class="row">
<h4>Node Detail</h4> <h4>{% trans "Node Detail" %}</h4>
</div> </div>
<div class="row"> <h5 class="row">{% trans "Power Management" %}</h5>
<h5>Power Management</h5>
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.driver required=True %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.driver required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_address %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_address %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_username %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_username %}
@ -13,20 +16,16 @@
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_address %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_address %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_username %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_username %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_key_contents %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_key_contents %}
</div> <div class="panel panel-default">
<div class="row"> <div class="panel-heading">
<h5>Networking</h5> <label class="checkbox">
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.mac_addresses required=True %} {{ form.do_autodiscovery|add_bootstrap_class }}
</div> {{ form.do_autodiscovery.label }}
<div class="row">
<div class="col-xs-4">
<h5>Hardware</h5>
</div>
<label class="col-xs-6 checkbox checkbox-inline">
{{ form.introspect_hardware }}<small> {{ form.introspect_hardware.label }}</small>
</label> </label>
</div> </div>
<div class="row" id="register-hardware-fields"> <h5 class="row">{% trans "Networking" %}</h5>
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.mac_addresses required=True %}
<h5 class="row">{% trans "Hardware" %}</h5>
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.cpu_arch required=True %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.cpu_arch required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.cpus extra_text=_('units') required=True %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.cpus extra_text=_('units') required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.memory_mb extra_text=_('MB') required=True %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.memory_mb extra_text=_('MB') required=True %}
@ -45,5 +44,11 @@
$form.find('input[name$="-ipmi_address"]').change(function () { $form.find('input[name$="-ipmi_address"]').change(function () {
$nav_link.html($(this).val() || undefined_name); $nav_link.html($(this).val() || undefined_name);
}); });
$form.find('input[name$="-do_autodiscovery"]').change(function () {
var $this = $(this);
$this.closest('.panel').find(
'.form-group .row').toggleClass('required', !($this.attr('checked')));
});
}); });
</script> </script>

View File

@ -0,0 +1,24 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% load url from future %}
{% block form_id %}upload_nodes_form{% endblock %}
{% block form_action %}{% url 'horizon:infrastructure:nodes:register' %}?upload{% endblock %}
{% block modal_id %}upload_nodes_modal{% endblock %}
{% block modal-header %}{% trans "Upload Nodes" %}{% endblock %}
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
{% block modal-body %}
{% include "horizon/common/_form_fields.html" %}
{% endblock %}
{% block modal-footer %}
<button class="btn btn-primary pull-right" type="submit">
<i class="fa fa-upload"></i>
{% trans "Upload" %}
</button>
<a href="{% url 'horizon:infrastructure:nodes:index' %}"
class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -1,11 +0,0 @@
{% extends "infrastructure/base.html" %}
{% load i18n %}
{% block title %}{% trans "Auto-Discover Nodes" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Auto-Discover Nodes") %}
{% endblock %}
{% block main %}
{% include "infrastructure/nodes/_auto_discover.html" %}
{% endblock %}

View File

@ -1,11 +0,0 @@
{% extends "infrastructure/base.html" %}
{% load i18n %}
{% block title %}{% trans "Auto-Discover Nodes (Upload CSV)" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Auto-Discover Nodes (Upload CSV)") %}
{% endblock %}
{% block main %}
{% include "infrastructure/nodes/_auto_discover_csv.html" %}
{% endblock %}

View File

@ -11,18 +11,16 @@
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<div class="actions pull-right"> <div class="actions pull-right">
<a href="{% url 'horizon:infrastructure:nodes:register' %}" class="btn btn-primary ajax-modal"> <a href="{% url 'horizon:infrastructure:nodes:register' %}"
class="btn btn-primary ajax-modal">
<span class="fa fa-plus"></span> <span class="fa fa-plus"></span>
{% trans 'Register Nodes' %} {% trans 'Register Nodes' %}
</a> </a>
{% if ironic_enabled %} {% if ironic_enabled %}
<a href="{% url 'horizon:infrastructure:nodes:auto-discover' %}" class="btn btn-primary ajax-modal"> <a href="{% url 'horizon:infrastructure:nodes:auto-discover-csv' %}"
<span class="fa fa-search-plus"></span> class="btn btn-primary ajax-modal">
{% trans 'Auto-Discover Nodes' %} <span class="fa fa-upload"></span>
</a> {% trans 'Upload Nodes' %}
<a href="{% url 'horizon:infrastructure:nodes:auto-discover-csv' %}" class="btn btn-primary ajax-modal">
<span class="fa fa-search-plus"></span>
{% trans 'Auto-Discover Nodes (Upload CSV)' %}
</a> </a>
{% endif %} {% endif %}
</div> </div>

View File

@ -0,0 +1,11 @@
{% extends "infrastructure/base.html" %}
{% load i18n %}
{% block title %}{% trans "Upload Nodes" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Upload Nodes") %}
{% endblock %}
{% block main %}
{% include "infrastructure/nodes/_upload.html" %}
{% endblock %}

View File

@ -17,7 +17,7 @@ import json
from django.core import urlresolvers from django.core import urlresolvers
from horizon import exceptions as horizon_exceptions from horizon import exceptions as horizon_exceptions
from mock import patch, call # noqa from mock import patch, call, ANY # noqa
from openstack_dashboard.test import helpers from openstack_dashboard.test import helpers
from openstack_dashboard.test.test_data import utils from openstack_dashboard.test.test_data import utils
@ -157,11 +157,11 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
'create.return_value': node, 'create.return_value': node,
}) as Node: }) as Node:
res = self.client.post(REGISTER_URL, data) res = self.client.post(REGISTER_URL, data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL) self.assertRedirectsNoFollow(res, INDEX_URL)
request = Node.create.call_args_list[0][0][0] # This is a hack.
self.assertListEqual(Node.create.call_args_list, [ self.assertListEqual(Node.create.call_args_list, [
call( call(
request, ANY,
ipmi_address=u'127.0.0.1', ipmi_address=u'127.0.0.1',
cpu_arch='x86', cpu_arch='x86',
cpus=1, cpus=1,
@ -173,7 +173,7 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
driver='ipmi', driver='ipmi',
), ),
call( call(
request, ANY,
ipmi_address=u'127.0.0.2', ipmi_address=u'127.0.0.2',
cpu_arch='x86', cpu_arch='x86',
cpus=4, cpus=4,
@ -216,10 +216,9 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
}) as Node: }) as Node:
res = self.client.post(REGISTER_URL, data) res = self.client.post(REGISTER_URL, data)
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
request = Node.create.call_args_list[0][0][0] # This is a hack.
self.assertListEqual(Node.create.call_args_list, [ self.assertListEqual(Node.create.call_args_list, [
call( call(
request, ANY,
ipmi_address=u'127.0.0.1', ipmi_address=u'127.0.0.1',
cpu_arch='x86', cpu_arch='x86',
cpus=1, cpus=1,
@ -231,7 +230,7 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
driver='ipmi', driver='ipmi',
), ),
call( call(
request, ANY,
ipmi_address=u'127.0.0.2', ipmi_address=u'127.0.0.2',
cpu_arch='x86', cpu_arch='x86',
cpus=4, cpus=4,

View File

@ -22,9 +22,7 @@ urlpatterns = urls.patterns(
urls.url(r'^$', views.IndexView.as_view(), name='index'), urls.url(r'^$', views.IndexView.as_view(), name='index'),
urls.url(r'^register/$', views.RegisterView.as_view(), urls.url(r'^register/$', views.RegisterView.as_view(),
name='register'), name='register'),
urls.url(r'^auto-discover/$', views.AutoDiscoverView.as_view(), urls.url(r'^auto-discover-csv/$', views.UploadView.as_view(),
name='auto-discover'),
urls.url(r'^auto-discover-csv/$', views.AutoDiscoverCSVView.as_view(),
name='auto-discover-csv'), name='auto-discover-csv'),
urls.url(r'^nodes_performance/$', urls.url(r'^nodes_performance/$',
views.PerformanceView.as_view(), name='nodes_performance'), views.PerformanceView.as_view(), name='nodes_performance'),

View File

@ -14,9 +14,11 @@
import json import json
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django import http import django.forms
import django.http
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import base from django.views.generic import base
from horizon import exceptions
from horizon import forms as horizon_forms from horizon import forms as horizon_forms
from horizon import tabs as horizon_tabs from horizon import tabs as horizon_tabs
from horizon.utils import memoized from horizon.utils import memoized
@ -50,32 +52,35 @@ class RegisterView(horizon_forms.ModalFormView):
return [] return []
def get_form(self, form_class): def get_form(self, form_class):
return form_class(self.request.POST or None, initial = []
initial=self.get_data(), if self.request.FILES:
prefix=self.form_prefix) csv_form = forms.UploadNodeForm(self.request,
files=self.request.FILES)
if csv_form.is_valid():
initial = csv_form.get_data()
formset = forms.RegisterNodeFormset(
None,
initial=initial,
prefix=self.form_prefix,
)
formset.extra = 0
return formset
return forms.RegisterNodeFormset(
self.request.POST or None,
initial=initial,
prefix=self.form_prefix,
)
class AutoDiscoverView(horizon_forms.ModalFormView): class UploadView(horizon_forms.ModalFormView):
form_class = forms.AutoDiscoverNodeFormset form_class = forms.UploadNodeForm
form_prefix = 'auto_discover_nodes' template_name = 'infrastructure/nodes/upload.html'
template_name = 'infrastructure/nodes/auto_discover.html'
success_url = reverse_lazy( success_url = reverse_lazy(
'horizon:infrastructure:nodes:index') 'horizon:infrastructure:nodes:index')
def get_data(self): def post(self, request, *args, **kwargs):
return [] # This form's POST is handled in RegisterView.
raise exceptions.NotFound()
def get_form(self, form_class):
return form_class(self.request.POST or None,
initial=self.get_data(),
prefix=self.form_prefix)
class AutoDiscoverCSVView(horizon_forms.ModalFormView):
form_class = forms.AutoDiscoverCSVNodeForm
template_name = 'infrastructure/nodes/auto_discover_csv.html'
success_url = reverse_lazy(
'horizon:infrastructure:nodes:index')
class DetailView(horizon_tabs.TabView): class DetailView(horizon_tabs.TabView):
@ -138,5 +143,5 @@ class PerformanceView(base.TemplateView):
date_from=date_from, date_to=date_to, date_from=date_from, date_to=date_to,
stats_attr=stats_attr, barchart=barchart) stats_attr=stats_attr, barchart=barchart)
return http.HttpResponse(json.dumps(json_output), return django.http.HttpResponse(
content_type='application/json') json.dumps(json_output), content_type='application/json')

View File

@ -32,7 +32,7 @@ tuskar.menu_formset = (function () {
function add_delete_link($nav_item) { function add_delete_link($nav_item) {
var $form = $content.find($nav_item.find('a').attr('href')); var $form = $content.find($nav_item.find('a').attr('href'));
$nav_item.prepend('<span class="btn-small pull-right delete-icon"><i class="fa fa-trash"></i></span>'); $nav_item.prepend('<span class="btn-small pull-right delete-icon"><i class="fa fa-times"></i></span>');
$nav_item.find('span.delete-icon:first').click(function () { $nav_item.find('span.delete-icon:first').click(function () {
var count; var count;
$form.remove(); $form.remove();
@ -53,13 +53,24 @@ tuskar.menu_formset = (function () {
$nav.append('<li><a href="#' + id + '" data-toggle="tab">Undefined node</a></li>'); $nav.append('<li><a href="#' + id + '" data-toggle="tab">Undefined node</a></li>');
$new_nav = $nav.find('li > a:last'); $new_nav = $nav.find('li > a:last');
add_delete_link($new_nav.parent()); add_delete_link($new_nav.parent());
$new_nav.click(function () { $(this).tab('show'); }); $new_nav.click(function () {
$(this).tab('show');
$('select.switchable').trigger('change');
});
$new_nav.tab('show'); $new_nav.tab('show');
$('select.switchable').trigger('change');
} }
// Connect all signals. // Connect all signals.
$('a.add-node-link').click(add_node); $('a.add-node-link').click(add_node);
$nav.find('li').each(function () { add_delete_link($(this)); }); $nav.find('li').each(function () {
add_delete_link($(this));
});
$nav.find('li a').click(function () {
window.setTimeout(function () {
$('select.switchable').trigger('change');
}, 0);
});
// Activate the first field that has errors. // Activate the first field that has errors.
$content.find('.control-group.error').each(function () { $content.find('.control-group.error').each(function () {
@ -68,6 +79,7 @@ tuskar.menu_formset = (function () {
activated = true; activated = true;
} }
}); });
}; };
return module; return module;

View File

@ -78,10 +78,18 @@ $link-color: #428bca;
font-weight: normal; font-weight: normal;
} }
.panel {
margin: 8px -8px 8px -8px;
padding: 8px;
.panel-heading {
margin: -8px -8px 0 -8px;
}
}
fieldset .form-group { fieldset .form-group {
width: 100%; width: 100%;
input, textarea, select { input, textarea, select {
width: 150px; width: 100%;
margin-left: 5px; margin-left: 5px;
} }
} }