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:
parent
75e1ae05bf
commit
db3d7cd35c
@ -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:
|
||||
|
@ -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,6 +141,46 @@ class BaseNodeForm(django.forms.Form):
|
||||
'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):
|
||||
try:
|
||||
@ -149,22 +191,10 @@ class BaseNodeForm(django.forms.Form):
|
||||
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)
|
||||
create_node(request, data)
|
||||
except Exception:
|
||||
success = False
|
||||
exceptions.handle(request, _('Unable to register node.'))
|
||||
@ -172,123 +202,68 @@ class BaseNodeForm(django.forms.Form):
|
||||
# have to unregister nodes, delete ports, etc?
|
||||
return success
|
||||
|
||||
def clean_ipmi_username(self):
|
||||
return self.cleaned_data.get('ipmi_username') or None
|
||||
|
||||
class RegisterNodeForm(BaseNodeForm):
|
||||
mac_addresses = tuskar_ui.forms.MultiMACField(
|
||||
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_ipmi_password(self):
|
||||
return self.cleaned_data.get('ipmi_password') or None
|
||||
|
||||
|
||||
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 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(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):
|
||||
|
||||
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
|
||||
|
||||
|
||||
class UploadNodeForm(forms.SelfHandlingForm):
|
||||
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(
|
||||
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)
|
||||
|
||||
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
|
||||
NodeForm, extra=1, formset=BaseNodeFormset)
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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>
|
@ -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="form form-inline"><fieldset class="well">
|
||||
{% include 'horizon/common/_form_errors.html' with form=form %}
|
||||
<div class="row">
|
||||
<h4>Node Detail</h4>
|
||||
<h4>{% trans "Node Detail" %}</h4>
|
||||
</div>
|
||||
<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 %}
|
||||
</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>
|
||||
|
24
tuskar_ui/infrastructure/nodes/templates/nodes/_upload.html
Normal file
24
tuskar_ui/infrastructure/nodes/templates/nodes/_upload.html
Normal 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 %}
|
||||
|
@ -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 %}
|
@ -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 %}
|
@ -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>
|
||||
|
11
tuskar_ui/infrastructure/nodes/templates/nodes/upload.html
Normal file
11
tuskar_ui/infrastructure/nodes/templates/nodes/upload.html
Normal 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 %}
|
@ -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,
|
||||
|
@ -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'),
|
||||
|
@ -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)
|
||||
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 AutoDiscoverView(horizon_forms.ModalFormView):
|
||||
form_class = forms.AutoDiscoverNodeFormset
|
||||
form_prefix = 'auto_discover_nodes'
|
||||
template_name = 'infrastructure/nodes/auto_discover.html'
|
||||
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')
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user