Commit d5fe25eb by Oláh István Gergely

dashboard: node functions

parent a383b8e4
...@@ -91,6 +91,10 @@ body { ...@@ -91,6 +91,10 @@ body {
min-height: 20em; min-height: 20em;
} }
#node-detail-panel .panel-body {
min-height: 20em;
}
:link i:before:hover { :link i:before:hover {
text-decoration: none !important; text-decoration: none !important;
} }
...@@ -142,19 +146,20 @@ body { ...@@ -142,19 +146,20 @@ body {
height: 300px; height: 300px;
} }
#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 * {
display: inline; display: inline;
} }
#vm-details-rename, #vm-list-rename { #vm-details-rename, #vm-list-rename, #node-details-rename, #node-list-rename {
display: none; display: none;
} }
#vm-details-rename-name { #vm-details-rename-name, #node-details-rename-name {
max-width: 160px; max-width: 160px;
} }
#vm-list-rename-name { #vm-list-rename-name, #node-list-rename-name {
max-width: 100px; max-width: 100px;
} }
......
...@@ -15,6 +15,23 @@ $(function () { ...@@ -15,6 +15,23 @@ $(function () {
}); });
return false; return false;
}); });
$('.node-create').click(function(e) {
$.ajax({
type: 'GET',
url: '/dashboard/node/create/',
success: function(data) {
$('body').append(data);
nodeCreateLoaded();
addSliderMiscs();
$('#node-create-modal').modal('show');
$('#node-create-modal').on('hidden.bs.modal', function() {
$('#node-create-modal').remove();
});
}
});
return false;
});
$('[href=#index-graph-view]').click(function (e) { $('[href=#index-graph-view]').click(function (e) {
var box = $(this).data('index-box'); var box = $(this).data('index-box');
$("#" + box + "-list-view").hide(); $("#" + box + "-list-view").hide();
...@@ -65,6 +82,20 @@ $(function () { ...@@ -65,6 +82,20 @@ $(function () {
return false; return false;
}); });
/* for Node removes buttons */
$('.node-delete').click(function() {
var node_pk = $(this).data('node-pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteNode,
{ 'url': '/dashboard/node/delete/' + node_pk + '/',
'data': [],
'node_pk': node_pk,
'redirect': dir});
return false;
});
}); });
function addSliderMiscs() { function addSliderMiscs() {
......
$(function() {
if($('.timeline .activity:first i:first').hasClass('icon-spin'))
checkNewActivity();
/* save resources */
$('#vm-details-resources-save').click(function() {
$('i.icon-save', this).removeClass("icon-save").addClass("icon-refresh icon-spin");
$.ajax({
type: 'POST',
url: location.href,
data: $('#vm-details-resources-form').serialize(),
success: function(data, textStatus, xhr) {
addMessage(data['message'], 'success');
$("#vm-details-resources-save i").removeClass('icon-refresh icon-spin').addClass("icon-save");
},
error: function(xhr, textStatus, error) {
$("#vm-details-resources-save i").removeClass('icon-refresh icon-spin').addClass("icon-save");
addMessage("Eww, something is wrong", 'danger');
if (xhr.status == 500) {
// alert("uhuhuhuhuhuh");
} else {
// alert("unknown error");
}
}
});
return false;
});
/* rename */
$("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide();
$("#vm-details-rename").css('display', 'inline');
});
/* rename ajax */
$('#vm-details-rename-submit').click(function() {
var name = $('#vm-details-rename-name').val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$("#vm-details-h1-name").html(data['new_name']).show();
$('#vm-details-rename').hide();
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("uhoh", "danger");
}
});
return false;
});
/* remove tag */
$('.vm-details-remove-tag').click(function() {
var to_remove = $.trim($(this).parent('div').text());
var clicked = $(this);
$.ajax({
type: 'POST',
url: location.href,
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'to_remove': to_remove},
success: function(re) {
if(re['message'].toLowerCase() == "success") {
$(clicked).closest(".label").fadeOut(500, function() {
$(this).remove();
});
}
},
error: function() {
addMessage(re['message'], 'danger');
}
});
return false;
});
});
function checkNewActivity() {
var latest = $('.activity:first').data('activity-id');
var latest_sub = $('div[data-activity-id="' + latest + '"] .sub-timeline .sub-activity:first').data('activity-id');
var instance = location.href.split('/'); instance = instance[instance.length - 2];
$.ajax({
type: 'POST',
url: '/dashboard/vm/' + instance + '/activity/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
data: {'latest': latest, 'latest_sub': latest_sub},
success: function(data) {
if(data['new_sub_activities'].length > 0) {
d = data['new_sub_activities'];
html = ""
for(var i=0; i<d.length; i++) {
html += '<div data-activity-id="' + d[i].id + '" class="sub-activity">' + d[i].name + ' - ';
if(d[i].finished != null) {
html += d[i].finished
} else {
html += '<i class="icon-refresh icon-spin" class="sub-activity-loading-icon"></i>';
}
html += '</div>';
}
$('div[data-activity-id="' + latest_sub + '"] .sub-activity .sub-activity-loading-icon').remove();
$('div[data-activity-id="' + latest + '"] .sub-timeline').prepend(html);
}
if(data['is_parent_finished']) {
var c = "icon-plus"
$('div[data-activity-id="' + latest + '"] .icon-refresh.icon-spin:first').removeClass('icon-refresh').removeClass('icon-spin').addClass(c);
}
if(data['latest_sub_finished'] != null) {
s = $('div[data-activity-id="' + latest_sub + '"]')
$('.icon-refresh.icon-spin', s).remove();
$(s).append(data['latest_sub_finished']);
}
if(data['is_parent_finished'])
return;
else
setTimeout(checkNewActivity, 1000);
},
error: function() {
}
});
}
...@@ -13,6 +13,7 @@ $(function() { ...@@ -13,6 +13,7 @@ $(function() {
}); });
$('.node-list-table tbody').find('tr').mousedown(function() { $('.node-list-table tbody').find('tr').mousedown(function() {
var retval = true;
if (ctrlDown) { if (ctrlDown) {
setRowColor($(this)); setRowColor($(this));
if(!$(this).hasClass('node-list-selected')) { if(!$(this).hasClass('node-list-selected')) {
...@@ -20,6 +21,7 @@ $(function() { ...@@ -20,6 +21,7 @@ $(function() {
} else { } else {
selected.push($(this).index()); selected.push($(this).index());
} }
retval = false;
} else if(shiftDown) { } else if(shiftDown) {
if(selected.length > 0) { if(selected.length > 0) {
start = selected[selected.length - 1] + 1; start = selected[selected.length - 1] + 1;
...@@ -36,6 +38,7 @@ $(function() { ...@@ -36,6 +38,7 @@ $(function() {
} }
} }
} }
retval = false;
} else { } else {
$('.node-list-selected').removeClass('node-list-selected'); $('.node-list-selected').removeClass('node-list-selected');
$(this).addClass('node-list-selected'); $(this).addClass('node-list-selected');
...@@ -53,9 +56,19 @@ $(function() { ...@@ -53,9 +56,19 @@ $(function() {
} else { } else {
$('.node-list-group-control a').attr('disabled', true); $('.node-list-group-control a').attr('disabled', true);
} }
return false; return retval;
}); });
$(':not(#anything)').on('click', function (e) {
$('.node-list-details').each(function () {
//the 'is' for buttons that trigger popups
// //the 'has' for icons and other elements within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
$(this).popover('hide');
return;
}
});
});
$('#node-list-group-migrate').click(function() { $('#node-list-group-migrate').click(function() {
console.log(collectIds(selected)); console.log(collectIds(selected));
...@@ -64,12 +77,6 @@ $(function() { ...@@ -64,12 +77,6 @@ $(function() {
$('.node-list-details').popover({ $('.node-list-details').popover({
'placement': 'auto', 'placement': 'auto',
'html': true, 'html': true,
'trigger': 'hover'
});
$('.node-list-connect').popover({
'placement': 'left',
'html': true,
'trigger': 'click' 'trigger': 'click'
}); });
...@@ -80,11 +87,48 @@ $(function() { ...@@ -80,11 +87,48 @@ $(function() {
$('tbody a').click(function(e) { $('tbody a').click(function(e) {
// browser doesn't jump to top when clicked the buttons // browser doesn't jump to top when clicked the buttons
if(!$(this).hasClass('real-link')) { if(!$(this).hasClass('real-link')) {
return false; return false;
} }
}); });
/* rename */
$("#node-list-rename-button, .node-details-rename-button").click(function() {
$("#node-list-column-name", $(this).closest("tr")).hide();
$("#node-list-rename", $(this).closest("tr")).css('display', 'inline');
});
/* rename ajax */
$('.node-list-rename-submit').click(function() {
var row = $(this).closest("tr")
var name = $('#node-list-rename-name', row).val();
var url = '/dashboard/node/' + 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) {
$("#node-list-column-name", row).html(
$("<a/>", {
'class': "real-link",
href: "/dashboard/node/" + data['node_pk'] + "/",
text: data['new_name']
})
).show();
$('#node-list-rename', row).hide();
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("uhoh", "danger");
}
});
return false;
});
/* group actions */ /* group actions */
/* select all */ /* select all */
...@@ -103,9 +147,15 @@ $(function() { ...@@ -103,9 +147,15 @@ $(function() {
/* mass vm delete */ /* mass vm delete */
$('#node-list-group-delete').click(function() { $('#node-list-group-delete').click(function() {
text = "Are you sure you want to delete the selected VMs?"; addModalConfirmation(massDeleteVm,
random_vm_pk = $('.vm-delete').eq(0).data('vm-pk'); {
addModalConfirmation(text, random_vm_pk, massDeleteVm, false); 'url': '/dashboard/node/mass-delete/',
'data': {
'selected': selected,
'v': collectIds(selected)
}
}
);
return false; return false;
}); });
}); });
...@@ -114,16 +164,15 @@ function collectIds(rows) { ...@@ -114,16 +164,15 @@ function collectIds(rows) {
var ids = []; var ids = [];
for(var i = 0; i < rows.length; i++) { for(var i = 0; i < rows.length; i++) {
var div = $('td:first-child div', $('.node-list-table tbody tr').eq(rows[i])); var div = $('td:first-child div', $('.node-list-table tbody tr').eq(rows[i]));
ids.push(div.prop('id').replace('vm-', '')); ids.push(div.prop('id').replace('node-', ''));
} }
return ids; return ids;
} }
function setRowColor(row) { function setRowColor(row) {
if(!row.hasClass('vm-list-selected')) { if(!row.hasClass('node-list-selected')) {
row.addClass('node-list-selected'); row.addClass('node-list-selected');
} else { } else {
row.removeClass('node-list-selected'); row.removeClass('node-list-selected');
} }
} }
from django_tables2 import Table, A from django_tables2 import Table, A
from django_tables2.columns import LinkColumn, TemplateColumn, Column from django_tables2.columns import LinkColumn, TemplateColumn, Column, BooleanColumn
from vm.models import Instance, Node from vm.models import Instance, Node
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -46,17 +46,99 @@ class NodeListTable(Table): ...@@ -46,17 +46,99 @@ class NodeListTable(Table):
pk = Column( pk = Column(
verbose_name="ID", verbose_name="ID",
attrs={'th': {'class': 'node-list-table-thin'}},
) )
name = LinkColumn( overcommit = Column(
'dashboard.views.node-detail', verbose_name="Overcommit",
args=[A('pk')], attrs={'th': {'class': 'node-list-table-thin'}},
attrs={'a': {'class': 'real-link'}}
) )
host = Column(
verbose_name="Host",
)
enabled = BooleanColumn(
verbose_name="Enabled",
attrs={'th': {'class': 'node-list-table-thin'}},
)
name = TemplateColumn(
template_name="dashboard/node-list/column-name.html"
)
priority = Column(
verbose_name=_("Priority"),
attrs={'th': {'class': 'node-list-table-thin'}},
)
number_of_VMs = TemplateColumn(
template_name='dashboard/node-list/column-vm.html',
attrs={'th': {'class': 'node-list-table-admin'}},
)
admin = TemplateColumn(
template_name='dashboard/node-list/column-admin.html',
attrs={'th': {'class': 'node-list-table-admin'}},
)
details = TemplateColumn(
template_name='dashboard/node-list/column-details.html',
attrs={'th': {'class': 'node-list-table-thin'}},
)
actions = TemplateColumn(
attrs={'th': {'class': 'node-list-table-thin'}},
template_code='{% include "dashboard/node-list/column-actions.html" with btn_size="btn-xs" %}',
# actions = TemplateColumn('{% load my_filters %}{{ record.name|int_to_time }}'
# attrs={'th': {'class': 'node-list-table-thin'},'extra_tag':},
)
# time_of_suspend = TemplateColumn(
# '{{ record.time_of_suspend|timeuntil }}',
# verbose_name=_("Suspend in"))
# time_of_delete = TemplateColumn(
# '{{ record.time_of_delete|timeuntil }}',
# verbose_name=_("Delete in"))
class Meta: class Meta:
model = Node model = Node
attrs = {'class': ('table table-bordered table-striped table-hover ' attrs = {'class': ('table table-bordered table-striped table-hover '
'node-list-table')} 'node-list-table')}
fields = ('pk', 'name', 'host', 'enabled', 'created', 'modified', fields = ('pk', 'name', 'host', 'enabled', 'priority', 'overcommit','number_of_VMs', )
'priority', 'overcommit', )
class NodeVmListTable(Table):
pk = TemplateColumn(
template_name='dashboard/vm-list/column-id.html',
verbose_name="ID",
attrs={'th': {'class': 'vm-list-table-thin'}},
)
name = TemplateColumn(
template_name="dashboard/vm-list/column-name.html"
)
admin = TemplateColumn(
template_name='dashboard/vm-list/column-admin.html',
attrs={'th': {'class': 'vm-list-table-admin'}},
)
details = TemplateColumn(
template_name='dashboard/vm-list/column-details.html',
attrs={'th': {'class': 'vm-list-table-thin'}},
)
actions = TemplateColumn(
template_name='dashboard/vm-list/column-actions.html',
attrs={'th': {'class': 'vm-list-table-thin'}},
)
time_of_suspend = TemplateColumn(
'{{ record.time_of_suspend|timeuntil }}',
verbose_name=_("Suspend in"))
time_of_delete = TemplateColumn(
'{{ record.time_of_delete|timeuntil }}',
verbose_name=_("Delete in"))
class Meta:
model = Instance
attrs = {'class': ('table table-bordered table-striped table-hover '
'vm-list-table')}
fields = ('pk', 'name', 'state', 'time_of_suspend', 'time_of_delete', )
{% load i18n %} {% load i18n %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="pull-right toolbar"> <div class="pull-right toolbar">
<div class="btn-group"> <div class="btn-group">
<a href="#node-graph-view" class="btn btn-default btn-xs"><i class="icon-dashboard"></i></a> <a href="#index-graph-view" data-index-box="node" class="btn btn-default btn-xs"><i class="icon-dashboard"></i></a>
<a href="#node-list-view" class="btn btn-default btn-xs"><i class="icon-list"></i></a> <a href="#index-list-view" data-index-box="node" class="btn btn-default btn-xs disabled"><i class="icon-list"></i></a>
</div> </div>
<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> <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> </div>
<h3 class="no-margin"> <h3 class="no-margin">
<i class="icon-sitemap"></i> {% trans "Compute nodes" %} <i class="icon-sitemap"></i> {% trans "Compute nodes" %}
</h3> </h3>
</div >
<div class="list-group" id="node-list-view">
{% for i in nodes %}
<a href="{% url "dashboard.views.node-detail" pk=i.pk %}" class="list-group-item">
<i class="icon-{% if i.enabled == True %}play-sign{% else %}pause{% endif %}"></i> {{ i.name }} <div class="pull-right"><i class="icon-star text-primary" title="Mark as favorite."></i></div>
</a>
{% endfor %}
<div href="#" class="list-group-item list-group-footer">
<div class="row">
<div class="col-sm-6 col-xs-6 input-group input-group-sm">
<input type="text" class="form-control" placeholder="Search..." />
<div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary" title="search"><i class="icon-search"></i></button>
</div>
</div>
<div class="col-sm-6 text-right">
{% if more_nodes > 0 %}
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.node-list" %}">
<i class="icon-chevron-sign-right"></i> <strong>{{ more_nodes }}</strong> more
</a>
{% endif %}
<a class="btn btn-success btn-xs node-create" href="{% url "dashboard.views.node-create" %}"><i class="icon-plus-sign"></i> new </a>
</div> </div>
<div class="panel-body" id="node-graph-view" style=""> </div>
<p class="pull-right"> <input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-width="60" data-height="60" data-readOnly="true" value="68"></p> </div>
<p><span class="big"><big>13</big> running </span> </div>
+ <big>3</big> off + <big>3</big> missing</p>
<div class="panel-body" id="node-graph-view" style="display: none">
<p class="pull-right"> <input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-width="60" data-height="60" data-readOnly="true" value="{% widthratio node_num.running sum_node_num 100 %}"></p>
<p><span class="big"><big>{{ node_num.running }}</big> running </span>
+ <big>{{ node_num.missing }}</big> missing + <br><big>{{ node_num.disabled }}</big> disabled + <big>{{ node_num.offline }}</big> offline</p>
<ul class="list-inline"> <ul class="list-inline">
{% for i in nodes %} {% for i in nodes %}
<li class="label label-success"><i class="icon-{% if i.enabled == True %}play-sign{% else %}pause{% endif %}"></i>{{ i.name }}</li> <a class="label {% if i.state == 'online' %}label-success{% elif i.state == 'missing' %}label-danger{% else %}label-warning{% endif %}" href="{% url "dashboard.views.node-detail" pk=i.pk %}"><i class="icon-{% if i.enabled == True %}play-sign{% else %}pause{% endif %}"></i> {{ i.name}}</a>
{% endfor %} {% endfor %}
</ul> </ul>
...@@ -31,12 +58,12 @@ ...@@ -31,12 +58,12 @@
</div> </div>
</div> </div>
<div class="col-sm-6 text-right"> <div class="col-sm-6 text-right">
{% if more_nodes > 0 %} {% if more_nodes >= 0 %}
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.node-list" %}"> <a class="btn btn-primary btn-xs" href="{% url "dashboard.views.node-list" %}">
<i class="icon-chevron-sign-right"></i> <strong>{{ more_nodes }}</strong> more <i class="icon-chevron-sign-right"></i> <strong>{{ more_nodes }}</strong> more
</a> </a>
{% endif %} {% endif %}
<a class="btn btn-success btn-xs vm-create" href="{% url "dashboard.views.vm-create" %}"><i class="icon-plus-sign"></i> new </a> <a class="btn btn-success btn-xs node-create" href="{% url "dashboard.views.node-create" %}"><i class="icon-plus-sign"></i> new </a>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -31,5 +31,6 @@ ...@@ -31,5 +31,6 @@
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script src="{{ STATIC_URL }}dashboard/vm-create.js"></script> <script src="{{ STATIC_URL }}dashboard/vm-create.js"></script>
<script src="{{ STATIC_URL }}dashboard/node-create.js"></script>
{% endblock %} {% endblock %}
...@@ -6,17 +6,6 @@ ...@@ -6,17 +6,6 @@
<form method="POST" action="/dashboard/vm/create/"> <form method="POST" action="/dashboard/vm/create/">
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col-sm-10">
<select class="select form-control" id="vm-create-template-select" name="template-pk">
<option value="-1">Choose a VM template</option>
{% for template in templates %}
<option value="{{ template.pk }}">{{ template.name}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<div class="col-sm-5"> <div class="col-sm-5">
<a class="btn btn-info vm-create-advanced-btn">Advanced <i class="vm-create-advanced-icon icon-caret-down"></i></a> <a class="btn btn-info vm-create-advanced-btn">Advanced <i class="vm-create-advanced-icon icon-caret-down"></i></a>
</div> </div>
......
...@@ -2,14 +2,21 @@ ...@@ -2,14 +2,21 @@
<form id="vm-details-resources-form" method="POST" action=""> <form id="vm-details-resources-form" method="POST" action="">
{% csrf_token %} {% csrf_token %}
<p class="row"> <dl class="dl-horizontal">
<div class="col-sm-3"> <dt>Name:</dt><dd>{{ node.name }}</dd>
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Name" %}</label> <dt>Number of cores:</dt><dd>{{ node.num_cores }}</dd>
</div> <dt>Memory:</dt> <dd>{% widthratio node.ram_size 1048576 1 %} MB</dd>
<div class="col-sm-9"> <dt>Architecture:</td><dd>{{ record.arch }}</dd>
<label > {{ node.name }} </label> <dt>IPv4 address:</dt><dd>{{ node.host.ipv4 }}</dd>
</div> <dt>IPv6 address:</dt><dd> {{ node.host.ipv6 }}</dd>
</p> <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>
</dl>
<p class="row"> <p class="row">
<div class="col-sm-3"> <div class="col-sm-3">
...@@ -154,7 +161,7 @@ ...@@ -154,7 +161,7 @@
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "CPU count" %}</label> <label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "CPU count" %}</label>
</div> </div>
<div class="col-sm-9"> <div class="col-sm-9">
<label > {{ node.host. }} </label> <label > {{ node.host }} </label>
</div> </div>
</p> </p>
......
{% load render_table from django_tables2 %}
{% block content %}
<div class="panel-body">
{% render_table table %}
</div>
{% endblock %}
<script>
"use strict";
$('a[data-toggle$="pill"][href!="#virtualmachines"]').click(function() {
$("#node-info-pane").fadeIn();
$("#node-detail-pane").removeClass("col-md-12");
});
$('a[href$="virtualmachines"]').click(function() {
$("#node-info-pane").hide();
$("#node-detail-pane").addClass("col-md-12");
});
</script>
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/vm-list.js"></script>
{% endblock %}
...@@ -7,22 +7,22 @@ ...@@ -7,22 +7,22 @@
<h1>{{ node.name }} <small>{{ node.get_connect_host }}</small></h1> <h1>{{ node.name }} <small>{{ node.get_connect_host }}</small></h1>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-4" id="vm-info-pane"> <div class="col-md-4" id="node-info-pane">
<div class="big"> <div class="big">
<span class="label label-success">{{ node.enabled }}</span> <span class="label {% if node.state == 'online' %}label-success
{% elif node.state == 'missing' %}label-danger
{% elif node.state == 'disabled' %}label-warning
{% elif node.state == 'offline' %}label-warning
{% endif %}">{{ node.state }}</span>
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown">Action <i class="icon-caret-down"></i></button> {% with node as record %}
<ul class="dropdown-menu" role="menu"> {% include "dashboard/node-list/column-actions.html" with btn_size="" %}
<li><a href="#"><i class="icon-refresh"></i> Flash</a></li> {% endwith %}
<li><a href="#"><i class="icon-refresh"></i> Reboot</a></li>
<li><a href="#"><i class="icon-off"></i> Shutdown</a></li>
<li><a data-vm-pk="{{ node.pk }}" class="vm-delete" href="{% url "dashboard.views.delete-vm" pk=node.pk %}"><i class="icon-remove"></i> Discard</a></li>
</ul>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-8" id="vm-detail-pane"> <div class="col-md-8" id="node-detail-pane">
<div class="panel panel-default" id="vm-detail-panel"> <div class="panel panel-default" id="node-detail-panel">
<ul class="nav nav-pills panel-heading"> <ul class="nav nav-pills panel-heading">
<li class="active"> <li class="active">
<a href="#home" data-toggle="pill" class="text-center"> <a href="#home" data-toggle="pill" class="text-center">
...@@ -33,6 +33,10 @@ ...@@ -33,6 +33,10 @@
<i class="icon-tasks icon-2x"></i><br> <i class="icon-tasks icon-2x"></i><br>
{% trans "Resources" %}</a></li> {% trans "Resources" %}</a></li>
<li> <li>
<a href="#virtualmachines" data-toggle="pill" class="text-center">
<i class="icon-desktop icon-2x"></i><br>
{% trans "Virtual Machines" %}</a></li>
<li>
<a href="#activity" data-toggle="pill" class="text-center"> <a href="#activity" data-toggle="pill" class="text-center">
<i class="icon-time icon-2x"></i><br> <i class="icon-time icon-2x"></i><br>
{% trans "Activity" %}</a></li> {% trans "Activity" %}</a></li>
...@@ -42,6 +46,7 @@ ...@@ -42,6 +46,7 @@
<div class="tab-pane active" id="home">{% include "dashboard/node-detail-home.html" %}</div> <div class="tab-pane active" id="home">{% include "dashboard/node-detail-home.html" %}</div>
<div class="tab-pane" id="resources">{% include "dashboard/node-detail-resources.html" %}</div> <div class="tab-pane" id="resources">{% include "dashboard/node-detail-resources.html" %}</div>
<div class="tab-pane" id="activity">{% include "dashboard/node-detail-activity.html" %}</div> <div class="tab-pane" id="activity">{% include "dashboard/node-detail-activity.html" %}</div>
<div class="tab-pane" id="virtualmachines">{% include "dashboard/node-detail-vm.html" %}</div>
</div> </div>
</div> </div>
</div> </div>
......
<div class="btn-group"> <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> <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"> <ul class="dropdown-menu" role="menu">
<li><a href="#"><i class="icon-refresh"></i> Reboot</a></li> <li><a href="#"><i class="icon-cloud-upload"></i> Flush</a></li>
<li><a href="#"><i class="icon-off"></i> Shutdown</a></li> {% if record.enabled %}
<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-remove"></i> Discard</a></li> <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-remove"></i> Disable</a></li>
{% else %}
<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-check"></i> Enable</a></li>
{% endif%}
<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> </ul>
</div> </div>
<a class="btn btn-default btn-xs" title data-original-title="Flush">
<a class="btn btn-default btn-xs" title data-original-title="Migrate"> <i class="icon-cloud-upload"></i>
<i class="icon-truck"></i>
</a> </a>
<a class="btn btn-default btn-xs" title data-original-title="Rename"> <a id="node-list-rename-button" class="btn btn-default btn-xs" title data-original-title="Rename">
<i class="icon-pencil"></i> <i class="icon-pencil"></i>
</a> </a>
<a href="#" class="btn btn-default btn-xs node-list-connect" data-toggle="popover"
data-content='
Belépés: <input style="width: 300px;" type="text" class="form-control" value="ssh xyz@vm.ik.bme.hu -p22312"/>
Jelszó: <input style="width: 300px;" type="text" class="form-control" value="asdfkicsiasdfkocsi"/>
'>Connect</a>
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<h4>Quick details</h4> <h4>Quick details</h4>
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>Number of cores:</dt><dd>{{ record.num_cores }}</dd> <dt>Number of cores:</dt><dd>{{ record.num_cores }}</dd>
<dt>Memory:</dt> <dd>{{ record.ram_size }} Mebibytes</dd> <dt>Memory:</dt> <dd>{% widthratio record.ram_size 1048576 1 %} MB</dd>
<dt>Architecture:</td><dd>{{ record.arch }}</dd> <dt>Architecture:</td><dd>{{ record.arch }}</dd>
</dl> </dl>
<dl> <dl>
......
{% load i18n %}
<div id="node-list-rename">
<form action="{% url "dashboard.views.node-detail" pk=record.pk %}" method="POST" id="node-list-rename-form">
{% csrf_token %}
<input id="node-list-rename-name" class="form-control input-sm" name="new_name" type="text" value="{{ record.name }}"/>
<button type="submit" class="node-list-rename-submit btn btn-sm">{% trans "Rename" %}</button>
</form>
</div>
<div id="node-list-column-name">
<a class="real-link" href="{% url "dashboard.views.node-detail" pk=record.pk %}">{{ record.name }}</a>
</div>
{% load i18n %}
<div id="node-list-column-vm">
<a class="real-link" href="{% url "dashboard.views.node-detail" pk=record.pk %}#virtualmachines">{{ record.instance_set.count }}</a>
</div>
...@@ -4,8 +4,8 @@ from vm.models import Instance ...@@ -4,8 +4,8 @@ from vm.models import Instance
from .views import ( from .views import (
IndexView, VmDetailView, VmList, VmCreate, TemplateDetail, AclUpdateView, IndexView, VmDetailView, VmList, VmCreate, TemplateDetail, AclUpdateView,
VmDelete, VmMassDelete, vm_activity, NodeList, NodeDetailView, PortDelete, VmDelete, VmMassDelete, vm_activity, NodeList, NodeDetailView, PortDelete,
TransferOwnershipView, TransferOwnershipConfirmView TransferOwnershipView, TransferOwnershipConfirmView, NodeDelete,
) NodeCreate)
urlpatterns = patterns( urlpatterns = patterns(
'', '',
...@@ -33,5 +33,8 @@ urlpatterns = patterns( ...@@ -33,5 +33,8 @@ urlpatterns = patterns(
name='dashboard.views.node-detail'), name='dashboard.views.node-detail'),
url(r'^tx/$', TransferOwnershipConfirmView.as_view(), url(r'^tx/$', TransferOwnershipConfirmView.as_view(),
name='dashboard.views.vm-transfer-ownership-confirm'), name='dashboard.views.vm-transfer-ownership-confirm'),
url(r'^node/delete/(?P<pk>\d+)/$', NodeDelete.as_view(),
name="dashboard.views.delete-node"),
url(r'^node/create/$', NodeCreate.as_view(),
name='dashboard.views.node-create'),
) )
...@@ -22,7 +22,7 @@ from django_tables2 import SingleTableView ...@@ -22,7 +22,7 @@ from django_tables2 import SingleTableView
from braces.views import LoginRequiredMixin from braces.views import LoginRequiredMixin
from .forms import VmCreateForm from .forms import VmCreateForm
from .tables import (VmListTable, NodeListTable) from .tables import (VmListTable, NodeListTable, NodeVmListTable)
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate, from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
InstanceActivity, Node, instance_activity) InstanceActivity, Node, instance_activity)
from firewall.models import Vlan, Host, Rule from firewall.models import Vlan, Host, Rule
...@@ -49,8 +49,15 @@ class IndexView(TemplateView): ...@@ -49,8 +49,15 @@ class IndexView(TemplateView):
nodes = Node.objects.all() nodes = Node.objects.all()
context.update({ context.update({
'nodes': nodes[:1], 'nodes': nodes[:10],
'more_nodes': nodes.count() - len(nodes[:1]) 'more_nodes': nodes.count() - len(nodes[:10]),
'sum_node_num': nodes.count(),
'node_num': {
'running': Node.get_state_count(True, True),
'missing': Node.get_state_count(False, True),
'disabled': Node.get_state_count(True, False),
'offline': Node.get_state_count(False, False)
}
}) })
context.update({ context.update({
...@@ -247,10 +254,39 @@ class NodeDetailView(DetailView): ...@@ -247,10 +254,39 @@ class NodeDetailView(DetailView):
template_name = "dashboard/node-detail.html" template_name = "dashboard/node-detail.html"
model = Node model = Node
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(NodeDetailView, self).get_context_data(**kwargs) context = super(NodeDetailView, self).get_context_data(**kwargs)
context['table'] = NodeVmListTable(Instance.active.filter(node=self.object))
return context 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")
Node.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.node-detail",
kwargs={'pk': self.object.pk}))
class AclUpdateView(View, SingleObjectMixin): class AclUpdateView(View, SingleObjectMixin):
...@@ -333,7 +369,6 @@ class VmList(SingleTableView): ...@@ -333,7 +369,6 @@ class VmList(SingleTableView):
table_class = VmListTable table_class = VmListTable
table_pagination = False table_pagination = False
class NodeList(SingleTableView): class NodeList(SingleTableView):
template_name = "dashboard/node-list.html" template_name = "dashboard/node-list.html"
model = Node model = Node
...@@ -414,6 +449,82 @@ class VmCreate(TemplateView): ...@@ -414,6 +449,82 @@ class VmCreate(TemplateView):
else: else:
return redirect(path) return redirect(path)
class NodeCreate(TemplateView):
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/modal-wrapper.html']
else:
return ['dashboard/nojs-wrapper.html']
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context.update({
'template': 'dashboard/node-create.html',
'box_title': 'Create a Node'
})
return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super(NodeCreate, self).get_context_data(**kwargs)
# TODO acl
context.update({
'templates': InstanceTemplate.objects.all(),
'vlans': Vlan.objects.all(),
'disks': Disk.objects.exclude(type="qcow2-snap")
})
return context
# TODO handle not ajax posts
def post(self, request, *args, **kwargs):
if self.request.user.is_authenticated():
user = self.request.user
else:
user = None
resp = {}
try:
ikwargs = {
'num_cores': int(request.POST.get('cpu-count')),
'ram_size': int(request.POST.get('ram-size')),
'priority': int(request.POST.get('cpu-priority')),
}
networks = [InterfaceTemplate(vlan=Vlan.objects.get(pk=l),
managed=True)
for l in request.POST.getlist('managed-vlans')
]
networks.extend([InterfaceTemplate(vlan=Vlan.objects.get(pk=l),
managed=False)
for l in request.POST.getlist('unmanaged-vlans')
])
disks = Disk.objects.filter(pk__in=request.POST.getlist('disks'))
template = InstanceTemplate.objects.get(
pk=request.POST.get('template-pk'))
inst = Instance.create_from_template(template=template,
owner=user, networks=networks,
disks=disks, **ikwargs)
inst.deploy_async(user=request.user)
resp['pk'] = inst.pk
messages.success(request, _('Node successfully created!'))
except InstanceTemplate.DoesNotExist:
resp['error'] = True
except Exception, e:
print e
resp['error'] = True
if request.is_ajax():
return HttpResponse(json.dumps(resp),
content_type="application/json",
status=500 if resp.get('error') else 200)
else:
return redirect(reverse_lazy('dashboard.views.detail', resp))
class VmDelete(DeleteView): class VmDelete(DeleteView):
model = Instance model = Instance
...@@ -460,6 +571,94 @@ class VmDelete(DeleteView): ...@@ -460,6 +571,94 @@ class VmDelete(DeleteView):
else: else:
return reverse_lazy('dashboard.index') return reverse_lazy('dashboard.index')
model = Instance
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(VmDelete, 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()
if not object.has_level(request.user, 'owner'):
raise PermissionDenied()
object.destroy_async(user=request.user)
success_url = self.get_success_url()
success_message = _("VM 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 NodeDelete(DeleteView):
"""This stuff deletes the node.
"""
model = Node
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(NodeDelete, 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 = _("Node 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 PortDelete(DeleteView): class PortDelete(DeleteView):
model = Rule model = Rule
......
...@@ -41,6 +41,7 @@ class Node(TimeStampedModel): ...@@ -41,6 +41,7 @@ class Node(TimeStampedModel):
help_text=_("The ratio of total memory with " help_text=_("The ratio of total memory with "
"to without overcommit.")) "to without overcommit."))
class Meta: class Meta:
app_label = 'vm' app_label = 'vm'
db_table = 'vm_node' db_table = 'vm_node'
...@@ -62,6 +63,19 @@ class Node(TimeStampedModel): ...@@ -62,6 +63,19 @@ class Node(TimeStampedModel):
""" """
return self.remote_query(vm_tasks.get_core_num) return self.remote_query(vm_tasks.get_core_num)
@property
def state(self):
"""Node state.
"""
if self.enabled and self.online :
return 'online'
elif self.enabled and not self.online:
return 'missing'
elif not self.enabled and self.online:
return 'disabled'
else:
return 'offline'
@property @property
@method_cache(300) @method_cache(300)
...@@ -124,3 +138,7 @@ class Node(TimeStampedModel): ...@@ -124,3 +138,7 @@ class Node(TimeStampedModel):
for i in domains.keys(): for i in domains.keys():
logger.info('Node %s update: domain %s in libvirt but not in db.', logger.info('Node %s update: domain %s in libvirt but not in db.',
self, i) self, i)
@classmethod
def get_state_count(cls, online, enabled):
return len([1 for i in cls.objects.filter(enabled=enabled).all() if i.online==online])
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