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,10 +56,20 @@ $(function() { ...@@ -53,10 +56,20 @@ $(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>
</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> <div class="panel-body" id="node-graph-view" style="display: none">
<p><span class="big"><big>13</big> running </span> <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>
+ <big>3</big> off + <big>3</big> missing</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");
});