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() {
......
$(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