Allow users to specify the order of VNICs of an instance.

Enable to order VNICs. It allows to choose your preferred network to eth0, eth1
and so on.
Added jQuery UI for implementing drag and drop sort function.

implements bp quantum-vnic-ordering

Change-Id: I29cba9004d7170af2be3a6645c2217d7a5b0f6ae
This commit is contained in:
Toshiyuki Hayashi 2013-01-31 12:16:01 -08:00
parent 1d99475ef9
commit 3a84686ae3
9 changed files with 283 additions and 2 deletions

View File

@ -4,6 +4,8 @@ horizon.projects = {
current_membership: [],
users: [],
roles: [],
networks_selected: [],
networks_available: [],
default_role_id: "",
workflow_loaded: false,
no_project_members: gettext('This project currently has no members.'),
@ -26,6 +28,14 @@ horizon.projects = {
get_role_element: function(role_id) {
return $('select[id^="id_role_' + role_id + '"]');
},
/*
* Gets the html select element associated with a given
* network id for network_id.
**/
get_network_element: function(network_id) {
return $('li > label[for^="id_network_' + network_id + '"]');
},
/*
* Initializes all of the horizon.projects lists with
@ -82,6 +92,28 @@ horizon.projects = {
});
},
/*
* Initializes an associative array of lists of the current
* networks.
**/
init_network_list: function() {
horizon.projects.networks_selected = [];
horizon.projects.networks_available = [];
$(this.get_network_element("")).each(function(){
var $this = $(this);
var $input = $this.children("input");
var network_property = {
name:$this.text().replace(/^\s+/,""),
id:$input.attr("id"),
value:$input.attr("value")
};
if($input.is(':checked')) {
horizon.projects.networks_selected.push(network_property);
} else {
horizon.projects.networks_available.push(network_property);
}
});
},
/*
* Checks to see whether a user is a member of the current project.
* If they are, returns the id of their primary role.
@ -171,6 +203,16 @@ horizon.projects = {
return $(user_el);
},
/*
* Generates the HTML structure for a network that will be displayed
* as a list item in the project network list.
**/
generate_network_element: function(name, id, value) {
var $li = $('<li>');
$li.attr('name', value).html(name + '<em class="network_id">(' + value + ')</em><a href="#" class="btn btn-primary"></a>');
return $li;
},
set_selected_role: function(selected_el, role_id) {
$(selected_el).text(horizon.projects.roles[role_id]);
$(selected_el).attr('data-role-id', role_id);
@ -197,6 +239,72 @@ horizon.projects = {
horizon.projects.detect_no_results();
},
/*
* Generates the HTML structure for the project Network List.
**/
generate_networklist_html: function() {
var self = this;
var updateForm = function() {
var lists = $("#networkListId div.input li").attr('data-index',100);
var active_networks = $("#selected_network > li").map(function(){
return $(this).attr("name");
});
$("#networkListId div.input input:checkbox").removeAttr('checked');
active_networks.each(function(index, value){
$("#networkListId div.input input:checkbox[value=" + value + "]")
.attr('checked','checked')
.parents("li").attr('data-index',index);
});
$("#networkListId div.input ul").html(
lists.sort(function(a,b){
if( $(a).data("index") < $(b).data("index")) return -1;
if( $(a).data("index") > $(b).data("index")) return 1;
return 0;
})
);
};
$("#networkListSortContainer").show();
$("#networkListIdContainer").hide();
self.init_network_list();
$.each(self.networks_available, function(index, value){
$("#available_network").append(self.generate_network_element(value.name, value.id, value.value));
});
$.each(self.networks_selected, function(index, value){
$("#selected_network").append(self.generate_network_element(value.name, value.id, value.value));
});
// $(".networklist > li").click(function(){
// $(this).toggleClass("ui-selected");
// });
$(".networklist > li > a.btn").click(function(e){
var $this = $(this);
e.preventDefault();
e.stopPropagation();
if($this.parents("ul#available_network").length > 0) {
$this.parent().appendTo($("#selected_network"));
} else if ($this.parents("ul#selected_network").length > 0) {
$this.parent().appendTo($("#available_network"));
}
updateForm();
});
if ($("#networkListId > div.control-group.error").length > 0) {
var errortext = $("#networkListId > div.control-group.error").find("span.help-inline").text();
$("#selected_network_h4").before($('<div class="dynamic-error">').html(errortext));
}
$(".networklist").sortable({
connectWith: "ul.networklist",
placeholder: "ui-state-highlight",
distance: 5,
start:function(e,info){
$("#selected_network").addClass("dragging");
},
stop:function(e,info){
$("#selected_network").removeClass("dragging");
updateForm();
}
}).disableSelection();
},
/*
* Triggers on click of link to add/remove member from the project.
**/
@ -395,6 +503,7 @@ horizon.projects = {
* Calls set-up functions upon loading the workflow.
**/
workflow_init: function(modal) {
horizon.projects.generate_networklist_html();
if (!horizon.projects.workflow_loaded) {
$(modal).find('form').each( function () {
// call the initalization functions
@ -438,6 +547,7 @@ horizon.projects = {
}
};
horizon.addInitFunction(function() {
$('.btn').on('click', function (evt) {
horizon.projects.workflow_loaded = false;

View File

@ -256,7 +256,7 @@ horizon.datatables.set_table_filter = function (parent) {
$(parent).find('table').each(function (index, elm) {
var input = $($(elm).find('div.table_search input')),
table_selector;
if (input) {
if (input.length > 0) {
// Disable server-side searcing if we have client-side searching since
// (for now) the client-side is actually superior. Server-side filtering
// remains as a noscript fallback.

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,7 @@
<script src="{{ STATIC_URL }}horizon/lib/spin.jquery.js" type="text/javascript" charset="utf-8"></script>
<script src='{{ STATIC_URL }}horizon/lib/json2.js' type='text/javascript' charset="utf-8"></script>
<script src="{{ STATIC_URL }}horizon/lib/underscore/underscore-min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ STATIC_URL }}horizon/lib/jquery/jquery-ui-1.9.2.custom.min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ STATIC_URL }}bootstrap/js/bootstrap.min.js" type="text/javascript" charset="utf-8"></script>

View File

@ -0,0 +1,3 @@
{% load i18n horizon %}
<p>{% blocktrans %}Choose network from Available networks to Selected Networks by push button or drag and drop, you may change nic order by drag and drop as well. {% endblocktrans %}</p>

View File

@ -0,0 +1,35 @@
{% load i18n %}
<noscript><h3>{{ step }}</h3></noscript>
<table class="table-fixed" id="networkListSortContainer">
<tbody>
<tr>
<td class="actions">
<h4 id="selected_network_h4">{% trans "Selected Networks" %}</h4>
<ul id="selected_network" class="networklist">
</ul>
<h4>{% trans "Available networks" %}</h4>
<ul id="available_network" class="networklist">
</ul>
</td>
<td class="help_text">
{% include "project/instances/_launch_network_help.html" %}
</td>
</tr>
</tbody>
</table>
<table class="table-fixed" id="networkListIdContainer">
<tbody>
<tr>
<td class="actions">
<div id="networkListId">
{% include "horizon/common/_form_fields.html" %}
</div>
</td>
<td class="help_text">
{{ step.get_help_text }}
</td>
</tr>
</tbody>
</table>

View File

@ -415,6 +415,10 @@ class SetNetworkAction(workflows.Action):
network = forms.MultipleChoiceField(label=_("Networks"),
required=True,
widget=forms.CheckboxSelectMultiple(),
error_messages={
'required': _(
"At least one network must"
" be specified.")},
help_text=_("Launch instance with"
"these networks"))
@ -439,6 +443,7 @@ class SetNetworkAction(workflows.Action):
class SetNetwork(workflows.Step):
action_class = SetNetworkAction
template_name = "project/instances/_update_networks.html"
contributes = ("network_id",)
def contribute(self, data, context):

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

View File

@ -1175,12 +1175,16 @@ div .flavor_table {
margin-right: 50px;
}
.error .help-inline {
.error .help-inline, .dynamic-error {
background: #efdfdf;
border: 1px solid #ead5d8;
padding: 10px;
display: block;
}
.dynamic-error {
color: #b94a48;
margin-bottom: 0.5em;
}
label.log-length {
line-height: 28px;
@ -1568,3 +1572,120 @@ label.log-length {
width: 275px;
margin-right: 2px;
}
/* Styling for draged network object */
#networkListSortContainer {
display: none;
}
.networklist {
padding: 6px;
background: #eee;
border: 1px solid #ccc;
min-height: 2em;
width: auto !important;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
li {
width: 226px;
list-style-type: none;
margin: 6px auto;
padding: 3px;
background: #fff;
border: 1px solid #aaa;
line-height: 18px;
border-radius: 3px;
cursor: move;
padding-left: 23px;
background: white url(/static/dashboard/img/drag.png) no-repeat 11px 50%;
em {
font-size: 0.5em;
line-height: 1em;
color:#999;
font-style: normal;
margin-left: 0.8em;
}
i {
margin-right: 5px;
vertical-align: middle;
}
a.btn {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
font-size: 11px;
line-height: 12px;
padding: 2px 5px 3px;
margin-right: 1px;
width: 18px;
text-align: center;
//position: absolute;
right:5px;
vertical-align: middle;
float: right;
&:before {
content: "+";
}
}
}
li.ui-sortable-helper {
background-color: #def;
}
li.ui-state-highlight {
border: 1px dotted #ccc;
background: #efefef;
height: 0.5em;
}
li:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
#selected_network {
margin-bottom: 1.5em;
counter-reset:v1 0;
background: #edf9ff;
border:1px solid #c0d9e4;
li {
position: relative;
a.btn {
&:before {
content: "-";
}
}
}
li:before {
content:"nic:"counter(v1);
counter-increment:v1;
display: inline-block;
margin-right: 5px;
background: #666;
color:#fff;
font-size: 90%;
padding: 0px 4px;
vertical-align: middle;
border-radius: 2px;
position: absolute;
left: -2em;
}
&.dragging {
li:before {
content:"nic:";
background-color:rgba(102,102,102,0.5);
padding-right: 10px;
}
li.ui-state-highlight:before {
content:"";
background:transparent;
}
}
}