Commit fdb2cc34 by Kálmán Viktor

Merge remote-tracking branch 'origin/master' into feature-template-wizard

parents da26baa2 08566087
...@@ -321,7 +321,7 @@ a.hover-black { ...@@ -321,7 +321,7 @@ a.hover-black {
} }
.template-list-table-thin { .template-list-table-thin {
width: 95px; width: 100px;
} }
...@@ -540,3 +540,11 @@ footer a, footer a:hover, footer a:visited { ...@@ -540,3 +540,11 @@ footer a, footer a:hover, footer a:visited {
bottom: 10px; bottom: 10px;
right: 20px; right: 20px;
} }
.vm-list-table th i {
margin-top: 3px;
}
.table-sorting {
display: none;
}
// Stupid jQuery table plugin.
/*
* Source: https://github.com/joequery/Stupid-Table-Plugin
* The Stupid jQuery Plugin is licensed under the MIT license.
* Copyright (c) 2012 Joseph McCullough
*/
// Call on a table
// sortFns: Sort functions for your datatypes.
(function($) {
$.fn.stupidtable = function(sortFns) {
return this.each(function() {
var $table = $(this);
sortFns = sortFns || {};
// Merge sort functions with some default sort functions.
sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
// ==================================================== //
// Begin execution! //
// ==================================================== //
// Do sorting when THs are clicked
$table.on("click.stupidtable", "th", function() {
var $this = $(this);
var th_index = 0;
var dir = $.fn.stupidtable.dir;
$table.find("th").slice(0, $this.index()).each(function() {
var cols = $(this).attr("colspan") || 1;
th_index += parseInt(cols,10);
});
// Determine (and/or reverse) sorting direction, default `asc`
var sort_dir = $this.data("sort-default") || dir.ASC;
if ($this.data("sort-dir"))
sort_dir = $this.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
// Choose appropriate sorting function.
var type = $this.data("sort") || null;
// Prevent sorting if no type defined
if (type === null) {
return;
}
// Trigger `beforetablesort` event that calling scripts can hook into;
// pass parameters for sorted column index and sorting direction
$table.trigger("beforetablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
// Run sorting asynchronously on a timout to force browser redraw after
// `beforetablesort` callback. Also avoids locking up the browser too much.
setTimeout(function() {
// Gather the elements for this column
var column = [];
var sortMethod = sortFns[type];
var trs = $table.children("tbody").children("tr");
// Extract the data for the column that needs to be sorted and pair it up
// with the TR itself into a tuple
trs.each(function(index,tr) {
var $e = $(tr).children().eq(th_index);
var sort_val = $e.data("sort-value");
var order_by = typeof(sort_val) !== "undefined" ? sort_val : $e.text();
column.push([order_by, tr]);
});
// Sort by the data-order-by value
column.sort(function(a, b) { return sortMethod(a[0], b[0]); });
if (sort_dir != dir.ASC)
column.reverse();
// Replace the content of tbody with the sorted rows. Strangely (and
// conveniently!) enough, .append accomplishes this for us.
trs = $.map(column, function(kv) { return kv[1]; });
$table.children("tbody").append(trs);
// Reset siblings
$table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
$this.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
// Trigger `aftertablesort` event. Similar to `beforetablesort`
$table.trigger("aftertablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
}, 10);
});
});
};
// Enum containing sorting directions
$.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
$.fn.stupidtable.default_sort_fns = {
"int": function(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
},
"float": function(a, b) {
return parseFloat(a) - parseFloat(b);
},
"string": function(a, b) {
if (a < b) return -1;
if (a > b) return +1;
return 0;
},
"string-ins": function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
};
})(jQuery);
...@@ -20,6 +20,27 @@ $(function() { ...@@ -20,6 +20,27 @@ $(function() {
}); });
return false; return false;
}); });
/* template table sort */
var ttable = $(".template-list-table").stupidtable();
ttable.on("beforetablesort", function(event, data) {
// pass
});
ttable.on("aftertablesort", function(event, data) {
$(".template-list-table thead th i").remove();
var icon_html = '<i class="icon-sort-' + (data.direction == "desc" ? "up" : "down") + ' pull-right" style="position: absolute;"></i>';
$(".template-list-table thead th").eq(data.column).append(icon_html);
});
// only if js is enabled
$(".template-list-table thead th").css("cursor", "pointer");
$(".template-list-table th a").on("click", function(event) {
event.preventDefault();
});
}); });
......
...@@ -17,14 +17,14 @@ $(function() { ...@@ -17,14 +17,14 @@ $(function() {
if (ctrlDown) { if (ctrlDown) {
setRowColor($(this)); setRowColor($(this));
if(!$(this).hasClass('vm-list-selected')) { if(!$(this).hasClass('vm-list-selected')) {
selected.splice(selected.indexOf($(this).index()), 1); selected.splice(getSelectedIndex($(this).index()), 1);
} else { } else {
selected.push($(this).index()); selected.push({'index': $(this).index(), 'vm': $(this).data("vm-pk")});
} }
retval = false; retval = false;
} else if(shiftDown) { } else if(shiftDown) {
if(selected.length > 0) { if(selected.length > 0) {
start = selected[selected.length - 1] + 1; start = selected[selected.length - 1]['index'] + 1;
end = $(this).index(); end = $(this).index();
if(start > end) { if(start > end) {
...@@ -32,8 +32,9 @@ $(function() { ...@@ -32,8 +32,9 @@ $(function() {
} }
for(var i = start; i <= end; i++) { for(var i = start; i <= end; i++) {
if(selected.indexOf(i) < 0) { var vm = $(".vm-list-table tbody tr").eq(i).data("vm-pk");
selected.push(i); if(!isAlreadySelected(vm)) {
selected.push({'index': i, 'vm': vm});
setRowColor($('.vm-list-table tbody tr').eq(i)); setRowColor($('.vm-list-table tbody tr').eq(i));
} }
} }
...@@ -42,13 +43,13 @@ $(function() { ...@@ -42,13 +43,13 @@ $(function() {
} else { } else {
$('.vm-list-selected').removeClass('vm-list-selected'); $('.vm-list-selected').removeClass('vm-list-selected');
$(this).addClass('vm-list-selected'); $(this).addClass('vm-list-selected');
selected = [$(this).index()]; selected = [{'index': $(this).index(), 'vm': $(this).data("vm-pk")}];
} }
// reset btn disables // reset btn disables
$('.vm-list-table tbody tr .btn').attr('disabled', false); $('.vm-list-table tbody tr .btn').attr('disabled', false);
// show/hide group controls // show/hide group controls
if(selected.length > 1) { if(selected.length > 0) {
$('.vm-list-group-control a').attr('disabled', false); $('.vm-list-group-control a').attr('disabled', false);
for(var i = 0; i < selected.length; i++) { for(var i = 0; i < selected.length; i++) {
$('.vm-list-table tbody tr').eq(selected[i]).find('.btn').attr('disabled', true); $('.vm-list-table tbody tr').eq(selected[i]).find('.btn').attr('disabled', true);
...@@ -61,7 +62,7 @@ $(function() { ...@@ -61,7 +62,7 @@ $(function() {
$('#vm-list-group-migrate').click(function() { $('#vm-list-group-migrate').click(function() {
console.log(collectIds(selected)); // pass?
}); });
$('.vm-list-details').popover({ $('.vm-list-details').popover({
...@@ -131,8 +132,9 @@ $(function() { ...@@ -131,8 +132,9 @@ $(function() {
$('#vm-list-group-select-all').click(function() { $('#vm-list-group-select-all').click(function() {
$('.vm-list-table tbody tr').each(function() { $('.vm-list-table tbody tr').each(function() {
var index = $(this).index(); var index = $(this).index();
if(selected.indexOf(index) < 0) { var vm = $(this).data("vm-pk");
selected.push(index); if(!isAlreadySelected(vm)) {
selected.push({'index': index, 'vm': vm});
$(this).addClass('vm-list-selected'); $(this).addClass('vm-list-selected');
} }
}); });
...@@ -154,13 +156,51 @@ $(function() { ...@@ -154,13 +156,51 @@ $(function() {
); );
return false; return false;
}); });
/* table sort */
var table = $(".vm-list-table").stupidtable();
table.on("beforetablesort", function(event, data) {
$(".table-sorting").show();
});
table.on("aftertablesort", function(event, data) {
// this didn't work ;;
// var th = $("this").find("th");
$(".table-sorting").hide();
$(".vm-list-table thead th i").remove();
var icon_html = '<i class="icon-sort-' + (data.direction == "desc" ? "up" : "down") + ' pull-right"></i>';
$(".vm-list-table thead th").eq(data.column).append(icon_html);
});
// only if js is enabled
$(".vm-list-table thead th").css("cursor", "pointer");
$(".vm-list-table th a").on("click", function(event) {
event.preventDefault();
});
}); });
function isAlreadySelected(vm) {
for(var i=0; i<selected.length; i++)
if(selected[i].vm == vm)
return true;
return false;
}
function getSelectedIndex(index) {
for(var i=0; i<selected.length; i++)
if(selected[i].index == index)
return i;
return -1;
}
function collectIds(rows) { function collectIds(rows) {
var ids = []; var ids = [];
for(var i = 0; i < rows.length; i++) { for(var i = 0; i < rows.length; i++) {
var div = $('td:first-child div', $('.vm-list-table tbody tr').eq(rows[i])); ids.push(rows[i].vm);
ids.push(div.prop('id').replace('vm-', ''));
} }
return ids; return ids;
} }
......
...@@ -220,21 +220,35 @@ class TemplateListTable(Table): ...@@ -220,21 +220,35 @@ class TemplateListTable(Table):
name = LinkColumn( name = LinkColumn(
'dashboard.views.template-detail', 'dashboard.views.template-detail',
args=[A('pk')], args=[A('pk')],
attrs={'th': {'data-sort': "string"}}
) )
num_cores = Column( num_cores = Column(
verbose_name=_("Cores"), verbose_name=_("Cores"),
attrs={'th': {'data-sort': "int"}}
) )
ram_size = TemplateColumn( ram_size = TemplateColumn(
"{{ record.ram_size }} Mb", "{{ record.ram_size }} Mb",
attrs={'th': {'data-sort': "string"}}
) )
lease = TemplateColumn( lease = TemplateColumn(
"{{ record.lease.name }}", "{{ record.lease.name }}",
verbose_name=_("Lease"), verbose_name=_("Lease"),
attrs={'th': {'data-sort': "string"}}
)
arch = Column(
attrs={'th': {'data-sort': "string"}}
)
system = Column(
attrs={'th': {'data-sort': "string"}}
)
access_method = Column(
attrs={'th': {'data-sort': "string"}}
) )
actions = TemplateColumn( actions = TemplateColumn(
verbose_name=_("Actions"), verbose_name=_("Actions"),
template_name="dashboard/template-list/column-template-actions.html", template_name="dashboard/template-list/column-template-actions.html",
attrs={'th': {'class': 'template-list-table-thin'}}, attrs={'th': {'class': 'template-list-table-thin'}},
orderable=False,
) )
class Meta: class Meta:
...@@ -242,8 +256,9 @@ class TemplateListTable(Table): ...@@ -242,8 +256,9 @@ class TemplateListTable(Table):
attrs = {'class': ('table table-bordered table-striped table-hover' attrs = {'class': ('table table-bordered table-striped table-hover'
' template-list-table')} ' template-list-table')}
fields = ('name', 'num_cores', 'ram_size', 'arch', fields = ('name', 'num_cores', 'ram_size', 'arch',
'system', 'access_method', 'lease', 'state', 'system', 'access_method', 'lease', 'actions', )
'actions', )
prefix = "template-"
class LeaseListTable(Table): class LeaseListTable(Table):
...@@ -252,21 +267,24 @@ class LeaseListTable(Table): ...@@ -252,21 +267,24 @@ class LeaseListTable(Table):
args=[A('pk')], args=[A('pk')],
) )
suspend_in = TemplateColumn( suspend_interval_seconds = TemplateColumn(
"{{ record.get_readable_suspend_time }}" "{{ record.get_readable_suspend_time }}"
) )
delete_in = TemplateColumn( delete_interval_seconds = TemplateColumn(
"{{ record.get_readable_delete_time }}" "{{ record.get_readable_delete_time }}"
) )
actions = TemplateColumn( actions = TemplateColumn(
verbose_name=_("Actions"), verbose_name=_("Actions"),
template_name="dashboard/template-list/column-lease-actions.html" template_name="dashboard/template-list/column-lease-actions.html",
orderable=False,
) )
class Meta: class Meta:
model = Lease model = Lease
attrs = {'class': ('table table-bordered table-striped table-hover' attrs = {'class': ('table table-bordered table-striped table-hover'
' lease-list-table')} ' lease-list-table')}
fields = ('name', 'suspend_in', 'delete_in', ) fields = ('name', 'suspend_interval_seconds',
'delete_interval_seconds', )
prefix = "lease-"
...@@ -55,5 +55,6 @@ ...@@ -55,5 +55,6 @@
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/template-list.js"></script> <script src="{{ STATIC_URL}}dashboard/template-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/js/stupidtable.min.js"></script>
{% endblock %} {% endblock %}
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="pull-right table-sorting">
{% trans "Sorting ... " %}
<!--<i class="icon-refresh icon-spin icon-2x"></i>-->
</div>
<h3 class="no-margin"><i class="icon-desktop"></i> {% trans "Virtual machines" %}</h3> <h3 class="no-margin"><i class="icon-desktop"></i> {% trans "Virtual machines" %}</h3>
</div> </div>
<div class="pull-right" style="max-width: 250px; margin-top: 15px; margin-right: 15px;"> <div class="pull-right" style="max-width: 250px; margin-top: 15px; margin-right: 15px;">
...@@ -32,22 +36,39 @@ ...@@ -32,22 +36,39 @@
<div class="panel-body"> <div class="panel-body">
<table class="table table-bordered table-striped table-hover vm-list-table"> <table class="table table-bordered table-striped table-hover vm-list-table">
<thead><tr> <thead><tr>
<th class="orderable pk sortable vm-list-table-thin"><a href="?sort=pk">{% trans "ID" %}</a></th> <th data-sort="int" class="orderable pk sortable vm-list-table-thin" style="min-width: 50px;">
<th class="name orderable sortable"><a href="?sort=name">{% trans "Name" %}</a></th> {% trans "ID" as t %}
<th>{% trans "State" %}</th> {% include "dashboard/vm-list/header-link.html" with name=t sort="pk" %}
<th class="orderable sortable"><a href="?sort=owner">{% trans "Owner" %}</a></th> </th>
{% if user.is_superuser %}<th class="orderable sortable"><a href="?sort=node">{% trans "Node" %}</a></th>{% endif %} <th data-sort="string" class="name orderable sortable">
{% trans "Name" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="name" %}
</th>
<th data-sort="string">
{% trans "State" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="status" %}
</th>
<th data-sort="string" class="orderable sortable">
{% trans "Owner" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="owner" %}
</th>
{% if user.is_superuser %}<th data-sort="string" class="orderable sortable">
{% trans "Node" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="node" %}
</th>{% endif %}
</tr></thead><tbody> </tr></thead><tbody>
{% for i in object_list %} {% for i in object_list %}
<tr class="{% cycle 'odd' 'even' %}"> <tr class="{% cycle 'odd' 'even' %}" data-vm-pk="{{ i.pk }}">
<td class="pk"><div id="vm-{{i.pk}}">{{i.pk}}</div> </td> <td class="pk"><div id="vm-{{i.pk}}">{{i.pk}}</div> </td>
<td class="name"><a class="real-link" href="{% url "dashboard.views.detail" i.pk %}">{{ i.name }}</a> </td> <td class="name"><a class="real-link" href="{% url "dashboard.views.detail" i.pk %}">{{ i.name }}</a> </td>
<td class="state">{{ i.get_status_display }}</td> <td class="state">{{ i.get_status_display }}</td>
<td>{{ i.owner }}</td> <td>{{ i.owner }}</td>
{% if user.is_superuser %}<td>{{ i.node.name|default:"-" }}</td>{% endif %} {% if user.is_superuser %}
<td data-sort-value="{{ i.node.normalized_name }}">{{ i.node.name|default:"-" }}</td>
{% endif %}
</tr> </tr>
{% empty %} {% empty %}
<tr><td colspan="4">{% trans "You have no virtual machines." %}</td></tr> <tr><td colspan="5"><strong>{% trans "You have no virtual machines." %}</strong></td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
...@@ -92,4 +113,5 @@ ...@@ -92,4 +113,5 @@
{% block extra_js %} {% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/vm-list.js"></script> <script src="{{ STATIC_URL}}dashboard/vm-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/vm-common.js"></script> <script src="{{ STATIC_URL}}dashboard/vm-common.js"></script>
<script src="{{ STATIC_URL}}dashboard/js/stupidtable.min.js"></script>
{% endblock %} {% endblock %}
<a href="?sort={% if request.GET.sort == sort %}-{% endif %}{{ sort }}">{{ name }}</a>
...@@ -1053,7 +1053,8 @@ class TemplateList(LoginRequiredMixin, SingleTableView): ...@@ -1053,7 +1053,8 @@ class TemplateList(LoginRequiredMixin, SingleTableView):
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
context = super(TemplateList, self).get_context_data(*args, **kwargs) context = super(TemplateList, self).get_context_data(*args, **kwargs)
context['lease_table'] = LeaseListTable(Lease.objects.all()) context['lease_table'] = LeaseListTable(Lease.objects.all(),
request=self.request)
return context return context
def get_queryset(self): def get_queryset(self):
...@@ -1126,6 +1127,14 @@ class VmList(LoginRequiredMixin, ListView): ...@@ -1126,6 +1127,14 @@ class VmList(LoginRequiredMixin, ListView):
s = self.request.GET.get("s") s = self.request.GET.get("s")
if s: if s:
queryset = queryset.filter(name__icontains=s) queryset = queryset.filter(name__icontains=s)
sort = self.request.GET.get("sort")
# remove "-" that means descending order
# also check if the column name is valid
if (sort and
(sort[1:] if sort[0] == "-" else sort)
in [i.name for i in Instance._meta.fields] + ["pk"]):
queryset = queryset.order_by(sort)
return queryset.select_related('owner', 'node') return queryset.select_related('owner', 'node')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment