Commit ae77d3c8 by Gregory Nagy

Merge

parents b1f4a5e6 e1795208
...@@ -19,7 +19,6 @@ from firewall.models import Vlan, Host ...@@ -19,7 +19,6 @@ from firewall.models import Vlan, Host
from storage.models import Disk, DataStore from storage.models import Disk, DataStore
from vm.models import InstanceTemplate, Lease, InterfaceTemplate, Node from vm.models import InstanceTemplate, Lease, InterfaceTemplate, Node
VLANS = Vlan.objects.all() VLANS = Vlan.objects.all()
DISKS = Disk.objects.exclude(type="qcow2-snap") DISKS = Disk.objects.exclude(type="qcow2-snap")
...@@ -554,7 +553,6 @@ class TemplateForm(forms.ModelForm): ...@@ -554,7 +553,6 @@ class TemplateForm(forms.ModelForm):
Field('description'), Field('description'),
Field("parent", type="hidden"), Field("parent", type="hidden"),
Field("system"), Field("system"),
Field("state"),
), ),
Fieldset( Fieldset(
_("Exeternal"), _("Exeternal"),
...@@ -569,6 +567,7 @@ class TemplateForm(forms.ModelForm): ...@@ -569,6 +567,7 @@ class TemplateForm(forms.ModelForm):
class Meta: class Meta:
model = InstanceTemplate model = InstanceTemplate
exclude = ('state', )
class LeaseForm(forms.ModelForm): class LeaseForm(forms.ModelForm):
......
...@@ -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;
} }
......
...@@ -112,12 +112,12 @@ $(function () { ...@@ -112,12 +112,12 @@ $(function () {
$('.vm-delete').click(function() { $('.vm-delete').click(function() {
var vm_pk = $(this).data('vm-pk'); var vm_pk = $(this).data('vm-pk');
var dir = window.location.pathname.indexOf('list') == -1; var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteVm, addModalConfirmation(deleteObject,
{ 'url': '/dashboard/vm/delete/' + vm_pk + '/', { 'url': '/dashboard/vm/delete/' + vm_pk + '/',
'data': [], 'data': [],
'vm_pk': vm_pk, 'pk': vm_pk,
'type': "vm",
'redirect': dir}); 'redirect': dir});
return false; return false;
}); });
...@@ -125,10 +125,38 @@ $(function () { ...@@ -125,10 +125,38 @@ $(function () {
$('.node-delete').click(function() { $('.node-delete').click(function() {
var node_pk = $(this).data('node-pk'); var node_pk = $(this).data('node-pk');
var dir = window.location.pathname.indexOf('list') == -1; var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteNode, addModalConfirmation(deleteObject,
{ 'url': '/dashboard/node/delete/' + node_pk + '/', { 'url': '/dashboard/node/delete/' + node_pk + '/',
'data': [], 'data': [],
'node_pk': node_pk, 'pk': node_pk,
'type': "node",
'redirect': dir});
return false;
});
/* for Node removes buttons */
$('.group-delete').click(function() {
var group_pk = $(this).data('group-pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteObject,
{ 'url': '/dashboard/group/delete/' + group_pk + '/',
'data': [],
'type': "group",
'pk': group_pk,
'redirect': dir});
return false;
});
/* for Group removes buttons */
$('.group-delete').click(function() {
var group_pk = $(this).data('group-pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteGroup,
{ 'url': '/dashboard/group/delete/' + group_pk + '/',
'data': [],
'group_pk': group_pk,
'redirect': dir}); 'redirect': dir});
return false; return false;
...@@ -222,7 +250,7 @@ function refreshSliders() { ...@@ -222,7 +250,7 @@ function refreshSliders() {
/* deletes the VM with the pk /* deletes the VM with the pk
* if dir is true, then redirect to the dashboard landing page * if dir is true, then redirect to the dashboard landing page
* else it adds a success message */ * else it adds a success message */
function deleteVm(data) { function deleteObject(data) {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
data: {'redirect': data['redirect']}, data: {'redirect': data['redirect']},
...@@ -232,7 +260,7 @@ function deleteVm(data) { ...@@ -232,7 +260,7 @@ function deleteVm(data) {
if(!data['redirect']) { if(!data['redirect']) {
selected = []; selected = [];
addMessage(re['message'], 'success'); addMessage(re['message'], 'success');
$('a[data-vm-pk="' + data['vm_pk'] + '"]').closest('tr').fadeOut(function() { $('a[data-'+data['type']+'-pk="' + data['pk'] + '"]').closest('tr').fadeOut(function() {
$(this).remove(); $(this).remove();
}); });
} else { } else {
......
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');
}
}
...@@ -162,8 +162,6 @@ $(function() { ...@@ -162,8 +162,6 @@ $(function() {
// change big status span // change big status span
$('#node-info-pane').load(location.href+" #node-info-data"); $('#node-info-pane').load(location.href+" #node-info-data");
// change resources
$('#resources').load(location.href+" #vm-details-resources-form");
} }
$('#table_container').on('click','.node-enable',function() { $('#table_container').on('click','.node-enable',function() {
......
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 %}
{% block content %}
<div class="body-content">
<div class="page-header">
<h1>
<div id="group-details-rename">
<form action="" method="POST" id="group-details-rename-form">
{% csrf_token %}
<input id="group-details-rename-name" class="form-control" name="new_name" type="text" value="{{ group.name }}"/>
<button type="submit" id="group-details-rename-submit" class="btn">{% trans "Rename" %}</button>
</form>
</div>
<div id="group-details-h1-name">
{{ group.name }}
</div>
</h1>
</div>
<div class="row">
<div class="col-md-4" id="group-info-pane">
<div class="big">
<div class="btn-group">
<button type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown">Action <i class="icon-caret-down"></i></button>
<ul class="dropdown-menu" role="menu">
<li><a href="#" class="group-details-rename-button"><i class="icon-pencil"></i> Rename</a></li>
<li><a data-group-pk="{{ group.pk }}" class="group-delete" href="{% url "dashboard.views.delete-group" pk=group.pk %}"><i class="icon-remove"></i> Discard</a></li>
</ul>
</div>
</div>
</div>
<div class="col-md-8" id="group-detail-pane">
<div class="panel panel-default" id="group-detail-panel">
<div class="tab-content panel-body">
<h3>{% trans "User list"|capfirst %}</h3>
<table class="table table-striped table-with-form-fields">
<tbody>
<thead><tr><th></th><th>{% trans "Who" %}</th><th></th><th></th></tr></thead>
{% for i in users %}
<tr><td><i class="icon-user"></i></td><td>{{i.username}}</td>
<td><a data-group-pk="{{ i.pk }}" href="#" class="real-link groupuser-delete btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
{% endfor %}
<tr><td><i class="icon-plus"></i></td>
<td><input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
</tr>
</tbody>
</table>
<h3>{% trans "Permissions"|capfirst %}</h3>
<form action="{{acl.url}}" method="post">{% csrf_token %}
<table class="table table-striped table-with-form-fields">
<thead><tr><th></th><th>{% trans "Who" %}</th><th>{% trans "What" %}</th><th></th></tr></thead>
<tbody>
{% for i in acl.users %}
<tr><td><i class="icon-user"></i></td><td>{{i.user}}</td>
<td><select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select></td>
<td class="user-remove"><a href="#" class="btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
{% endfor %}
<tr><td><i class="icon-plus"></i></td>
<td><input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
<td><select class="form-control" name="perm-new">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select></td><td></td>
</tr>
</tbody>
</table>
<textarea class="form-control"></textarea>
<div class="form-actions panel-body">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="{{ STATIC_URL}}dashboard/group-details.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 %}
<a data-group-pk="{{ record.pk }}" class="btn btn-danger btn-xs real-link group-delete" href="{% url "dashboard.views.delete-group" pk=record.pk %}?next={{ request.path }}"><i class="icon-trash"></i></a>
<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="group-{{ 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>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<ul class="list-inline pull-right"> <div class="pull-right toolbar">
<li><a href="#vm-graph-view" class="btn btn-default btn-xs"><i class="icon-dashboard"></i></a></li> <span class="btn btn-default btn-xs infobtn" title="Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. "><i class="icon-info-sign"></i></span>
<li><a href="#vm-list-view" class="btn btn-default btn-xs"><i class="icon-list"></i></a></li> </div>
</ul> <h3 class="no-margin"><i class="icon-group"></i> Groups
<h3 class="no-margin"><i class="icon-group"></i> Groups </h3>
</h3>
</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">
......
{% load i18n %} {% load i18n %}
<form id="vm-details-resources-form" method="POST" action="">
{% csrf_token %} {% csrf_token %}
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>Name:</dt><dd>{{ node.name }}</dd> <dt>{% trans "Node name" %}:</dt><dd>{{ node.name }}</dd>
<dt>Number of cores:</dt><dd>{{ node.num_cores }}</dd> <dt>{% trans "CPU cores" %}:</dt><dd>{{ node.num_cores }}</dd>
<dt>Memory:</dt> <dd>{% widthratio node.ram_size 1048576 1 %} MB</dd> <dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.ram_size 1048576 1 %} MB</dd>
<dt>Architecture:</td><dd>{{ record.arch }}</dd> <dt>{% trans "Architecture" %}:</td><dd>{{ node.arch }}</dd>
<dt>IPv4 address:</dt><dd>{{ node.host.ipv4 }}</dd> <dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd>
<dt>IPv6 address:</dt><dd> {{ node.host.ipv6 }}</dd> <dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled }}</dd>
<dt>Enabled:</dt><dd>{{ node.enabled }}</dd> <dt>{% trans "Host online" %}:</dt><dd> {{ node.online }}</dd>
<dt>Host online:</dt><dd> {{ node.online }}</dd> <dt>{% trans "Priority" %}:</dt><dd>{{ node.priority }}</dd>
<dt>Priority:</dt><dd>{{ node.priority }}</dd> <dt>{% trans "Host owner" %}:</dt><dd>{{ node.host.owner }}</dd>
<dt>Host owner:</dt><dd>{{ node.host.owner }}</dd> <dt>{% trans "Vlan" %}:</dt><dd>{{ node.host.vlan }}</dd>
<dt>Vlan:</dt><dd>{{ node.host.vlan }}</dd> <dt>{% trans "Host name" %}:</dt><dd>{{ node.host.hostname }}</dd>
<dt>Hostname:</dt><dd>{{ node.host.hostname }}</dd>
</dl> </dl>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Enabled" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.enabled }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Priority" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.priority }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "CPU cores" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.num_cores }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "RAM size" %}</label>
</div>
<div class="col-sm-9">
<label > {% widthratio node.ram_size 1024000 1 %} MB </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Traits" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.traits.all }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Tags" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.tags.all }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host owner" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.owner }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host IP" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.ipv4 }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Shared IP" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.shared_ip }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host vlan" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.vlan }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host name" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.groups.all }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host name" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.hostname }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host name" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host.hostname }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Host online" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.online }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="ram-slider"><i class="icon-ticket"></i> {% trans "RAM amount" %}</label>
</div>
<div class="col-sm-9">
<input name="ram-size" type="text" id="vm-ram-size-slider" class="vm-slider" value="{{ instance.ram_size }}" data-slider-min="128" data-slider-max="4096" data-slider-step="128" data-slider-value="{{ instance.ram_size }}" data-slider-orientation="horizontal" data-slider-handle="square" data-slider-tooltip="hide"/> MiB
</div>
</p>
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "CPU count" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host }} </label>
</div>
</p>
<p class="row">
<div class="col-sm-12">
<button type="submit" class="btn btn-success btn-sm" id="vm-details-resources-save"><i class="icon-save"></i> {% trans "Save resources" %}</button>
</div>
</p>
</form>
{% block extra_js %} {% block extra_js %}
<style> <style>
label {padding-top: 6px;} label {padding-top: 6px;}
......
...@@ -52,6 +52,11 @@ ...@@ -52,6 +52,11 @@
</div> </div>
</div> </div>
</div> </div>
<style>
.popover {
max-width: 600px;
}
</style>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
......
...@@ -7,7 +7,8 @@ from .views import ( ...@@ -7,7 +7,8 @@ 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, GroupDelete,
GroupAclUpdateView, GroupUserDelete,
) )
urlpatterns = patterns( urlpatterns = patterns(
...@@ -62,11 +63,18 @@ urlpatterns = patterns( ...@@ -62,11 +63,18 @@ urlpatterns = patterns(
url(r'^favourite/$', FavouriteView.as_view(), url(r'^favourite/$', FavouriteView.as_view(),
name='dashboard.views.favourite'), name='dashboard.views.favourite'),
url(r'^group/delete/(?P<pk>\d+)/$', GroupDelete.as_view(),
name="dashboard.views.delete-group"),
url(r'^group/list/$', GroupList.as_view(), url(r'^group/list/$', GroupList.as_view(),
name='dashboard.views.group-list'), name='dashboard.views.group-list'),
url((r'^vm/(?P<pk>\d+)/graph/(?P<metric>cpu|memory|network)/' url((r'^vm/(?P<pk>\d+)/graph/(?P<metric>cpu|memory|network)/'
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'),
url(r'^group/(?P<pk>\d+)/acl/$', GroupAclUpdateView.as_view(),
name='dashboard.views.group-acl'),
url(r'^groupuser/delete/(?P<pk>\d+)/$', GroupUserDelete.as_view(),
name="dashboard.views.delete-groupuser"),
) )
...@@ -31,7 +31,7 @@ from .forms import ( ...@@ -31,7 +31,7 @@ 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,)
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)
...@@ -110,7 +110,7 @@ class IndexView(LoginRequiredMixin, TemplateView): ...@@ -110,7 +110,7 @@ class IndexView(LoginRequiredMixin, TemplateView):
return context return context
def get_acl_data(obj): def get_vm_acl_data(obj):
levels = obj.ACL_LEVELS levels = obj.ACL_LEVELS
users = obj.get_users_with_level() users = obj.get_users_with_level()
users = [{'user': u, 'level': l} for u, l in users] users = [{'user': u, 'level': l} for u, l in users]
...@@ -120,13 +120,26 @@ def get_acl_data(obj): ...@@ -120,13 +120,26 @@ def get_acl_data(obj):
'url': reverse('dashboard.views.vm-acl', args=[obj.pk])} 'url': reverse('dashboard.views.vm-acl', args=[obj.pk])}
def get_group_acl_data(obj):
aclobj = obj.profile
levels = aclobj.ACL_LEVELS
users = aclobj.get_users_with_level()
users = [{'user': u, 'level': l} for u, l in users]
groups = aclobj.get_groups_with_level()
groups = [{'group': g, 'level': l} for g, l in groups]
return {'users': users, 'groups': groups, 'levels': levels,
'url': reverse('dashboard.views.group-acl', args=[obj.pk])}
class CheckedDetailView(LoginRequiredMixin, DetailView): class CheckedDetailView(LoginRequiredMixin, DetailView):
read_level = 'user' read_level = 'user'
def get_has_level(self):
return self.object.has_level
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CheckedDetailView, self).get_context_data(**kwargs) context = super(CheckedDetailView, self).get_context_data(**kwargs)
instance = context['instance'] if not self.get_has_level()(self.request.user, self.read_level):
if not instance.has_level(self.request.user, self.read_level):
raise PermissionDenied() raise PermissionDenied()
return context return context
...@@ -160,7 +173,7 @@ class VmDetailView(CheckedDetailView): ...@@ -160,7 +173,7 @@ class VmDetailView(CheckedDetailView):
pk__in=Interface.objects.filter( pk__in=Interface.objects.filter(
instance=self.get_object()).values_list("vlan", flat=True) instance=self.get_object()).values_list("vlan", flat=True)
).all() ).all()
context['acl'] = get_acl_data(instance) context['acl'] = get_vm_acl_data(instance)
context['forms'] = { context['forms'] = {
'disk_add_form': DiskAddForm(prefix="disk"), 'disk_add_form': DiskAddForm(prefix="disk"),
} }
...@@ -488,6 +501,48 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): ...@@ -488,6 +501,48 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
kwargs={'pk': self.object.pk})) kwargs={'pk': self.object.pk}))
class GroupDetailView(CheckedDetailView):
template_name = "dashboard/group-detail.html"
model = Group
def get_has_level(self):
return self.object.profile.has_level
def get_context_data(self, **kwargs):
context = super(GroupDetailView, self).get_context_data(**kwargs)
context['group'] = self.object
context['users'] = self.object.user_set.all()
context['acl'] = get_group_acl_data(self.object)
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 = _("Group successfully renamed!")
if request.is_ajax():
response = {
'message': success_message,
'new_name': new_name,
'group_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):
...@@ -567,6 +622,49 @@ class TemplateAclUpdateView(AclUpdateView): ...@@ -567,6 +622,49 @@ class TemplateAclUpdateView(AclUpdateView):
kwargs=self.kwargs)) kwargs=self.kwargs))
class GroupAclUpdateView(AclUpdateView):
model = Group
def post(self, request, *args, **kwargs):
instance = self.get_object().profile
if not (instance.has_level(request.user, "owner") or
getattr(instance, 'owner', None) == request.user):
logger.warning('Tried to set permissions of %s by non-owner %s.',
unicode(instance), unicode(request.user))
raise PermissionDenied()
name = request.POST['perm-new-name']
if (User.objects.filter(username=name).count() +
Group.objects.filter(name=name).count() < 1
and len(name) > 0):
warning(request, _('User or group "%s" not found.') % name)
else:
self.set_levels(request, instance)
self.add_levels(request, instance)
# return redirect(self.profile)
return redirect(reverse("dashboard.views.group-detail",
kwargs=self.kwargs))
def repost(self, request, *args, **kwargs):
group = self.get_object()
if not (group.profile.has_level(request.user, "owner") or
getattr(group.profile, 'owner', None) == request.user):
logger.warning('Tried to set permissions of %s by non-owner %s.',
unicode(group), unicode(request.user))
raise PermissionDenied()
name = request.POST['perm-new-name']
if (User.objects.filter(username=name).count() +
Group.objects.filter(name=name).count() < 1
and len(name) > 0):
warning(request, _('User or group "%s" not found.') % name)
else:
self.set_levels(request, group.profile)
self.add_levels(request, group.profile)
return redirect(reverse("dashboard.views.group-detail",
kwargs=self.kwargs))
class TemplateCreate(SuccessMessageMixin, CreateView): class TemplateCreate(SuccessMessageMixin, CreateView):
model = InstanceTemplate model = InstanceTemplate
form_class = TemplateForm form_class = TemplateForm
...@@ -639,7 +737,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -639,7 +737,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(TemplateDetail, self).get_context_data(**kwargs) context = super(TemplateDetail, self).get_context_data(**kwargs)
context['acl'] = get_acl_data(self.get_object()) context['acl'] = get_vm_acl_data(self.get_object())
return context return context
def get_success_url(self): def get_success_url(self):
...@@ -760,6 +858,98 @@ class GroupList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView): ...@@ -760,6 +858,98 @@ class GroupList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
table_pagination = False table_pagination = False
class GroupUserDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
"""This stuff deletes the group.
"""
model = User
template_name = "dashboard/confirm/base-delete.html"
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def get_context_data(self, **kwargs):
# this is redundant now, but if we wanna add more to print
# we'll need this
context = super(GroupUserDelete, self).get_context_data(**kwargs)
return context
# github.com/django/django/blob/master/django/views/generic/edit.py#L245
def delete(self, request, *args, **kwargs):
object = self.get_object()
object.delete()
success_url = self.get_success_url()
success_message = _("Group successfully deleted!")
if request.is_ajax():
if request.POST.get('redirect').lower() == "true":
messages.success(request, success_message)
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy('dashboard.index')
class GroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
"""This stuff deletes the group.
"""
model = Group
template_name = "dashboard/confirm/base-delete.html"
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def get_context_data(self, **kwargs):
# this is redundant now, but if we wanna add more to print
# we'll need this
context = super(GroupDelete, self).get_context_data(**kwargs)
return context
# github.com/django/django/blob/master/django/views/generic/edit.py#L245
def delete(self, request, *args, **kwargs):
object = self.get_object()
object.delete()
success_url = self.get_success_url()
success_message = _("Group successfully deleted!")
if request.is_ajax():
if request.POST.get('redirect').lower() == "true":
messages.success(request, success_message)
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy('dashboard.index')
class VmCreate(LoginRequiredMixin, TemplateView): class VmCreate(LoginRequiredMixin, TemplateView):
form_class = VmCreateForm form_class = VmCreateForm
......
...@@ -50,9 +50,13 @@ def has_enough_ram(ram_size, node): ...@@ -50,9 +50,13 @@ def has_enough_ram(ram_size, node):
"""True, if the node has enough memory to accomodate a guest requiring """True, if the node has enough memory to accomodate a guest requiring
ram_size mebibytes of memory; otherwise, false. ram_size mebibytes of memory; otherwise, false.
""" """
<<<<<<< HEAD
total = node.ram_size total = node.ram_size
used = (node.ram_usage() / 100) * total used = (node.ram_usage() / 100) * total
unused = total - used unused = total - used
=======
unused = node.ram_size * (1 - node.ram_usage)
>>>>>>> e17952087c648c91908f22e85c4db7194c5f8f60
overcommit = node.ram_size_with_overcommit overcommit = node.ram_size_with_overcommit
reserved = node.instance_set.aggregate(r=Sum('ram_size'))['r'] or 0 reserved = node.instance_set.aggregate(r=Sum('ram_size'))['r'] or 0
...@@ -66,7 +70,11 @@ def free_cpu_time(node): ...@@ -66,7 +70,11 @@ def free_cpu_time(node):
Higher values indicate more idle time. Higher values indicate more idle time.
""" """
<<<<<<< HEAD
activity = node.cpu_usage() / 100 activity = node.cpu_usage() / 100
=======
activity = node.cpu_usage
>>>>>>> e17952087c648c91908f22e85c4db7194c5f8f60
inactivity = 1 - activity inactivity = 1 - activity
cores = node.num_cores cores = node.num_cores
return cores * inactivity return cores * inactivity
...@@ -163,11 +163,13 @@ class Node(TimeStampedModel): ...@@ -163,11 +163,13 @@ class Node(TimeStampedModel):
collected[metric] = cache collected[metric] = cache
return collected return collected
@property
def cpu_usage(self): def cpu_usage(self):
return self.get_monitor_info()["cpu.usage"] return float(self.get_monitor_info()["cpu.usage"]) / 100
@property
def ram_usage(self): def ram_usage(self):
return self.get_monitor_info()["memory.usage"] return float(self.get_monitor_info()["memory.usage"]) / 100
def update_vm_states(self): def update_vm_states(self):
domains = {} domains = {}
......
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