Commit d5fe25eb by Oláh István Gergely

dashboard: node functions

parent a383b8e4
......@@ -91,6 +91,10 @@ body {
min-height: 20em;
}
#node-detail-panel .panel-body {
min-height: 20em;
}
:link i:before:hover {
text-decoration: none !important;
}
......@@ -142,19 +146,20 @@ body {
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;
}
#vm-details-rename, #vm-list-rename {
#vm-details-rename, #vm-list-rename, #node-details-rename, #node-list-rename {
display: none;
}
#vm-details-rename-name {
#vm-details-rename-name, #node-details-rename-name {
max-width: 160px;
}
#vm-list-rename-name {
#vm-list-rename-name, #node-list-rename-name {
max-width: 100px;
}
......
......@@ -15,6 +15,23 @@ $(function () {
});
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) {
var box = $(this).data('index-box');
$("#" + box + "-list-view").hide();
......@@ -65,6 +82,20 @@ $(function () {
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() {
......
var vlans = [];
var disks = [];
$(function() {
nodeCreateLoaded();
});
function nodeCreateLoaded() {
$('.node-create-advanced').hide();
$('.node-create-advanced-btn').click(function() {
$('.vm-create-advanced').stop().slideToggle();
if ($('.node-create-advanced-icon').hasClass('icon-caret-down')) {
$('.node-create-advanced-icon').removeClass('icon-caret-down').addClass('icon-caret-up');
} else {
$('.node-create-advanced-icon').removeClass('icon-caret-up').addClass('icon-caret-down');
}
});
$('#node-create-template-select').change(function() {
nodeCreateTemplateChange(this);
});
/* network thingies */
/* add network */
$('#node-create-network-add-button').click(function() {
var vlan_pk = $('#node-create-network-add-select :selected').val();
var managed = $('#node-create-network-add-checkbox-managed').prop('checked');
var name = $('#node-create-network-add-select :selected').text();
if ($('#node-create-network-list').children('span').length < 1) {
$('#node-create-network-list').html('');
}
$('#node-create-network-list').append(
nodeCreateNetworkLabel(vlan_pk, name, managed)
);
/* select the network from the managed/unmanaged multiple select */
if(managed) {
$('#node-create-network-add-managed option[value="' + vlan_pk + '"]').prop('selected', true);
} else {
$('#node-create-network-add-unmanaged option[value="' + vlan_pk + '"]').prop('selected', true);
}
$('option:selected', $('#node-create-network-add-select')).remove();
/* add dummy text if no more networks are available */
if($('#node-create-network-add-select option').length < 1) {
$('#node-create-network-add-button').attr('disabled', true);
$('#node-create-network-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
}
return false;
});
/* remove network */
// event for network remove button (icon, X)
$('body').on('click', '.node-create-remove-network', function() {
var vlan_pk = ($(this).parent('span').prop('id')).replace('vlan-', '')
// if it's "blue" then it's managed, kinda not cool
var managed = $(this).parent('span').hasClass('label-primary');
$(this).parent('span').fadeOut(500, function() {
/* if ther are no more vlans disabled the add button */
if($('#node-create-network-add-select option')[0].value == -1) {
$('#node-create-network-add-button').attr('disabled', false);
$('#node-create-network-add-select').html('');
}
/* remove the network label */
$(this).remove();
var vlan_name = $(this).text();
$('#node-create-network-add-select').append($('<option>', {
value: vlan_pk,
text: vlan_name
}));
/* remove the selection from the multiple select */
$('#node-create-network-add-' + (managed ? '' : 'un') + 'managed option[value="' + vlan_pk + '"]').prop('selected', false);
if ($('#node-create-network-list').children('span').length < 1) {
$('#node-create-network-list').append('Not added to any network!');
}
});
return false;
});
/* copy networks from hidden select */
$('#node-create-network-add-select').html($('#node-create-network-add-managed').html());
/* build up network list */
$('#node-create-network-add-select option').each(function() {
vlans.push({
'name': $(this).text(),
'pk': parseInt($(this).val())
});
});
/* ----- end of networks thingies ----- */
/* add disk */
$('#node-create-disk-add-button').click(function() {
var disk_pk = $('#node-create-disk-add-select :selected').val();
var name = $('#node-create-disk-add-select :selected').text();
if ($('#node-create-disk-list').children('span').length < 1) {
$('#node-create-disk-list').html('');
}
$('#node-create-disk-list').append(
nodeCreateDiskLabel(disk_pk, name)
);
/* select the disk from the multiple select */
$('#node-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', true);
$('option:selected', $('#node-create-disk-add-select')).remove();
/* add dummy text if no more disks are available */
if($('#node-create-disk-add-select option').length < 1) {
$('#node-create-disk-add-button').attr('disabled', true);
$('#node-create-disk-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
}
return false;
});
/* remove disk */
// event for disk remove button (icon, X)
$('body').on('click', '.node-create-remove-disk', function() {
var disk_pk = ($(this).parent('span').prop('id')).replace('vlan-', '')
$(this).parent('span').fadeOut(500, function() {
/* if ther are no more disks disabled the add button */
if($('#node-create-disk-add-select option')[0].value == -1) {
$('#node-create-disk-add-button').attr('disabled', false);
$('#node-create-disk-add-select').html('');
}
/* remove the disk label */
$(this).remove();
var disk_name = $(this).text();
$('#node-create-disk-add-select').append($('<option>', {
value: disk_pk,
text: disk_name
}));
/* remove the selection from the multiple select */
$('#node-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', false);
if ($('#node-create-disk-list').children('span').length < 1) {
$('#node-create-disk-list').append('No disks are added!');
}
});
return false;
});
/* copy disks from hidden select */
$('#node-create-disk-add-select').html($('#node-create-disk-add-form').html());
/* build up disk list */
$('#node-create-disk-add-select option').each(function() {
disks.push({
'name': $(this).text(),
'pk': parseInt($(this).val())
});
});
/* add button */
$('#node-create-submit').click(function() {
$.ajax({
url: '/dashboard/node/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: $('form').serialize(),
success: function(data, textStatus, xhr) {
if(data.pk) {
window.location.replace('/dashboard/node/' + data.pk + '/#activity');
}
},
error: function(xhr, textStatus, error) {
if (xhr.status == 500) {
alert("uhuhuhuhuhuh");
} else {
alert("unknown error");
}
}
});
return false;
});
/* no js compatibility */
$('.no-js-hidden').show();
$('.js-hidden').hide();
}
function nodeCreateTemplateChange(new_this) {
this.value = new_this.value;
if(this.value < 0) return;
$.ajax({
url: '/dashboard/template/' + this.value,
type: 'GET',
success: function(data, textStatus, xhr) {
if(xhr.status == 200) {
// set sliders
$('#node-cpu-priority-slider').slider("setValue", data['priority']);
$('#node-cpu-count-slider').slider("setValue", data['num_cores']);
$('#node-ram-size-slider').slider("setValue", data['ram_size']);
/* slider doesn't have change event ........................ */
refreshSliders();
/* clear selections */
$('select[id^="node-create-network-add"], select[id$="managed"]').find('option').prop('selected', false);
$('#node-create-disk-add-form').find('option').prop('selected', false);
/* append vlans from InterfaceTemplates */
$('#vm-create-network-list').html('');
var added_vlans = []
for(var n = 0; n<data['network'].length; n++) {
nn = data['network'][n]
$('#node-create-network-list').append(
nodeCreateNetworkLabel(nn.vlan_pk, nn.vlan, nn.managed)
);
$('#node-create-network-add-' + (nn.managed ? '' : 'un') + 'managed option[value="' + nn.vlan_pk + '"]').prop('selected', true);
added_vlans.push(nn.vlan_pk);
}
/* remove already added vlans from dropdown or add new ones */
$('#node-create-network-add-select').html('');
for(var i=0; i < vlans.length; i++)
if(added_vlans.indexOf(vlans[i].pk) == -1)
$('#node-create-network-add-select').append($('<option>', {
value: vlans[i].pk,
text: vlans[i].name
}));
/* enalbe the network add button if there are not added vlans */
if(added_vlans.length != vlans.length) {
$('#node-create-network-add-button').attr('disabled', false);
} else {
$('#node-create-network-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
$('#node-create-network-add-button').attr('disabled', true);
}
/* append disks */
$('#node-create-disk-list').html('');
var added_disks = []
for(var d = 0; d<data['disks'].length; d++) {
dd = data['disks'][d]
$('#node-create-disk-list').append(
nodeCreateDiskLabel(dd.pk, dd.name)
);
$('#node-create-disk-add-form option[value="' + dd.pk + '"]').prop('selected', true);
added_disks.push(dd.pk);
}
/* remove already added disks from dropdown or add new ones */
$('#node-create-disk-add-select').html('');
for(var i=0; i < disks.length; i++)
if(added_disks.indexOf(disks[i].pk) == -1)
$('#node-create-disk-add-select').append($('<option>', {
value: disks[i].pk,
text: disks[i].name
}));
/* enalbe the disk add button if there are not added disks */
if(added_disks.length != disks.length) {
$('#node-create-disk-add-button').attr('disabled', false);
} else {
$('#node-create-disk-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
$('#node-create-disk-add-button').attr('disabled', true);
}
}
}
});
}
function vmCreateNetworkLabel(pk, name, managed) {
return '<span id="vlan-' + pk + '" class="label label-' + (managed ? 'primary' : 'default') + '"><i class="icon-' + (managed ? 'globe' : 'link') + '"></i> ' + name + ' <a href="#" class="hover-black node-create-remove-network"><i class="icon-remove-sign"></i></a></span> ';
}
function vmCreateDiskLabel(pk, name) {
return '<span id="vlan-' + pk + '" class="label label-primary"><i class="icon-file"></i> ' + name + ' <a href="#" class="hover-black node-create-remove-disk"><i class="icon-remove-sign"></i></a></span> ';
}
$(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() {
});
$('.node-list-table tbody').find('tr').mousedown(function() {
var retval = true;
if (ctrlDown) {
setRowColor($(this));
if(!$(this).hasClass('node-list-selected')) {
......@@ -20,6 +21,7 @@ $(function() {
} else {
selected.push($(this).index());
}
retval = false;
} else if(shiftDown) {
if(selected.length > 0) {
start = selected[selected.length - 1] + 1;
......@@ -36,6 +38,7 @@ $(function() {
}
}
}
retval = false;
} else {
$('.node-list-selected').removeClass('node-list-selected');
$(this).addClass('node-list-selected');
......@@ -53,10 +56,20 @@ $(function() {
} else {
$('.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() {
console.log(collectIds(selected));
});
......@@ -64,12 +77,6 @@ $(function() {
$('.node-list-details').popover({
'placement': 'auto',
'html': true,
'trigger': 'hover'
});
$('.node-list-connect').popover({
'placement': 'left',
'html': true,
'trigger': 'click'
});
......@@ -80,11 +87,48 @@ $(function() {
$('tbody a').click(function(e) {
// browser doesn't jump to top when clicked the buttons
if(!$(this).hasClass('real-link')) {
if(!$(this).hasClass('real-link')) {
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 */
/* select all */
......@@ -103,9 +147,15 @@ $(function() {
/* mass vm delete */
$('#node-list-group-delete').click(function() {
text = "Are you sure you want to delete the selected VMs?";
random_vm_pk = $('.vm-delete').eq(0).data('vm-pk');
addModalConfirmation(text, random_vm_pk, massDeleteVm, false);
addModalConfirmation(massDeleteVm,
{
'url': '/dashboard/node/mass-delete/',
'data': {
'selected': selected,
'v': collectIds(selected)
}
}
);
return false;
});
});
......@@ -114,16 +164,15 @@ function collectIds(rows) {
var ids = [];
for(var i = 0; i < rows.length; 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;
}
function setRowColor(row) {
if(!row.hasClass('vm-list-selected')) {
if(!row.hasClass('node-list-selected')) {
row.addClass('node-list-selected');
} else {
row.removeClass('node-list-selected');
}
}
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 django.utils.translation import ugettext_lazy as _
......@@ -46,17 +46,99 @@ class NodeListTable(Table):
pk = Column(
verbose_name="ID",
attrs={'th': {'class': 'node-list-table-thin'}},
)
name = LinkColumn(
'dashboard.views.node-detail',
args=[A('pk')],
attrs={'a': {'class': 'real-link'}}
overcommit = Column(
verbose_name="Overcommit",
attrs={'th': {'class': 'node-list-table-thin'}},
)
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:
model = Node
attrs = {'class': ('table table-bordered table-striped table-hover '
'node-list-table')}
fields = ('pk', 'name', 'host', 'enabled', 'created', 'modified',
'priority', 'overcommit', )
fields = ('pk', 'name', 'host', 'enabled', 'priority', 'overcommit','number_of_VMs', )
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 %}
<div class="panel panel-default">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right toolbar">
<div class="btn-group">
<a href="#node-graph-view" 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-graph-view" data-index-box="node" class="btn btn-default btn-xs"><i class="icon-dashboard"></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>
<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-sitemap"></i> {% trans "Compute nodes" %}
</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>
</div>
<div class="panel-body" id="node-graph-view" style="">
<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>
<p><span class="big"><big>13</big> running </span>
+ <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">
{% 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>
{% for i in nodes %}
<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 %}
</ul>
......@@ -31,12 +58,12 @@
</div>
</div>
<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" %}">
<i class="icon-chevron-sign-right"></i> <strong>{{ more_nodes }}</strong> more
</a>
{% 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>
......
......@@ -31,5 +31,6 @@
{% endblock %}
{% 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 %}
......@@ -6,17 +6,6 @@
<form method="POST" action="/dashboard/vm/create/">
{% csrf_token %}
<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">
<a class="btn btn-info vm-create-advanced-btn">Advanced <i class="vm-create-advanced-icon icon-caret-down"></i></a>
</div>
......
......@@ -2,14 +2,21 @@
<form id="vm-details-resources-form" method="POST" action="">
{% csrf_token %}
<p class="row">
<div class="col-sm-3">
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "Name" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.name }} </label>
</div>
</p>
<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>
</dl>
<p class="row">
<div class="col-sm-3">
......@@ -154,7 +161,7 @@
<label for="cpu-count-slider"><i class="icon-cogs"></i> {% trans "CPU count" %}</label>
</div>
<div class="col-sm-9">
<label > {{ node.host. }} </label>
<label > {{ node.host }} </label>
</div>
</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 @@
<h1>{{ node.name }} <small>{{ node.get_connect_host }}</small></h1>
</div>
<div class="row">
<div class="col-md-4" id="vm-info-pane">
<div class="col-md-4" id="node-info-pane">
<div class="big">
<span class="label label-success">{{ node.enabled }}</span>
<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="#"><i class="icon-refresh"></i> Flash</a></li>
<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>
<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">
{% with node as record %}
{% include "dashboard/node-list/column-actions.html" with btn_size="" %}
{% endwith %}
</div>
</div>
</div>
<div class="col-md-8" id="vm-detail-pane">
<div class="panel panel-default" id="vm-detail-panel">
<div class="col-md-8" id="node-detail-pane">
<div class="panel panel-default" id="node-detail-panel">
<ul class="nav nav-pills panel-heading">
<li class="active">
<a href="#home" data-toggle="pill" class="text-center">
......@@ -33,16 +33,21 @@
<i class="icon-tasks icon-2x"></i><br>
{% trans "Resources" %}</a></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">
<i class="icon-time icon-2x"></i><br>
{% trans "Activity" %}</a></li>
</ul>
</ul>
<div class="tab-content panel-body">
<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="activity">{% include "dashboard/node-detail-activity.html" %}</div>
</div>
<div class="tab-pane" id="virtualmachines">{% include "dashboard/node-detail-vm.html" %}</div>
</div>
</div>
</div>
</div>
......
<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">
<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-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>
</ul>
<li><a href="#"><i class="icon-cloud-upload"></i> Flush</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> 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>
</div>
<a class="btn btn-default btn-xs" title data-original-title="Migrate">
<i class="icon-truck"></i>
<a class="btn btn-default btn-xs" title data-original-title="Flush">
<i class="icon-cloud-upload"></i>
</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>
</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>
</a>
......@@ -3,7 +3,7 @@
<h4>Quick details</h4>
<dl class="dl-horizontal">
<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>
</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
from .views import (
IndexView, VmDetailView, VmList, VmCreate, TemplateDetail, AclUpdateView,
VmDelete, VmMassDelete, vm_activity, NodeList, NodeDetailView, PortDelete,
TransferOwnershipView, TransferOwnershipConfirmView
)
TransferOwnershipView, TransferOwnershipConfirmView, NodeDelete,
NodeCreate)
urlpatterns = patterns(
'',
......@@ -33,5 +33,8 @@ urlpatterns = patterns(
name='dashboard.views.node-detail'),
url(r'^tx/$', TransferOwnershipConfirmView.as_view(),
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
from braces.views import LoginRequiredMixin
from .forms import VmCreateForm
from .tables import (VmListTable, NodeListTable)
from .tables import (VmListTable, NodeListTable, NodeVmListTable)
from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
InstanceActivity, Node, instance_activity)
from firewall.models import Vlan, Host, Rule
......@@ -49,8 +49,15 @@ class IndexView(TemplateView):
nodes = Node.objects.all()
context.update({
'nodes': nodes[:1],
'more_nodes': nodes.count() - len(nodes[:1])
'nodes': nodes[:10],
'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({
......@@ -247,10 +254,39 @@ class NodeDetailView(DetailView):
template_name = "dashboard/node-detail.html"
model = Node
def get_context_data(self, **kwargs):
context = super(NodeDetailView, self).get_context_data(**kwargs)
context['table'] = NodeVmListTable(Instance.active.filter(node=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")
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):
......@@ -333,7 +369,6 @@ class VmList(SingleTableView):
table_class = VmListTable
table_pagination = False
class NodeList(SingleTableView):
template_name = "dashboard/node-list.html"
model = Node
......@@ -414,6 +449,82 @@ class VmCreate(TemplateView):
else:
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):
model = Instance
......@@ -460,6 +571,94 @@ class VmDelete(DeleteView):
else:
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):
model = Rule
......
......@@ -41,6 +41,7 @@ class Node(TimeStampedModel):
help_text=_("The ratio of total memory with "
"to without overcommit."))
class Meta:
app_label = 'vm'
db_table = 'vm_node'
......@@ -62,6 +63,19 @@ class Node(TimeStampedModel):
"""
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
@method_cache(300)
......@@ -124,3 +138,7 @@ class Node(TimeStampedModel):
for i in domains.keys():
logger.info('Node %s update: domain %s in libvirt but not in db.',
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