Commit ae77d3c8 by Gregory Nagy

Merge

parents b1f4a5e6 e1795208
......@@ -19,7 +19,6 @@ from firewall.models import Vlan, Host
from storage.models import Disk, DataStore
from vm.models import InstanceTemplate, Lease, InterfaceTemplate, Node
VLANS = Vlan.objects.all()
DISKS = Disk.objects.exclude(type="qcow2-snap")
......@@ -554,7 +553,6 @@ class TemplateForm(forms.ModelForm):
Field('description'),
Field("parent", type="hidden"),
Field("system"),
Field("state"),
),
Fieldset(
_("Exeternal"),
......@@ -569,6 +567,7 @@ class TemplateForm(forms.ModelForm):
class Meta:
model = InstanceTemplate
exclude = ('state', )
class LeaseForm(forms.ModelForm):
......
......@@ -114,6 +114,10 @@ body {
min-height: 20em;
}
#group-detail-panel .panel-body {
min-height: 20em;
}
:link i:before:hover {
text-decoration: none !important;
}
......@@ -166,19 +170,20 @@ body {
}
#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;
}
#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;
}
#vm-details-rename-name, #node-details-rename-name {
#vm-details-rename-name, #node-details-rename-name, #group-details-rename-name {
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;
}
......
......@@ -112,12 +112,12 @@ $(function () {
$('.vm-delete').click(function() {
var vm_pk = $(this).data('vm-pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteVm,
addModalConfirmation(deleteObject,
{ 'url': '/dashboard/vm/delete/' + vm_pk + '/',
'data': [],
'vm_pk': vm_pk,
'pk': vm_pk,
'type': "vm",
'redirect': dir});
return false;
});
......@@ -125,10 +125,38 @@ $(function () {
$('.node-delete').click(function() {
var node_pk = $(this).data('node-pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteNode,
addModalConfirmation(deleteObject,
{ 'url': '/dashboard/node/delete/' + node_pk + '/',
'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});
return false;
......@@ -222,7 +250,7 @@ function refreshSliders() {
/* deletes the VM with the pk
* if dir is true, then redirect to the dashboard landing page
* else it adds a success message */
function deleteVm(data) {
function deleteObject(data) {
$.ajax({
type: 'POST',
data: {'redirect': data['redirect']},
......@@ -232,7 +260,7 @@ function deleteVm(data) {
if(!data['redirect']) {
selected = [];
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();
});
} 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() {
// change big status span
$('#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() {
......
from django.contrib.auth.models import Group
from django.contrib.auth.models import Group, User
from django_tables2 import Table, A
from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
LinkColumn)
......@@ -103,11 +103,55 @@ class NodeListTable(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:
model = Group
attrs = {'class': ('table table-bordered table-striped table-hover '
'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):
......@@ -147,6 +191,11 @@ class NodeVmListTable(Table):
fields = ('pk', 'name', 'state', 'time_of_suspend', 'time_of_delete', )
class UserListTablex(Table):
class Meta:
model = User
class TemplateListTable(Table):
name = LinkColumn(
'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 @@
<div class="col-md-12">
<div class="panel panel-default">
<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 class="panel-body node-list-group-control">
<div class="panel-body group-list-group-control">
<p>
<strong>Group actions</strong>
<button id="node-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 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>
<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">
......@@ -41,28 +38,28 @@
max-width: 600px;
}
.node-list-selected, .node-list-selected td {
.group-list-selected, .group-list-selected td {
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;
}
.node-list-selected td:first-child {
.group-list-selected td:first-child {
font-weight: bold;
}
.node-list-table-thin {
.group-list-table-thin {
width: 10px;
}
.node-list-table-admin {
.group-list-table-admin {
width: 130px;
}
</style>
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/node-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/group-list.js"></script>
{% 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-heading">
<ul class="list-inline pull-right">
<li><a href="#vm-graph-view" class="btn btn-default btn-xs"><i class="icon-dashboard"></i></a></li>
<li><a href="#vm-list-view" class="btn btn-default btn-xs"><i class="icon-list"></i></a></li>
</ul>
<h3 class="no-margin"><i class="icon-group"></i> Groups
</h3>
</div>
<div class="pull-right toolbar">
<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>
</div>
<h3 class="no-margin"><i class="icon-group"></i> Groups
</h3>
</div>
<div class="list-group" id="vm-list-view">
{% for i in groups %}
<a href="#" class="list-group-item">
<i class="icon-file"></i> {{ i.name }}<div class="pull-right"><i class="icon-download-alt "></i></div>
</a>
<a href="{% url "dashboard.views.group-detail" pk=i.pk %}" class="list-group-item real-link">
<i class="icon-group"></i> {{ i.name }}
</a>
{% endfor %}
<div href="#" class="list-group-item list-group-footer text-right">
<div class="row">
......
{% load i18n %}
<form id="vm-details-resources-form" method="POST" action="">
{% csrf_token %}
<dl class="dl-horizontal">
<dt>Name:</dt><dd>{{ node.name }}</dd>
<dt>Number of cores:</dt><dd>{{ node.num_cores }}</dd>
<dt>Memory:</dt> <dd>{% widthratio node.ram_size 1048576 1 %} MB</dd>
<dt>Architecture:</td><dd>{{ record.arch }}</dd>
<dt>IPv4 address:</dt><dd>{{ node.host.ipv4 }}</dd>
<dt>IPv6 address:</dt><dd> {{ node.host.ipv6 }}</dd>
<dt>Enabled:</dt><dd>{{ node.enabled }}</dd>
<dt>Host online:</dt><dd> {{ node.online }}</dd>
<dt>Priority:</dt><dd>{{ node.priority }}</dd>
<dt>Host owner:</dt><dd>{{ node.host.owner }}</dd>
<dt>Vlan:</dt><dd>{{ node.host.vlan }}</dd>
<dt>Hostname:</dt><dd>{{ node.host.hostname }}</dd>
<dt>{% trans "Node name" %}:</dt><dd>{{ node.name }}</dd>
<dt>{% trans "CPU cores" %}:</dt><dd>{{ node.num_cores }}</dd>
<dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.ram_size 1048576 1 %} MB</dd>
<dt>{% trans "Architecture" %}:</td><dd>{{ node.arch }}</dd>
<dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd>
<dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled }}</dd>
<dt>{% trans "Host online" %}:</dt><dd> {{ node.online }}</dd>
<dt>{% trans "Priority" %}:</dt><dd>{{ node.priority }}</dd>
<dt>{% trans "Host owner" %}:</dt><dd>{{ node.host.owner }}</dd>
<dt>{% trans "Vlan" %}:</dt><dd>{{ node.host.vlan }}</dd>
<dt>{% trans "Host name" %}:</dt><dd>{{ node.host.hostname }}</dd>
</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 %}
<style>
label {padding-top: 6px;}
......
......@@ -52,6 +52,11 @@
</div>
</div>
</div>
<style>
.popover {
max-width: 600px;
}
</style>
{% endblock %}
{% block extra_js %}
......
......@@ -7,7 +7,8 @@ from .views import (
TransferOwnershipView, TransferOwnershipConfirmView, NodeDelete,
TemplateList, LeaseDetail, NodeCreate, LeaseCreate, TemplateCreate,
FavouriteView, NodeStatus, GroupList, TemplateDelete, LeaseDelete,
VmGraphView, TemplateAclUpdateView
VmGraphView, TemplateAclUpdateView, GroupDetailView, GroupDelete,
GroupAclUpdateView, GroupUserDelete,
)
urlpatterns = patterns(
......@@ -62,11 +63,18 @@ urlpatterns = patterns(
url(r'^favourite/$', FavouriteView.as_view(),
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(),
name='dashboard.views.group-list'),
url((r'^vm/(?P<pk>\d+)/graph/(?P<metric>cpu|memory|network)/'
r'(?P<time>[0-9]{1,2}[hdwy])$'),
VmGraphView.as_view(),
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 (
VmCreateForm, TemplateForm, LeaseForm, NodeForm, HostForm, DiskAddForm,
)
from .tables import (VmListTable, NodeListTable, NodeVmListTable,
TemplateListTable, LeaseListTable, GroupListTable)
TemplateListTable, LeaseListTable, GroupListTable,)
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
InstanceActivity, Node, instance_activity, Lease,
Interface)
......@@ -110,7 +110,7 @@ class IndexView(LoginRequiredMixin, TemplateView):
return context
def get_acl_data(obj):
def get_vm_acl_data(obj):
levels = obj.ACL_LEVELS
users = obj.get_users_with_level()
users = [{'user': u, 'level': l} for u, l in users]
......@@ -120,13 +120,26 @@ def get_acl_data(obj):
'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):
read_level = 'user'
def get_has_level(self):
return self.object.has_level
def get_context_data(self, **kwargs):
context = super(CheckedDetailView, self).get_context_data(**kwargs)
instance = context['instance']
if not instance.has_level(self.request.user, self.read_level):
if not self.get_has_level()(self.request.user, self.read_level):
raise PermissionDenied()
return context
......@@ -160,7 +173,7 @@ class VmDetailView(CheckedDetailView):
pk__in=Interface.objects.filter(
instance=self.get_object()).values_list("vlan", flat=True)
).all()
context['acl'] = get_acl_data(instance)
context['acl'] = get_vm_acl_data(instance)
context['forms'] = {
'disk_add_form': DiskAddForm(prefix="disk"),
}
......@@ -488,6 +501,48 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
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):
def post(self, request, *args, **kwargs):
......@@ -567,6 +622,49 @@ class TemplateAclUpdateView(AclUpdateView):
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):
model = InstanceTemplate
form_class = TemplateForm
......@@ -639,7 +737,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
def get_context_data(self, **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
def get_success_url(self):
......@@ -760,6 +858,98 @@ class GroupList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
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):
form_class = VmCreateForm
......
......@@ -50,9 +50,13 @@ def has_enough_ram(ram_size, node):
"""True, if the node has enough memory to accomodate a guest requiring
ram_size mebibytes of memory; otherwise, false.
"""
<<<<<<< HEAD
total = node.ram_size
used = (node.ram_usage() / 100) * total
unused = total - used
=======
unused = node.ram_size * (1 - node.ram_usage)
>>>>>>> e17952087c648c91908f22e85c4db7194c5f8f60
overcommit = node.ram_size_with_overcommit
reserved = node.instance_set.aggregate(r=Sum('ram_size'))['r'] or 0
......@@ -66,7 +70,11 @@ def free_cpu_time(node):
Higher values indicate more idle time.
"""
<<<<<<< HEAD
activity = node.cpu_usage() / 100
=======
activity = node.cpu_usage
>>>>>>> e17952087c648c91908f22e85c4db7194c5f8f60
inactivity = 1 - activity
cores = node.num_cores
return cores * inactivity
......@@ -163,11 +163,13 @@ class Node(TimeStampedModel):
collected[metric] = cache
return collected
@property
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):
return self.get_monitor_info()["memory.usage"]
return float(self.get_monitor_info()["memory.usage"]) / 100
def update_vm_states(self):
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