summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-10-14 08:16:06 +0000
committerGerrit Code Review <review@openstack.org>2014-10-14 08:16:06 +0000
commit03e1425aa87357543b11391ed19eb5b6c0763b30 (patch)
treef83e312b342ad2366412605594e0cda0727db781
parentef08ef4e12b2961edebda80dfa10c3b5adef285c (diff)
parentdb3d7cd35c69ef7ebe901e1f5e5417e010bb71bd (diff)
downloadtuskar-ui-03e1425aa87357543b11391ed19eb5b6c0763b30.tar.gz
Merge "New node registration and upload dialogs"
-rw-r--r--tuskar_ui/forms.py2
-rw-r--r--tuskar_ui/infrastructure/nodes/forms.py221
-rw-r--r--tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover.html20
-rw-r--r--tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_csv.html12
-rw-r--r--tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_nodes_formset_form.html31
-rw-r--r--tuskar_ui/infrastructure/nodes/templates/nodes/_nodes_formset_form.html55
-rw-r--r--tuskar_ui/infrastructure/nodes/templates/nodes/_upload.html24
-rw-r--r--tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover.html11
-rw-r--r--tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover_csv.html11
-rw-r--r--tuskar_ui/infrastructure/nodes/templates/nodes/index.html14
-rw-r--r--tuskar_ui/infrastructure/nodes/templates/nodes/upload.html11
-rw-r--r--tuskar_ui/infrastructure/nodes/tests.py13
-rw-r--r--tuskar_ui/infrastructure/nodes/urls.py4
-rw-r--r--tuskar_ui/infrastructure/nodes/views.py57
-rw-r--r--tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js18
-rw-r--r--tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss10
16 files changed, 239 insertions, 275 deletions
diff --git a/tuskar_ui/forms.py b/tuskar_ui/forms.py
index 6a17b037..acdd4ce3 100644
--- a/tuskar_ui/forms.py
+++ b/tuskar_ui/forms.py
@@ -109,7 +109,7 @@ class SelfHandlingFormset(forms.formsets.BaseFormSet):
def handle(self, request, data):
success = True
for form in self:
- form_success = form.handle(request, data)
+ form_success = form.handle(request, form.cleaned_data)
if not form_success:
success = False
else:
diff --git a/tuskar_ui/infrastructure/nodes/forms.py b/tuskar_ui/infrastructure/nodes/forms.py
index c529a0cd..b2bfd244 100644
--- a/tuskar_ui/infrastructure/nodes/forms.py
+++ b/tuskar_ui/infrastructure/nodes/forms.py
@@ -11,7 +11,6 @@
# 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 csv
import django.forms
@@ -24,6 +23,7 @@ import tuskar_ui.forms
CPU_ARCH_CHOICES = [
+ ('', _("unspecified")),
('amd64', _("amd64")),
('x86', _("x86")),
('x86_64', _("x86_64")),
@@ -36,9 +36,7 @@ DRIVER_CHOICES = [
def get_driver_info_dict(data):
driver = data['driver']
- driver_dict = {
- 'driver': driver
- }
+ driver_dict = {'driver': driver}
if driver == 'ipmi':
driver_dict.update(
# TODO(rdopieralski) If ipmi_address is no longer required,
@@ -56,18 +54,22 @@ def get_driver_info_dict(data):
return driver_dict
-def auto_discover_node(request, kwargs):
- node = api.node.Node.create(
- request,
- **kwargs
+def create_node(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.set_maintenance(request,
- node.uuid,
- True)
- api.node.Node.discover(request, [node.uuid])
+ node = api.node.Node.create(request, **kwargs)
+ if data.get('do_autodiscovery', False):
+ api.node.Node.set_maintenance(request, node.uuid, True)
+ api.node.Node.discover(request, [node.uuid])
-class BaseNodeForm(django.forms.Form):
+class NodeForm(django.forms.Form):
id = django.forms.IntegerField(
label="",
required=False,
@@ -139,156 +141,129 @@ class BaseNodeForm(django.forms.Form):
'rows': 2,
}),
)
-
- def get_name(self):
- try:
- name = (self.fields['ipmi_address'].value() or
- self.fields['ssh_address'].value())
- except AttributeError:
- # when the field is not bound
- name = _("Undefined node")
- 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):
- success = True
- data = self.cleaned_data
- try:
- self.create_node(request, data)
- 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
-
-
-class RegisterNodeForm(BaseNodeForm):
+ 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={
- 'class': 'form-control',
+ 'placeholder': _('unspecified'),
'rows': '2',
}),
)
cpu_arch = django.forms.ChoiceField(
label=_("Architecture"),
- required=True,
+ required=False,
choices=CPU_ARCH_CHOICES,
widget=django.forms.Select(
- attrs={'class': 'form-control'}),
+ attrs={'placeholder': _('unspecified')}),
)
cpus = django.forms.IntegerField(
label=_("CPUs"),
- required=True,
- min_value=1,
- initial=1,
+ required=False,
+ min_value=0,
widget=tuskar_ui.forms.NumberInput(
- attrs={'class': 'form-control'}),
+ attrs={'placeholder': _('unspecified')}),
)
memory_mb = django.forms.IntegerField(
label=_("Memory"),
- required=True,
- min_value=1,
- initial=1,
+ required=False,
+ min_value=0,
widget=tuskar_ui.forms.NumberInput(
- attrs={'class': 'form-control'}),
+ attrs={'placeholder': _('unspecified')}),
)
local_gb = django.forms.IntegerField(
label=_("Local Disk"),
- required=True,
- min_value=1,
- initial=1,
+ required=False,
+ min_value=0,
widget=tuskar_ui.forms.NumberInput(
- attrs={'class': 'form-control'}),
+ attrs={'placeholder': _('unspecified')}),
)
+ def get_name(self):
+ try:
+ name = (self.fields['ipmi_address'].value() or
+ self.fields['ssh_address'].value())
+ except AttributeError:
+ # when the field is not bound
+ name = _("Undefined node")
+ return name
+
+ def handle(self, request, data):
+ success = True
+ try:
+ create_node(request, data)
+ 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
-class AutoDiscoverNodeForm(BaseNodeForm):
- mac_addresses = tuskar_ui.forms.MultiMACField(
- label=_("NIC MAC Addresses"),
- required=False,
- widget=django.forms.Textarea(attrs={
- 'class': 'form-control switched',
- 'data-switch-on': 'driver',
- 'data-driver-pxe_ssh': _("PXE + SSH"),
- 'rows': 2,
- }),
- )
+ def clean_ipmi_username(self):
+ return self.cleaned_data.get('ipmi_username') or None
- def create_node(self, request, data):
- kwargs = get_driver_info_dict(data)
- kwargs.update(
- mac_addresses=data['mac_addresses'].split(),
- )
- auto_discover_node(request, kwargs)
+ def clean_ipmi_password(self):
+ return self.cleaned_data.get('ipmi_password') or None
+ def clean(self):
+ cleaned_data = super(NodeForm, self).clean()
+ if not cleaned_data.get('do_autodiscovery', False):
+ for field_name in [
+ 'mac_addresses',
+ 'cpu_arch',
+ 'cpus',
+ 'memory_mb',
+ 'local_gb',
+ ]:
+ if not cleaned_data.get(field_name):
+ self._errors[field_name] = self.error_class([(
+ u"This field is required "
+ u"when autodiscovery is disabled."
+ )])
+ return cleaned_data
-class BaseNodeFormset(tuskar_ui.forms.SelfHandlingFormset):
+class BaseNodeFormset(tuskar_ui.forms.SelfHandlingFormset):
def clean(self):
for form in self:
if not form.cleaned_data:
raise django.forms.ValidationError(
_("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'):
- form.cleaned_data['ipmi_password'] = None
-RegisterNodeFormset = django.forms.formsets.formset_factory(
- RegisterNodeForm, extra=1,
- 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)
+class UploadNodeForm(forms.SelfHandlingForm):
+ csv_file = forms.FileField(label=_("CSV File"), required=True)
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
- }
+ 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':
- kwargs.update(
- ssh_address=node_data[1],
- ssh_username=node_data[2],
- ssh_key_contents=node_data[3],
- mac_addresses=node_data[4].split()
+ node = dict(
+ ssh_address=row[1],
+ ssh_username=row[2],
+ ssh_key_contents=row[3],
+ mac_addresses=row[4],
+ driver=driver,
+ do_autodiscovery=True,
)
- else:
- kwargs.update(
- ipmi_address=node_data[1],
- ipmi_username=node_data[2],
- ipmi_password=node_data[3],
+ 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
- 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
+RegisterNodeFormset = django.forms.formsets.formset_factory(
+ NodeForm, extra=1, formset=BaseNodeFormset)
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover.html
deleted file mode 100644
index 1e06ba3b..00000000
--- a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover.html
+++ /dev/null
@@ -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 %}
-
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_csv.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_csv.html
index 9e4c4978..b6d7f14b 100644
--- a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_csv.html
+++ b/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_csv.html
@@ -3,9 +3,10 @@
{% load url from future %}
{% 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-header %}{% trans "Auto-Discover Nodes (Upload CSV)" %}{% endblock %}
+{% block modal-header %}{% trans "Upload Nodes" %}{% endblock %}
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
{% block modal-body %}
@@ -13,9 +14,10 @@
{% endblock %}
{% block modal-footer %}
- <input class="btn btn-primary pull-right" type="submit"
- value="{% trans "Register Nodes" %}" />
+ <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 %}
-
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_nodes_formset_form.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_nodes_formset_form.html
deleted file mode 100644
index c8ef4bbe..00000000
--- a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_nodes_formset_form.html
+++ /dev/null
@@ -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>
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_nodes_formset_form.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_nodes_formset_form.html
index 4802dc04..1bb851e0 100644
--- a/tuskar_ui/infrastructure/nodes/templates/nodes/_nodes_formset_form.html
+++ b/tuskar_ui/infrastructure/nodes/templates/nodes/_nodes_formset_form.html
@@ -1,32 +1,31 @@
-<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 }}">
- <div class="form form-inline"><fieldset>
- <div class="row">
- <h4>Node Detail</h4>
- </div>
+ <div class="form form-inline"><fieldset class="well">
+ {% include 'horizon/common/_form_errors.html' with form=form %}
<div class="row">
- <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.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 %}
- </div>
- <div class="row">
- <h5>Networking</h5>
- {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.mac_addresses required=True %}
+ <h4>{% trans "Node Detail" %}</h4>
</div>
- <div class="row">
- <div class="col-xs-4">
- <h5>Hardware</h5>
+ <h5 class="row">{% trans "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.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 %}
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <label class="checkbox">
+ {{ form.do_autodiscovery|add_bootstrap_class }}
+ {{ form.do_autodiscovery.label }}
+ </label>
</div>
- <label class="col-xs-6 checkbox checkbox-inline">
- {{ form.introspect_hardware }}<small> {{ form.introspect_hardware.label }}</small>
- </label>
- </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.cpus extra_text=_('units') 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 () {
$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>
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_upload.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_upload.html
new file mode 100644
index 00000000..7638cb49
--- /dev/null
+++ b/tuskar_ui/infrastructure/nodes/templates/nodes/_upload.html
@@ -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 %}
+
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover.html b/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover.html
deleted file mode 100644
index e52ebaae..00000000
--- a/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover.html
+++ /dev/null
@@ -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 %}
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover_csv.html b/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover_csv.html
deleted file mode 100644
index bde32008..00000000
--- a/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover_csv.html
+++ /dev/null
@@ -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 %}
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/index.html b/tuskar_ui/infrastructure/nodes/templates/nodes/index.html
index 3519a9b7..362c876a 100644
--- a/tuskar_ui/infrastructure/nodes/templates/nodes/index.html
+++ b/tuskar_ui/infrastructure/nodes/templates/nodes/index.html
@@ -11,18 +11,16 @@
<div class="row">
<div class="col-xs-12">
<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>
{% trans 'Register Nodes' %}
</a>
{% if ironic_enabled %}
- <a href="{% url 'horizon:infrastructure:nodes:auto-discover' %}" class="btn btn-primary ajax-modal">
- <span class="fa fa-search-plus"></span>
- {% trans 'Auto-Discover Nodes' %}
- </a>
- <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 href="{% url 'horizon:infrastructure:nodes:auto-discover-csv' %}"
+ class="btn btn-primary ajax-modal">
+ <span class="fa fa-upload"></span>
+ {% trans 'Upload Nodes' %}
</a>
{% endif %}
</div>
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/upload.html b/tuskar_ui/infrastructure/nodes/templates/nodes/upload.html
new file mode 100644
index 00000000..a95bcede
--- /dev/null
+++ b/tuskar_ui/infrastructure/nodes/templates/nodes/upload.html
@@ -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 %}
diff --git a/tuskar_ui/infrastructure/nodes/tests.py b/tuskar_ui/infrastructure/nodes/tests.py
index ea5473b3..5725f9bb 100644
--- a/tuskar_ui/infrastructure/nodes/tests.py
+++ b/tuskar_ui/infrastructure/nodes/tests.py
@@ -17,7 +17,7 @@ import json
from django.core import urlresolvers
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.test_data import utils
@@ -157,11 +157,11 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
'create.return_value': node,
}) as Node:
res = self.client.post(REGISTER_URL, data)
+ self.assertNoFormErrors(res)
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, [
call(
- request,
+ ANY,
ipmi_address=u'127.0.0.1',
cpu_arch='x86',
cpus=1,
@@ -173,7 +173,7 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
driver='ipmi',
),
call(
- request,
+ ANY,
ipmi_address=u'127.0.0.2',
cpu_arch='x86',
cpus=4,
@@ -216,10 +216,9 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
}) as Node:
res = self.client.post(REGISTER_URL, data)
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, [
call(
- request,
+ ANY,
ipmi_address=u'127.0.0.1',
cpu_arch='x86',
cpus=1,
@@ -231,7 +230,7 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
driver='ipmi',
),
call(
- request,
+ ANY,
ipmi_address=u'127.0.0.2',
cpu_arch='x86',
cpus=4,
diff --git a/tuskar_ui/infrastructure/nodes/urls.py b/tuskar_ui/infrastructure/nodes/urls.py
index d1dc0935..c26dd16e 100644
--- a/tuskar_ui/infrastructure/nodes/urls.py
+++ b/tuskar_ui/infrastructure/nodes/urls.py
@@ -22,9 +22,7 @@ urlpatterns = urls.patterns(
urls.url(r'^$', views.IndexView.as_view(), name='index'),
urls.url(r'^register/$', views.RegisterView.as_view(),
name='register'),
- urls.url(r'^auto-discover/$', views.AutoDiscoverView.as_view(),
- name='auto-discover'),
- urls.url(r'^auto-discover-csv/$', views.AutoDiscoverCSVView.as_view(),
+ urls.url(r'^auto-discover-csv/$', views.UploadView.as_view(),
name='auto-discover-csv'),
urls.url(r'^nodes_performance/$',
views.PerformanceView.as_view(), name='nodes_performance'),
diff --git a/tuskar_ui/infrastructure/nodes/views.py b/tuskar_ui/infrastructure/nodes/views.py
index 6f80da87..0db92b2e 100644
--- a/tuskar_ui/infrastructure/nodes/views.py
+++ b/tuskar_ui/infrastructure/nodes/views.py
@@ -14,9 +14,11 @@
import json
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.views.generic import base
+from horizon import exceptions
from horizon import forms as horizon_forms
from horizon import tabs as horizon_tabs
from horizon.utils import memoized
@@ -50,32 +52,35 @@ class RegisterView(horizon_forms.ModalFormView):
return []
def get_form(self, form_class):
- return form_class(self.request.POST or None,
- initial=self.get_data(),
- prefix=self.form_prefix)
-
-
-class AutoDiscoverView(horizon_forms.ModalFormView):
- form_class = forms.AutoDiscoverNodeFormset
- form_prefix = 'auto_discover_nodes'
- template_name = 'infrastructure/nodes/auto_discover.html'
+ initial = []
+ if self.request.FILES:
+ 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 UploadView(horizon_forms.ModalFormView):
+ form_class = forms.UploadNodeForm
+ template_name = 'infrastructure/nodes/upload.html'
success_url = reverse_lazy(
'horizon:infrastructure:nodes:index')
- def get_data(self):
- return []
-
- 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')
+ def post(self, request, *args, **kwargs):
+ # This form's POST is handled in RegisterView.
+ raise exceptions.NotFound()
class DetailView(horizon_tabs.TabView):
@@ -138,5 +143,5 @@ class PerformanceView(base.TemplateView):
date_from=date_from, date_to=date_to,
stats_attr=stats_attr, barchart=barchart)
- return http.HttpResponse(json.dumps(json_output),
- content_type='application/json')
+ return django.http.HttpResponse(
+ json.dumps(json_output), content_type='application/json')
diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js
index d3d8bc89..ef9c3b4c 100644
--- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js
+++ b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js
@@ -32,7 +32,7 @@ tuskar.menu_formset = (function () {
function add_delete_link($nav_item) {
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 () {
var count;
$form.remove();
@@ -53,13 +53,24 @@ tuskar.menu_formset = (function () {
$nav.append('<li><a href="#' + id + '" data-toggle="tab">Undefined node</a></li>');
$new_nav = $nav.find('li > a:last');
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');
+ $('select.switchable').trigger('change');
}
// Connect all signals.
$('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.
$content.find('.control-group.error').each(function () {
@@ -68,6 +79,7 @@ tuskar.menu_formset = (function () {
activated = true;
}
});
+
};
return module;
diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss
index 88861707..63f47ec9 100644
--- a/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss
+++ b/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss
@@ -78,10 +78,18 @@ $link-color: #428bca;
font-weight: normal;
}
+ .panel {
+ margin: 8px -8px 8px -8px;
+ padding: 8px;
+ .panel-heading {
+ margin: -8px -8px 0 -8px;
+ }
+ }
+
fieldset .form-group {
width: 100%;
input, textarea, select {
- width: 150px;
+ width: 100%;
margin-left: 5px;
}
}