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):
|
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:
|
||||||
|
@ -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
|
|
||||||
|
@ -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 %}
|
{% 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 %}
|
||||||
|
|
||||||
|
@ -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,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>
|
||||||
|
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="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>
|
||||||
|
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 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,
|
||||||
|
@ -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'),
|
||||||
|
@ -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')
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user