Commit fda521ab by Oláh István Gergely

dashboard: list groups, group users

parent 4a871470
...@@ -114,6 +114,10 @@ body { ...@@ -114,6 +114,10 @@ body {
min-height: 20em; min-height: 20em;
} }
#group-detail-panel .panel-body {
min-height: 20em;
}
:link i:before:hover { :link i:before:hover {
text-decoration: none !important; text-decoration: none !important;
} }
...@@ -166,19 +170,20 @@ body { ...@@ -166,19 +170,20 @@ body {
} }
#vm-details-rename, #vm-details-rename *, #vm-details-h1-name, #vm-list-rename, #vm-list-rename *, #vm-details-rename, #vm-details-rename *, #vm-details-h1-name, #vm-list-rename, #vm-list-rename *,
#node-details-rename, #node-details-rename *, #node-details-h1-name, #node-list-rename, #node-list-rename * { #node-details-rename, #node-details-rename *, #node-details-h1-name, #node-list-rename, #node-list-rename *#group-details-rename, #group-details-rename *, #group-details-h1-name, #group-list-rename, #group-list-rename * {
display: inline; display: inline;
} }
#vm-details-rename, #vm-list-rename, #node-details-rename, #node-list-rename { #vm-details-rename, #vm-list-rename, #node-details-rename, #node-list-rename, #group-details-rename, #group-list-rename {
display: none; display: none;
} }
#vm-details-rename-name, #node-details-rename-name { #vm-details-rename-name, #node-details-rename-name, #group-details-rename-name {
max-width: 160px; max-width: 160px;
} }
#vm-list-rename-name, #node-list-rename-name { #vm-list-rename-name, #node-list-rename-name, #group-list-rename-name {
max-width: 100px; max-width: 100px;
} }
......
var ctrlDown, shiftDown = false;
var ctrlKey = 17;
var shiftKey = 16;
var selected = [];
$(function() {
$(document).keydown(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = true;
if (e.keyCode == shiftKey) shiftDown = true;
}).keyup(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = false;
if (e.keyCode == shiftKey) shiftDown = false;
});
$('.group-list-table tbody').find('tr').mousedown(function() {
var retval = true;
if (ctrlDown) {
setRowColor($(this));
if(!$(this).hasClass('group-list-selected')) {
selected.splice(selected.indexOf($(this).index()), 1);
} else {
selected.push($(this).index());
}
retval = false;
} else if(shiftDown) {
if(selected.length > 0) {
start = selected[selected.length - 1] + 1;
end = $(this).index();
if(start > end) {
var tmp = start - 1; start = end; end = tmp - 1;
}
for(var i = start; i <= end; i++) {
if(selected.indexOf(i) < 0) {
selected.push(i);
setRowColor($('.group-list-table tbody tr').eq(i));
}
}
}
retval = false;
} else {
$('.group-list-selected').removeClass('group-list-selected');
$(this).addClass('group-list-selected');
selected = [$(this).index()];
}
// reset btn disables
$('.group-list-table tbody tr .btn').attr('disabled', false);
// show/hide group controls
if(selected.length > 1) {
$('.group-list-group-control a').attr('disabled', false);
for(var i = 0; i < selected.length; i++) {
$('.group-list-table tbody tr').eq(selected[i]).find('.btn').attr('disabled', true);
}
} else {
$('.group-list-group-control a').attr('disabled', true);
}
return retval;
});
$('#group-list-group-migrate').click(function() {
console.log(collectIds(selected));
});
$('tbody a').mousedown(function(e) {
// parent tr doesn't get selected when clicked
e.stopPropagation();
});
$('tbody a').click(function(e) {
// browser doesn't jump to top when clicked the buttons
if(!$(this).hasClass('real-link')) {
return false;
}
});
/* rename */
$("#group-list-rename-button, .group-details-rename-button").click(function() {
$("#group-list-column-name", $(this).closest("tr")).hide();
$("#group-list-rename", $(this).closest("tr")).css('display', 'inline');
});
/* rename ajax */
$('.group-list-rename-submit').click(function() {
var row = $(this).closest("tr")
var name = $('#group-list-rename-name', row).val();
var url = '/dashboard/group/' + row.children("td:first-child").text().replace(" ", "") + '/';
$.ajax({
method: 'POST',
url: url,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$("#group-list-column-name", row).html(
$("<a/>", {
'class': "real-link",
href: "/dashboard/group/" + data['node_pk'] + "/",
text: data['new_name']
})
).show();
$('#group-list-rename', row).hide();
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("uhoh", "danger");
}
});
return false;
});
/* group actions */
/* select all */
$('#group-list-group-select-all').click(function() {
$('.group-list-table tbody tr').each(function() {
var index = $(this).index();
if(selected.indexOf(index) < 0) {
selected.push(index);
$(this).addClass('group-list-selected');
}
});
if(selected.length > 0)
$('.group-list-group-control a').attr('disabled', false);
return false;
});
/* mass vm delete */
$('#group-list-group-delete').click(function() {
addModalConfirmation(massDeleteVm,
{
'url': '/dashboard/group/mass-delete/',
'data': {
'selected': selected,
'v': collectIds(selected)
}
}
);
return false;
});
});
function collectIds(rows) {
var ids = [];
for(var i = 0; i < rows.length; i++) {
var div = $('td:first-child div', $('.group-list-table tbody tr').eq(rows[i]));
ids.push(div.prop('id').replace('node-', ''));
}
return ids;
}
function setRowColor(row) {
if(!row.hasClass('group-list-selected')) {
row.addClass('group-list-selected');
} else {
row.removeClass('group-list-selected');
}
}
from django.contrib.auth.models import Group from django.contrib.auth.models import Group, User
from django_tables2 import Table, A from django_tables2 import Table, A
from django_tables2.columns import (TemplateColumn, Column, BooleanColumn, from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
LinkColumn) LinkColumn)
...@@ -103,11 +103,55 @@ class NodeListTable(Table): ...@@ -103,11 +103,55 @@ class NodeListTable(Table):
class GroupListTable(Table): class GroupListTable(Table):
pk = TemplateColumn(
template_name='dashboard/group-list/column-id.html',
verbose_name="ID",
attrs={'th': {'class': 'group-list-table-thin'}},
)
name = TemplateColumn(
template_name="dashboard/group-list/column-name.html"
)
number_of_users = TemplateColumn(
template_name='dashboard/group-list/column-users.html',
attrs={'th': {'class': 'group-list-table-admin'}},
)
admin = TemplateColumn(
template_name='dashboard/group-list/column-admin.html',
attrs={'th': {'class': 'group-list-table-admin'}},
)
actions = TemplateColumn(
attrs={'th': {'class': 'group-list-table-thin'}},
template_code=('{% include "dashboard/group-list/column-'
'actions.html" with btn_size="btn-xs" %}'),
)
class Meta: class Meta:
model = Group model = Group
attrs = {'class': ('table table-bordered table-striped table-hover ' attrs = {'class': ('table table-bordered table-striped table-hover '
'group-list-table')} 'group-list-table')}
fields = ('id', 'name', ) fields = ('pk', 'name', )
class UserListTable(Table):
pk = TemplateColumn(
template_name='dashboard/vm-list/column-id.html',
verbose_name="ID",
attrs={'th': {'class': 'vm-list-table-thin'}},
)
username = TemplateColumn(
template_name="dashboard/group-list/column-username.html"
)
class Meta:
model = User
attrs = {'class': ('table table-bordered table-striped table-hover '
'vm-list-table')}
fields = ('pk', 'username', )
class NodeVmListTable(Table): class NodeVmListTable(Table):
...@@ -147,6 +191,11 @@ class NodeVmListTable(Table): ...@@ -147,6 +191,11 @@ class NodeVmListTable(Table):
fields = ('pk', 'name', 'state', 'time_of_suspend', 'time_of_delete', ) fields = ('pk', 'name', 'state', 'time_of_suspend', 'time_of_delete', )
class UserListTablex(Table):
class Meta:
model = User
class TemplateListTable(Table): class TemplateListTable(Table):
name = LinkColumn( name = LinkColumn(
'dashboard.views.template-detail', 'dashboard.views.template-detail',
......
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block content %}
<div class="alert alert-info">
Tip #1: you can select multiple vm instances while holding down the <strong>CTRL</strong> key!
</div>
<div class="alert alert-info">
Tip #2: if you want to select multiple instances by one click select an instance then hold down <strong>SHIFT</strong> key and select another one!
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin"><i class="icon-user"></i> Your users</h3>
</div>
<div class="panel-body group-list-group-control">
<p>
<strong>Group actions</strong>
<button id="group-list-group-select-all" class="btn btn-info btn-xs">Select all</button>
<a id="group-list-group-delete" disabled href="#" class="btn btn-danger btn-xs"><i class="icon-remove"></i> Discard</a>
</p>
</div>
<div id="table_container">
<div id="rendered_table" class="panel-body">
{% render_table table %}
</div>
</div>
</div>
</div>
</div>
<style>
.popover {
max-width: 600px;
}
.group-list-selected, .group-list-selected td {
background-color: #e8e8e8 !important;
}
.group-list-selected:hover, .group-list-selected:hover td {
background-color: #d0d0d0 !important;
}
.group-list-selected td:first-child {
font-weight: bold;
}
.group-list-table-thin {
width: 10px;
}
.group-list-table-admin {
width: 130px;
}
</style>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/group-list.js"></script>
{% endblock %}
...@@ -15,16 +15,13 @@ ...@@ -15,16 +15,13 @@
<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">
<h3 class="no-margin"><i class="icon-desktop"></i> Your nodes</h3> <h3 class="no-margin"><i class="icon-group"></i> Your groups</h3>
</div> </div>
<div class="panel-body node-list-group-control"> <div class="panel-body group-list-group-control">
<p> <p>
<strong>Group actions</strong> <strong>Group actions</strong>
<button id="node-list-group-select-all" class="btn btn-info btn-xs">Select all</button> <button id="group-list-group-select-all" class="btn btn-info btn-xs">Select all</button>
<a class="btn btn-default btn-xs" id="node-list-group-migrate" disabled><i class="icon-truck"></i> Migrate</a> <a id="group-list-group-delete" disabled href="#" class="btn btn-danger btn-xs"><i class="icon-remove"></i> Discard</a>
<a disabled href="#" class="btn btn-default btn-xs"><i class="icon-refresh"></i> Reboot</a>
<a disabled href="#" class="btn btn-default btn-xs"><i class="icon-off"></i> Shutdown</a>
<a id="node-list-group-delete" disabled href="#" class="btn btn-danger btn-xs"><i class="icon-remove"></i> Discard</a>
</p> </p>
</div> </div>
<div id="table_container"> <div id="table_container">
...@@ -41,28 +38,28 @@ ...@@ -41,28 +38,28 @@
max-width: 600px; max-width: 600px;
} }
.node-list-selected, .node-list-selected td { .group-list-selected, .group-list-selected td {
background-color: #e8e8e8 !important; background-color: #e8e8e8 !important;
} }
.node-list-selected:hover, .node-list-selected:hover td { .group-list-selected:hover, .group-list-selected:hover td {
background-color: #d0d0d0 !important; background-color: #d0d0d0 !important;
} }
.node-list-selected td:first-child { .group-list-selected td:first-child {
font-weight: bold; font-weight: bold;
} }
.node-list-table-thin { .group-list-table-thin {
width: 10px; width: 10px;
} }
.node-list-table-admin { .group-list-table-admin {
width: 130px; width: 130px;
} }
</style> </style>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/node-list.js"></script> <script src="{{ STATIC_URL}}dashboard/group-list.js"></script>
{% endblock %} {% endblock %}
<div class="btn-group">
<button type="button" class="btn {{ btn_size }} btn-warning dropdown-toggle" data-toggle="dropdown">Action <i class="icon-caret-down"></i></button>
<ul class="dropdown-menu" role="menu">
<li><a data-node-pk="{{ record.pk }}" class="real-link node-delete" href="{% url "dashboard.views.delete-node" pk=record.pk %}?next={{ request.path }}"><i class="icon-trash"></i> Delete</a></li>
</ul>
</div>
<a id="group-list-rename-button" class="btn btn-default btn-xs" title data-original-title="Rename">
<i class="icon-pencil"></i>
</a>
<a class="btn btn-info btn-xs group-list-details" href="#"
>Member list</a>
<div id="node-{{ record.pk }}">{{ record.pk }}</div>
{% load i18n %}
<div id="group-list-rename">
<form action="{% url "dashboard.views.group-detail" pk=record.pk %}" method="POST" id="group-list-rename-form">
{% csrf_token %}
<input id="group-list-rename-name" class="form-control input-sm" name="new_name" type="text" value="{{ record.name }}"/>
<button type="submit" class="group-list-rename-submit btn btn-sm">{% trans "Rename" %}</button>
</form>
</div>
<div id="group-list-column-name">
<a class="real-link" href="{% url "dashboard.views.group-detail" pk=record.pk %}">{{ record.name }}</a>
</div>
{% load i18n %}
<div id="group-list-column-users">
<a class="real-link" href="{% url "dashboard.views.group-detail" pk=record.pk %}">{{ record.user_set.count }}</a>
</div>
<tr>
<!--<td><input type="checkbox"/ class="vm-checkbox" id="vm-1825{{ c }}"></td>-->
<td>
<div id="vm-1{{ c }}">1{{ c }}</div>
</td>
<td><a href="" class="real-link">network-devenv</a></td>
<td>running</td>
<td>10 days</td>
<td>1 month</td>
<td>
<a class="btn btn-default btn-xs" title data-original-title="Migrate">
<i class="icon-truck"></i>
</a>
<a class="btn btn-default btn-xs" title data-original-title="Rename">
<i class="icon-pencil"></i>
</a>
<a href="#" class="btn btn-default btn-xs vm-list-connect" data-toggle="popover"
data-content='
Belépés: <input style="width: 300px;" type="text" class="form-control" value="ssh cloud@vm.ik.bme.hu -p22312"/>
Jelszó: <input style="width: 300px;" type="text" class="form-control" value="asdfkicsiasdfkocsi"/>
'>Connect</a>
</td>
<td>
<a class="btn btn-info btn-xs vm-list-details" href="#" data-toggle="popover"
data-content='
<h4>Quick details</h4>
<dl class="dl-horizontal">
<dt>Number of cores:</dt><dd>4</dd>
<dt>Memory:</dt> <dd>512 MB</dd>
<dt>Architecture:</td><dd>x86-64</dd>
</dl>
<dl>
<dt>IPv4 address:</dt><dd>10.9.8.7</dd>
<dt>IPv6 address:</dt><dd> 2001:2001:2001:2001:2001:2001::</dd>
<dt>DNS name:</dt><dd>1825.vm.ik.bme.hu</dd>
</ul>
'>Details</a>
</td>
<td>
<div class="btn-group">
<button type="button" class="btn btn-xs btn-warning dropdown-toggle" data-toggle="dropdown">Action <i class="icon-caret-down"></i></button>
<ul class="dropdown-menu" role="menu">
<li><a href="#"><i class="icon-refresh"></i> Reboot</a></li>
<li><a href="#"><i class="icon-off"></i> Shutdown</a></li>
<li><a href="#"><i class="icon-remove"></i> Discard</a></li>
</ul>
</div>
</td>
</tr>
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
</div> </div>
<div class="list-group" id="vm-list-view"> <div class="list-group" id="vm-list-view">
{% for i in groups %} {% for i in groups %}
<a href="#" class="list-group-item"> <a href="{% url "dashboard.views.group-detail" pk=i.pk %}" class="list-group-item real-link">
<i class="icon-file"></i> {{ i.name }}<div class="pull-right"><i class="icon-download-alt "></i></div> <i class="icon-group"></i> {{ i.name }}
</a> </a>
{% endfor %} {% endfor %}
<div href="#" class="list-group-item list-group-footer text-right"> <div href="#" class="list-group-item list-group-footer text-right">
<div class="row"> <div class="row">
......
...@@ -7,7 +7,7 @@ from .views import ( ...@@ -7,7 +7,7 @@ from .views import (
TransferOwnershipView, TransferOwnershipConfirmView, NodeDelete, TransferOwnershipView, TransferOwnershipConfirmView, NodeDelete,
TemplateList, LeaseDetail, NodeCreate, LeaseCreate, TemplateCreate, TemplateList, LeaseDetail, NodeCreate, LeaseCreate, TemplateCreate,
FavouriteView, NodeStatus, GroupList, TemplateDelete, LeaseDelete, FavouriteView, NodeStatus, GroupList, TemplateDelete, LeaseDelete,
VmGraphView, TemplateAclUpdateView VmGraphView, TemplateAclUpdateView, GroupDetailView,
) )
urlpatterns = patterns( urlpatterns = patterns(
...@@ -69,4 +69,6 @@ urlpatterns = patterns( ...@@ -69,4 +69,6 @@ urlpatterns = patterns(
r'(?P<time>[0-9]{1,2}[hdwy])$'), r'(?P<time>[0-9]{1,2}[hdwy])$'),
VmGraphView.as_view(), VmGraphView.as_view(),
name='dashboard.views.vm-graph'), name='dashboard.views.vm-graph'),
url(r'^group/(?P<pk>\d+)/$', GroupDetailView.as_view(),
name='dashboard.views.group-detail'),
) )
...@@ -31,7 +31,8 @@ from .forms import ( ...@@ -31,7 +31,8 @@ from .forms import (
VmCreateForm, TemplateForm, LeaseForm, NodeForm, HostForm, DiskAddForm, VmCreateForm, TemplateForm, LeaseForm, NodeForm, HostForm, DiskAddForm,
) )
from .tables import (VmListTable, NodeListTable, NodeVmListTable, from .tables import (VmListTable, NodeListTable, NodeVmListTable,
TemplateListTable, LeaseListTable, GroupListTable) TemplateListTable, LeaseListTable, GroupListTable,
UserListTable)
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate, from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
InstanceActivity, Node, instance_activity, Lease, InstanceActivity, Node, instance_activity, Lease,
Interface) Interface)
...@@ -488,6 +489,44 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): ...@@ -488,6 +489,44 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
kwargs={'pk': self.object.pk})) kwargs={'pk': self.object.pk}))
class GroupDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
template_name = "dashboard/group-detail.html"
model = Group
table_pagination = False
def get_context_data(self, **kwargs):
context = super(GroupDetailView, self).get_context_data(**kwargs)
instances = Group.objects.filter(name=self.object)
context['table'] = UserListTable(instances[0].user_set.all())
return context
def post(self, request, *args, **kwargs):
if request.POST.get('new_name'):
return self.__set_name(request)
def __set_name(self, request):
self.object = self.get_object()
new_name = request.POST.get("new_name")
Group.objects.filter(pk=self.object.pk).update(
**{'name': new_name})
success_message = _("Node successfully renamed!")
if request.is_ajax():
response = {
'message': success_message,
'new_name': new_name,
'node_pk': self.object.pk
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.object.pk}))
class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin): class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
......
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