Commit af03144a by Őry Máté

Merge branch 'feature-vm-mass-op' into 'feature-mass-ops'

save resultant_state early
parents 5b01216a 48b00b23
...@@ -25,9 +25,9 @@ def handler500(request): ...@@ -25,9 +25,9 @@ def handler500(request):
ctx['error'] = exception.get_admin_text() ctx['error'] = exception.get_admin_text()
except: except:
pass pass
try: try:
resp = render_to_response("500.html", ctx, RequestContext(request)) resp = render_to_response("500.html", ctx, RequestContext(request))
except: except:
resp = render_to_response("500.html", ctx) resp = render_to_response("500.html", ctx)
resp.status_code = 500 resp.status_code = 500
return resp return resp
...@@ -552,7 +552,8 @@ class TemplateForm(forms.ModelForm): ...@@ -552,7 +552,8 @@ class TemplateForm(forms.ModelForm):
exclude = ('state', 'disks', ) exclude = ('state', 'disks', )
widgets = { widgets = {
'system': forms.TextInput, 'system': forms.TextInput,
'max_ram_size': forms.HiddenInput 'max_ram_size': forms.HiddenInput,
'parent': forms.Select(attrs={'disabled': ""}),
} }
...@@ -925,10 +926,8 @@ class TraitForm(forms.ModelForm): ...@@ -925,10 +926,8 @@ class TraitForm(forms.ModelForm):
Field('name', id="node-details-traits-input", Field('name', id="node-details-traits-input",
css_class="input-sm input-traits"), css_class="input-sm input-traits"),
Div( Div(
HTML('<input type="submit" ' Submit("submit", _("Add trait"),
'class="btn btn-default btn-sm input-traits" ' css_class="btn btn-primary btn-sm input-traits"),
'value="Add trait"/>',
),
css_class="input-group-btn", css_class="input-group-btn",
), ),
css_class="input-group", css_class="input-group",
......
$(function() {
/* rename */ /* rename */
$("#node-details-h1-name, .node-details-rename-button").click(function() { $("#node-details-h1-name, .node-details-rename-button").click(function() {
$("#node-details-h1-name").hide(); $("#node-details-h1-name").hide();
...@@ -43,26 +44,6 @@ ...@@ -43,26 +44,6 @@
return false; return false;
}); });
function changeNodeStatus(data) {
$.ajax({
type: 'POST',
url: data['url'],
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
if(!data['redirect']) {
selected = [];
addMessage(re['message'], 'success');
} else {
window.location.replace('/dashboard');
}
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
}
});
}
// remove trait // remove trait
$('.node-details-remove-trait').click(function() { $('.node-details-remove-trait').click(function() {
var to_remove = $(this).data("trait-pk"); var to_remove = $(this).data("trait-pk");
...@@ -86,3 +67,24 @@ function changeNodeStatus(data) { ...@@ -86,3 +67,24 @@ function changeNodeStatus(data) {
}); });
return false; return false;
}); });
});
function changeNodeStatus(data) {
$.ajax({
type: 'POST',
url: data['url'],
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
if(!data['redirect']) {
selected = [];
addMessage(re['message'], 'success');
} else {
window.location.replace('/dashboard');
}
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
}
});
}
var ctrlDown, shiftDown = false;
var ctrlKey = 17;
var shiftKey = 16;
var selected = [];
$(function() { $(function() {
$(document).keydown(function(e) { $(document).ready( function() {
if (e.keyCode == ctrlKey) ctrlDown = true;
if (e.keyCode == shiftKey) shiftDown = true;
}).keyup(function(e) {
if (e.keyCode == ctrlKey) ctrlDown = false;
if (e.keyCode == shiftKey) shiftDown = false;
});
$('.node-list-table tbody').find('tr').mousedown(function() {
var retval = true;
if (ctrlDown) {
setRowColor($(this));
if(!$(this).hasClass('node-list-selected')) {
selected.splice(selected.indexOf($(this).index()), 1);
} else {
selected.push($(this).index());
}
retval = false;
} else if(shiftDown) {
if(selected.length > 0) {
start = selected[selected.length - 1] + 1;
end = $(this).index();
if(start > end) {
var tmp = start - 1; start = end; end = tmp - 1;
}
for(var i = start; i <= end; i++) {
if(selected.indexOf(i) < 0) {
selected.push(i);
setRowColor($('.node-list-table tbody tr').eq(i));
}
}
}
retval = false;
} else {
$('.node-list-selected').removeClass('node-list-selected');
$(this).addClass('node-list-selected');
selected = [$(this).index()];
}
// reset btn disables
$('.node-list-table tbody tr .btn').attr('disabled', false);
// show/hide group controls
if(selected.length > 1) {
$('.node-list-group-control a').attr('disabled', false);
for(var i = 0; i < selected.length; i++) {
$('.node-list-table tbody tr').eq(selected[i]).find('.btn').attr('disabled', true);
}
} else {
$('.node-list-group-control a').attr('disabled', true);
}
return retval;
});
$('#node-list-group-migrate').click(function() {
console.log(collectIds(selected));
});
$(document).ready( function()
{
colortable(); colortable();
$('.node-list-details').popover(
{
placement : 'auto',
html : true,
trigger : 'click',
});
});
$('tbody a').mousedown(function(e) {
// parent tr doesn't get selected when clicked
e.stopPropagation();
});
$('tbody a').click(function(e) {
// browser doesn't jump to top when clicked the buttons
if(!$(this).hasClass('real-link')) {
return false;
}
}); });
// find disabled nodes, set danger (red) on the rows // find disabled nodes, set danger (red) on the rows
...@@ -176,51 +91,4 @@ $(function() { ...@@ -176,51 +91,4 @@ $(function() {
}); });
return false; return false;
}); });
/* group actions */
/* select all */
$('#node-list-group-select-all').click(function() {
$('.node-list-table tbody tr').each(function() {
var index = $(this).index();
if(selected.indexOf(index) < 0) {
selected.push(index);
$(this).addClass('node-list-selected');
}
});
if(selected.length > 0)
$('.node-list-group-control a').attr('disabled', false);
return false;
});
/* mass vm delete */
$('#node-list-group-delete').click(function() {
addModalConfirmation(massDeleteVm,
{
'url': '/dashboard/node/mass-delete/',
'data': {
'selected': selected,
'v': collectIds(selected)
}
}
);
return false;
});
}); });
function collectIds(rows) {
var ids = [];
for(var i = 0; i < rows.length; i++) {
var div = $('td:first-child div', $('.node-list-table tbody tr').eq(rows[i]));
ids.push(div.prop('id').replace('node-', ''));
}
return ids;
}
function setRowColor(row) {
if(!row.hasClass('node-list-selected')) {
row.addClass('node-list-selected');
} else {
row.removeClass('node-list-selected');
}
}
...@@ -22,7 +22,7 @@ from django_tables2 import Table, A ...@@ -22,7 +22,7 @@ from django_tables2 import Table, A
from django_tables2.columns import (TemplateColumn, Column, BooleanColumn, from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
LinkColumn) LinkColumn)
from vm.models import Instance, Node, InstanceTemplate, Lease from vm.models import Node, InstanceTemplate, Lease
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
...@@ -35,16 +35,11 @@ class NodeListTable(Table): ...@@ -35,16 +35,11 @@ class NodeListTable(Table):
) )
overcommit = Column( overcommit = Column(
verbose_name="Overcommit", verbose_name=_("Overcommit"),
attrs={'th': {'class': 'node-list-table-thin'}}, attrs={'th': {'class': 'node-list-table-thin'}},
) )
host = Column(
verbose_name="Host",
)
enabled = BooleanColumn( enabled = BooleanColumn(
verbose_name="Enabled",
attrs={'th': {'class': 'node-list-table-thin'}}, attrs={'th': {'class': 'node-list-table-thin'}},
) )
...@@ -54,28 +49,28 @@ class NodeListTable(Table): ...@@ -54,28 +49,28 @@ class NodeListTable(Table):
) )
priority = Column( priority = Column(
verbose_name=_("Priority"),
attrs={'th': {'class': 'node-list-table-thin'}}, attrs={'th': {'class': 'node-list-table-thin'}},
) )
number_of_VMs = TemplateColumn( number_of_VMs = TemplateColumn(
verbose_name=_("Number of VMs"),
template_name='dashboard/node-list/column-vm.html', template_name='dashboard/node-list/column-vm.html',
attrs={'th': {'class': 'node-list-table-thin'}}, attrs={'th': {'class': 'node-list-table-thin'}},
) )
monitor = TemplateColumn( monitor = TemplateColumn(
verbose_name=_("Monitor"),
template_name='dashboard/node-list/column-monitor.html', template_name='dashboard/node-list/column-monitor.html',
attrs={'th': {'class': 'node-list-table-monitor'}}, attrs={'th': {'class': 'node-list-table-monitor'}},
orderable=False,
) )
details = TemplateColumn(
template_name='dashboard/node-list/column-details.html',
attrs={'th': {'class': 'node-list-table-thin'}},
)
actions = TemplateColumn( actions = TemplateColumn(
verbose_name=_("Actions"),
attrs={'th': {'class': 'node-list-table-thin'}}, attrs={'th': {'class': 'node-list-table-thin'}},
template_code=('{% include "dashboard/node-list/column-' template_code=('{% include "dashboard/node-list/column-'
'actions.html" with btn_size="btn-xs" %}'), 'actions.html" with btn_size="btn-xs" %}'),
orderable=False,
) )
class Meta: class Meta:
...@@ -141,52 +136,14 @@ class UserListTable(Table): ...@@ -141,52 +136,14 @@ class UserListTable(Table):
fields = ('pk', 'username', ) fields = ('pk', 'username', )
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', )
class UserListTablex(Table): class UserListTablex(Table):
class Meta: class Meta:
model = User model = User
class TemplateListTable(Table): class TemplateListTable(Table):
name = LinkColumn( name = TemplateColumn(
'dashboard.views.template-detail', template_name="dashboard/template-list/column-template-name.html",
args=[A('pk')],
attrs={'th': {'data-sort': "string"}} attrs={'th': {'data-sort': "string"}}
) )
num_cores = Column( num_cores = Column(
...@@ -194,23 +151,31 @@ class TemplateListTable(Table): ...@@ -194,23 +151,31 @@ class TemplateListTable(Table):
attrs={'th': {'data-sort': "int"}} attrs={'th': {'data-sort': "int"}}
) )
ram_size = TemplateColumn( ram_size = TemplateColumn(
"{{ record.ram_size }} Mb", "{{ record.ram_size }} MiB",
attrs={'th': {'data-sort': "string"}} attrs={'th': {'data-sort': "int"}},
) )
lease = TemplateColumn( lease = TemplateColumn(
"{{ record.lease.name }}", "{{ record.lease.name }}",
verbose_name=_("Lease"), verbose_name=_("Lease"),
attrs={'th': {'data-sort': "string"}} attrs={'th': {'data-sort': "string"}}
) )
arch = Column(
attrs={'th': {'data-sort': "string"}}
)
system = Column( system = Column(
attrs={'th': {'data-sort': "string"}} attrs={'th': {'data-sort': "string"}}
) )
access_method = Column( access_method = Column(
attrs={'th': {'data-sort': "string"}} attrs={'th': {'data-sort': "string"}}
) )
owner = TemplateColumn(
template_name="dashboard/template-list/column-template-owner.html",
verbose_name=_("Owner"),
attrs={'th': {'data-sort': "string"}}
)
running = TemplateColumn(
template_name="dashboard/template-list/column-template-running.html",
verbose_name=_("Running"),
attrs={'th': {'data-sort': "int"}},
orderable=False,
)
actions = TemplateColumn( actions = TemplateColumn(
verbose_name=_("Actions"), verbose_name=_("Actions"),
template_name="dashboard/template-list/column-template-actions.html", template_name="dashboard/template-list/column-template-actions.html",
...@@ -222,8 +187,8 @@ class TemplateListTable(Table): ...@@ -222,8 +187,8 @@ class TemplateListTable(Table):
model = InstanceTemplate model = InstanceTemplate
attrs = {'class': ('table table-bordered table-striped table-hover' attrs = {'class': ('table table-bordered table-striped table-hover'
' template-list-table')} ' template-list-table')}
fields = ('name', 'num_cores', 'ram_size', 'arch', fields = ('name', 'num_cores', 'ram_size', 'system',
'system', 'access_method', 'lease', 'actions', ) 'access_method', 'lease', 'owner', 'running', 'actions', )
prefix = "template-" prefix = "template-"
......
...@@ -54,11 +54,13 @@ ...@@ -54,11 +54,13 @@
<div class="row"> <div class="row">
<div class="col-md-2" id="node-info-pane"> <div class="col-md-2" id="node-info-pane">
<div id="node-info-data" class="big"> <div id="node-info-data" class="big">
<span id="node-details-state" class="label {% if node.state == 'ONLINE' %}label-success <span id="node-details-state" class="label
{% elif node.state == 'MISSING' %}label-danger {% if node.state == 'ONLINE' %}label-success
{% elif node.state == 'DISABLED' %}label-warning {% elif node.state == 'MISSING' %}label-danger
{% elif node.state == 'OFFLINE' %}label-warning {% elif node.state == 'DISABLED' %}label-warning
{% endif %}">{{ node.get_status_display|upper }}</span> {% elif node.state == 'OFFLINE' %}label-warning{% endif %}">
<i class="fa {{ node.get_status_icon }}"></i> {{ node.get_status_display|upper }}
</span>
</div> </div>
</div> </div>
<div class="col-md-10" id="node-detail-pane"> <div class="col-md-10" id="node-detail-pane">
...@@ -67,39 +69,41 @@ ...@@ -67,39 +69,41 @@
<li class="active"> <li class="active">
<a href="#home" data-toggle="pill" class="text-center"> <a href="#home" data-toggle="pill" class="text-center">
<i class="fa fa-compass fa-2x"></i><br> <i class="fa fa-compass fa-2x"></i><br>
{% trans "Home" %}</a></li> {% trans "Home" %}
</a>
</li>
<li> <li>
<a href="#resources" data-toggle="pill" class="text-center"> <a href="#resources" data-toggle="pill" class="text-center">
<i class="fa fa-tasks fa-2x"></i><br> <i class="fa fa-tasks fa-2x"></i><br>
{% trans "Resources" %}</a></li> {% trans "Resources" %}
</a>
</li>
<li> <li>
<a href="#virtualmachines" data-toggle="pill" class="text-center"> <a href="{% url "dashboard.views.vm-list" %}?s=node:{{ node.name }}"
target="blank" class="text-center">
<i class="fa fa-desktop fa-2x"></i><br> <i class="fa fa-desktop fa-2x"></i><br>
{% trans "Virtual Machines" %}</a></li> {% trans "Virtual Machines" %}
</a>
</li>
<li> <li>
<a href="#activity" data-toggle="pill" class="text-center"> <a href="#activity" data-toggle="pill" class="text-center">
<i class="fa fa-clock-o fa-2x"></i><br> <i class="fa fa-clock-o fa-2x"></i><br>
{% trans "Activity" %}</a></li> {% trans "Activity" %}
</a>
</li>
</ul> </ul>
<div id="panel-body" class="tab-content panel-body"> <div id="panel-body" class="tab-content panel-body">
<div class="tab-pane active" id="home">{% include "dashboard/node-detail/home.html" %}</div> <div class="tab-pane active" id="home">{% include "dashboard/node-detail/home.html" %}</div>
<div class="tab-pane" id="resources">{% include "dashboard/node-detail/resources.html" %}</div> <div class="tab-pane" id="resources">{% include "dashboard/node-detail/resources.html" %}</div>
<div class="tab-pane" id="activity">{% include "dashboard/node-detail/activity.html" %}</div> <div class="tab-pane" id="activity">{% include "dashboard/node-detail/activity.html" %}</div>
<div class="tab-pane" id="virtualmachines">{% include "dashboard/node-detail/vm.html" %}</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<style>
.popover {
max-width: 600px;
}
</style>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/node-details.js"></script> <script src="{{ STATIC_URL}}dashboard/node-details.js"></script>
{% endblock %} {% endblock %}
...@@ -5,15 +5,17 @@ ...@@ -5,15 +5,17 @@
<span class="timeline-icon{% if a.has_failed %} timeline-icon-failed{% endif %}"> <span class="timeline-icon{% if a.has_failed %} timeline-icon-failed{% endif %}">
<i class="fa {% if not a.finished %}fa-refresh fa-spin {% else %}fa-plus{% endif %}"></i> <i class="fa {% if not a.finished %}fa-refresh fa-spin {% else %}fa-plus{% endif %}"></i>
</span> </span>
<strong>{% if user.is_superuser %} <strong title="{{ a.result.get_admin_text }}">
{{ a.readable_name.get_admin_text }} {{ a.readable_name.get_admin_text|capfirst }}
{% else %} </strong>
{{ a.readable_name.get_user_text }}{% endif %}</strong>
{{ a.started|date:"Y-m-d H:i" }}, {{ a.user }} {{ a.started|date:"Y-m-d H:i" }}, {{ a.user }}
{% if a.children.count > 0 %} {% if a.children.count > 0 %}
<div class="sub-timeline"> <div class="sub-timeline">
{% for s in a.children.all %} {% for s in a.children.all %}
<div data-activity-id="{{ s.pk }}" class="sub-activity{% if s.has_failed %} sub-activity-failed{% endif %}"> <div data-activity-id="{{ s.pk }}"
class="sub-activity{% if s.has_failed %} sub-activity-failed{% endif %}"
>
{% if user.is_superuser %} {% if user.is_superuser %}
{{ s.readable_name.get_admin_text }} {{ s.readable_name.get_admin_text }}
{% else %} {% else %}
...@@ -25,7 +27,7 @@ ...@@ -25,7 +27,7 @@
<i class="fa fa-refresh fa-spin" class="sub-activity-loading-icon"></i> <i class="fa fa-refresh fa-spin" class="sub-activity-loading-icon"></i>
{% endif %} {% endif %}
{% if s.has_failed %} {% if s.has_failed %}
<div class="label label-danger">{% trans "failed" %}</div> <div title="{{ s.result.get_admin_text }}" class="label label-danger">{% trans "failed" %}</div>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
......
...@@ -4,15 +4,25 @@ ...@@ -4,15 +4,25 @@
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>{% trans "Node name" %}:</dt><dd>{{ node.name }}</dd> <dt>{% trans "Node name" %}:</dt><dd>{{ node.name }}</dd>
<dt>{% trans "CPU cores" %}:</dt><dd>{{ node.info.core_num }}</dd> <dt>{% trans "CPU cores" %}:</dt><dd>{{ node.info.core_num }}</dd>
<dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.info.ram_size 1048576 1 %} MB</dd> <dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.info.ram_size 1048576 1 %} MiB</dd>
<dt>{% trans "Architecture" %}:</dt><dd>{{ node.info.architecture }}</dd> <dt>{% trans "Architecture" %}:</dt><dd>{{ node.info.architecture }}</dd>
<dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd> <dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd>
<dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled }}</dd> <dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled }}</dd>
<dt>{% trans "Host online" %}:</dt><dd> {{ node.online }}</dd> <dt>{% trans "Host online" %}:</dt><dd> {{ node.online }}</dd>
<dt>{% trans "Priority" %}:</dt><dd>{{ node.priority }}</dd> <dt>{% trans "Priority" %}:</dt><dd>{{ node.priority }}</dd>
<dt>{% trans "Host owner" %}:</dt><dd>{{ node.host.owner }}</dd> <dt>{% trans "Host owner" %}:</dt>
<dd>
{% include "dashboard/_display-name.html" with user=node.host.owner show_org=True %}
</dd>
<dt>{% trans "Vlan" %}:</dt><dd>{{ node.host.vlan }}</dd> <dt>{% trans "Vlan" %}:</dt><dd>{{ node.host.vlan }}</dd>
<dt>{% trans "Host name" %}:</dt><dd>{{ node.host.hostname }}</dd> <dt>{% trans "Host name" %}:</dt>
<dd>
{{ node.host.hostname }}
<a href="{{ node.host.get_absolute_url }}" class="btn btn-default btn-xs">
<i class="fa fa-pencil"></i>
{% trans "Edit host" %}
</a>
</dd>
</dl> </dl>
{% block extra_js %} {% block extra_js %}
......
{% 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 %}
...@@ -12,35 +12,24 @@ ...@@ -12,35 +12,24 @@
<div class="panel-heading"> <div class="panel-heading">
<h3 class="no-margin"><i class="fa fa-desktop"></i> {% trans "Compute nodes" %}</h3> <h3 class="no-margin"><i class="fa fa-desktop"></i> {% trans "Compute nodes" %}</h3>
</div> </div>
<div id="table_container"> <div id="table_container">
<div id="rendered_table" class="panel-body"> <div id="rendered_table" class="panel-body">
{% render_table table %} {% render_table table %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<style>
.node-list-table tbody>tr>td { <style>
.node-list-table tbody>tr>td, .node-list-table thead>tr>th {
vertical-align: middle; vertical-align: middle;
} }
.popover { .node-list-table thead>tr>th,
max-width: 600px; .node-list-table .enabled, .node-list-table .priority,
} .node-list-table .overcommit, .node-list-table .number_of_VMs {
text-align: center;
.node-list-selected, .node-list-selected td {
background-color: #e8e8e8 !important;
}
.node-list-selected:hover, .node-list-selected:hover td {
background-color: #d0d0d0 !important;
}
.node-list-selected td:first-child {
font-weight: bold;
} }
.node-list-table-thin { .node-list-table-thin {
...@@ -54,5 +43,5 @@ ...@@ -54,5 +43,5 @@
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/node-list.js"></script> <script src="{{ STATIC_URL}}dashboard/node-list.js"></script>
{% endblock %} {% endblock %}
{% load i18n %} {% load i18n %}
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn {{ btn_size }} btn-warning nojs-dropdown-toogle dropdown-toggle" data-toggle="dropdown">Action <i class="fa fa-caret-down"></i></button> <button type="button" class="btn {{ btn_size }} btn-warning nojs-dropdown-toogle dropdown-toggle" data-toggle="dropdown">Action
<i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu nojs-dropdown-toogle" role="menu"> <ul class="dropdown-menu nojs-dropdown-toogle" role="menu">
<li><a href="#" class="node-details-rename-button"><i class="fa fa-pencil"></i> {% trans "Rename" %}</a></li> <li>
<li><a data-node-pk="{{ record.pk }}" class="real-link node-flush" href="{% url "dashboard.views.flush-node" pk=record.pk %}"><i class="fa fa-cloud-upload"></i>{% trans "Flush" %}</a> <a href="#" class="node-details-rename-button">
<li><a style={% if record.enabled %}"display:none"{% else %}"display:block"{% endif %} data-node-pk="{{ record.pk }}" class="real-link node-enable" href="{% url "dashboard.views.status-node" pk=record.pk %}?next={{ request.path }}"><i class="fa fa-check"></i>{% trans "Enable" %}</a> <i class="fa fa-pencil"></i> {% trans "Rename" %}
<a style={% if record.enabled %}"display:block"{% else %}"display:none"{% endif %} data-node-pk="{{ record.pk }}" class="real-link node-enable" href="{% url "dashboard.views.status-node" pk=record.pk %}?next={{ request.path }}"><i class="fa fa-times"></i>{% trans "Disable" %}</a></li> </a>
<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="fa fa-trash"></i>{% trans "Delete" %}</a></li> </li>
</ul> <li>
<a data-node-pk="{{ record.pk }}" class="real-link node-flush" href="{% url "dashboard.views.flush-node" pk=record.pk %}">
<i class="fa fa-cloud-upload"></i> {% trans "Flush" %}
</a>
</li>
<li>
<a style={% if record.enabled %}"display:none"{% else %}"display:block"{% endif %} data-node-pk="{{ record.pk }}" class="real-link node-enable" href="{% url "dashboard.views.status-node" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-check"></i> {% trans "Enable" %}
</a>
</li>
<li>
<a style={% if record.enabled %}"display:block"{% else %}"display:none"{% endif %} data-node-pk="{{ record.pk }}" class="real-link node-enable" href="{% url "dashboard.views.status-node" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-times"></i> {% trans "Disable" %}
</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="fa fa-trash-o"></i> {% trans "Delete" %}
</a>
</li>
</ul>
</div> </div>
{% load i18n %}
<a class="btn btn-default btn-xs" title="{% trans "Flush" %}">
<i class="fa fa-cloud-upload"></i>
</a>
<a id="node-list-rename-button" class="btn btn-default btn-xs" title="{% trans "Rename" %}">
<i class="fa fa-pencil"></i>
</a>
<a class="btn btn-info btn-xs node-list-details" rel="popover" href="#" data-toggle="popover"
data-content='
<h4>Quick details</h4>
<dl class="dl-horizontal">
<dt>Number of cores:</dt><dd>{{ record.num_cores }}</dd>
<dt>Memory:</dt> <dd>{% widthratio record.ram_size 1048576 1 %} MB</dd>
<dt>Architecture:</td><dd>{{ record.arch }}</dd>
</dl>
<dl>
<dt>IPv4 address:</dt><dd>{{ record.ipv4 }}10.9.8.7</dd>
<dt>IPv6 address:</dt><dd> 2001:2001:2001:2001:2001:2001::</dd>
<dt>DNS name:</dt><dd>1825.vm.ik.bme.hu</dd>
</ul>
'>Details</a>
<div id="node-{{ record.pk }}">{{ record.pk }}</div>
{% load i18n %} {% load i18n %}
<div id="node-list-column-vm"> <div id="node-list-column-vm">
<a class="real-link" href="{% url "dashboard.views.node-detail" pk=record.pk %}#virtualmachines">{{ value }}</a> <a class="real-link" href="{% url "dashboard.views.vm-list" %}?s=node:{{ record.name }}">
{{ value }}
</a>
</div> </div>
<tr>
<!--<td><input type="checkbox"/ class="vm-checkbox" id="vm-1825{{ c }}"></td>-->
<td>
<div id="vm-1{{ c }}">1{{ c }}</div>
</td>
<td><a href="" class="real-link">network-devenv</a></td>
<td>running</td>
<td>10 days</td>
<td>1 month</td>
<td>
<a class="btn btn-default btn-xs" title data-original-title="Migrate">
<i class="fa fa-truck"></i>
</a>
<a class="btn btn-default btn-xs" title data-original-title="Rename">
<i class="fa fa-pencil"></i>
</a>
<a href="#" class="btn btn-default btn-xs vm-list-connect" data-toggle="popover"
data-content='
Belépés: <input style="width: 300px;" type="text" class="form-control" value="ssh cloud@vm.ik.bme.hu -p22312"/>
Jelszó: <input style="width: 300px;" type="text" class="form-control" value="asdfkicsiasdfkocsi"/>
'>Connect</a>
</td>
<td>
<a class="btn btn-info btn-xs vm-list-details" href="#" data-toggle="popover"
data-content='
<h4>Quick details</h4>
<dl class="dl-horizontal">
<dt>Number of cores:</dt><dd>4</dd>
<dt>Memory:</dt> <dd>512 MB</dd>
<dt>Architecture:</td><dd>x86-64</dd>
</dl>
<dl>
<dt>IPv4 address:</dt><dd>10.9.8.7</dd>
<dt>IPv6 address:</dt><dd> 2001:2001:2001:2001:2001:2001::</dd>
<dt>DNS name:</dt><dd>1825.vm.ik.bme.hu</dd>
</ul>
'>Details</a>
</td>
<td>
<div class="btn-group">
<button type="button" class="btn btn-xs btn-warning nojs-dropdown-toogle dropdown-toggle" data-toggle="dropdown">Action <i class="fa fa-caret-down"></i></button>
<ul class="nojs-dropdown-menu dropdown-menu" role="menu">
<li><a href="#"><i class="fa fa-refresh"></i> Reboot</a></li>
<li><a href="#"><i class="fa fa-off"></i> Shutdown</a></li>
<li><a href="#"><i class="fa fa-times"></i> Discard</a></li>
</ul>
</div>
</td>
</tr>
...@@ -23,6 +23,18 @@ ...@@ -23,6 +23,18 @@
{% csrf_token %} {% csrf_token %}
{{ form.name|as_crispy_field }} {{ form.name|as_crispy_field }}
<a {% if form.parent.value %}
href="{% url "dashboard.views.template-detail" pk=form.parent.value %}"
{% else %}
disabled %}
{% endif %}
class="btn btn-default pull-right" style="margin-top: 24px;">
{% trans "Visit" %}
<i class="fa fa-arrow-circle-right"></i>
</a>
<div style="width: 80%;">
{{ form.parent|as_crispy_field }}
</div>
<fieldset class="resources-sliders"> <fieldset class="resources-sliders">
<legend>{% trans "Resource configuration" %}</legend> <legend>{% trans "Resource configuration" %}</legend>
......
<a href="{% url "dashboard.views.template-detail" pk=record.pk %}" title="{{ record.description }}">
{{ record.name }}
</a>
{% include "dashboard/_display-name.html" with user=record.owner show_org=True %}
<a href="{% url "dashboard.views.vm-list" %}?s=template:{{ record.pk }}%20status:running">
{{ record.get_running_instances.count }}
</a>
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<span class="operation operation-{{op.op}} btn btn-default disabled btn-xs"> <span class="operation operation-{{op.op}} btn btn-default disabled btn-xs">
{% else %} {% else %}
<a href="{{op.get_url}}" class="operation operation-{{op.op}} btn <a href="{{op.get_url}}" class="operation operation-{{op.op}} btn
btn-{{op.effect}} btn-xs" title="{{op.name}}: {{op.description}}"> btn-{{op.effect}} btn-xs" title="{{op.name|capfirst}}: {{op.description|truncatewords:20}}">
{% endif %} {% endif %}
<i class="fa fa-{{op.icon}}"></i> <i class="fa fa-{{op.icon}}"></i>
<span{% if not op.is_preferred %} class="sr-only"{% endif %}>{{op.name}}</span> <span{% if not op.is_preferred %} class="sr-only"{% endif %}>{{op.name}}</span>
......
...@@ -90,6 +90,16 @@ ...@@ -90,6 +90,16 @@
</div> </div>
</form> </form>
</div><!-- id:vm-details-tags --> </div><!-- id:vm-details-tags -->
<dl>
<dt>{% trans "Template" %}:</dt>
<dd>
{% if instance.template %}
{{ instance.template.name }}
{% else %}
-
{% endif %}
</dd>
</dl>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
{% if graphite_enabled %} {% if graphite_enabled %}
......
...@@ -74,7 +74,7 @@ from .forms import ( ...@@ -74,7 +74,7 @@ from .forms import (
) )
from .tables import ( from .tables import (
NodeListTable, NodeVmListTable, TemplateListTable, LeaseListTable, NodeListTable, TemplateListTable, LeaseListTable,
GroupListTable, UserKeyListTable GroupListTable, UserKeyListTable
) )
from common.models import HumanReadableObject, HumanReadableException from common.models import HumanReadableObject, HumanReadableException
...@@ -1081,8 +1081,6 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): ...@@ -1081,8 +1081,6 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
if form is None: if form is None:
form = self.form_class() form = self.form_class()
context = super(NodeDetailView, self).get_context_data(**kwargs) context = super(NodeDetailView, self).get_context_data(**kwargs)
instances = Instance.active.filter(node=self.object)
context['table'] = NodeVmListTable(instances)
na = NodeActivity.objects.filter( na = NodeActivity.objects.filter(
node=self.object, parent=None node=self.object, parent=None
).order_by('-started').select_related() ).order_by('-started').select_related()
...@@ -1640,6 +1638,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView): ...@@ -1640,6 +1638,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
'tags[]': "tags__name__in", 'tags[]': "tags__name__in",
'tags': "tags__name__in", # for search string 'tags': "tags__name__in", # for search string
'owner': "owner__username", 'owner': "owner__username",
'template': "template__pk",
} }
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
......
...@@ -6,11 +6,18 @@ ...@@ -6,11 +6,18 @@
{% block page_title %}{% trans ":(" %}{% endblock page_title %} {% block page_title %}{% trans ":(" %}{% endblock page_title %}
{% block content %} {% block content %}
<div style="margin-top: 4em;"> <div class="alert alert-danger" style="font-size: 22px; margin-top: 2em;">
{% if error %} <div class="row">
<p>{{ error }}</p> <div class="col-md-2" style="text-align: center;">
{% else %} HTTP 500
<p>{% trans "Internal Server Error... Please leave the server alone..." %}</p> </div>
{% endif %} <div class="col-md-10" style="text-align: center;">
{% if error %}
{{ error }}
{% else %}
{% trans "Internal Server Error... Please leave the server alone..." %}
{% endif %}
</div>
</div>
</div> </div>
{% endblock content %} {% endblock content %}
...@@ -90,7 +90,8 @@ class InstanceActivity(ActivityModel): ...@@ -90,7 +90,8 @@ class InstanceActivity(ActivityModel):
@classmethod @classmethod
def create(cls, code_suffix, instance, task_uuid=None, user=None, def create(cls, code_suffix, instance, task_uuid=None, user=None,
concurrency_check=True, readable_name=None): concurrency_check=True, readable_name=None,
resultant_state=None):
readable_name = _normalize_readable_name(readable_name, code_suffix) readable_name = _normalize_readable_name(readable_name, code_suffix)
# Check for concurrent activities # Check for concurrent activities
...@@ -100,14 +101,14 @@ class InstanceActivity(ActivityModel): ...@@ -100,14 +101,14 @@ class InstanceActivity(ActivityModel):
activity_code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix) activity_code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix)
act = cls(activity_code=activity_code, instance=instance, parent=None, act = cls(activity_code=activity_code, instance=instance, parent=None,
resultant_state=None, started=timezone.now(), resultant_state=resultant_state, started=timezone.now(),
readable_name_data=readable_name.to_dict(), readable_name_data=readable_name.to_dict(),
task_uuid=task_uuid, user=user) task_uuid=task_uuid, user=user)
act.save() act.save()
return act return act
def create_sub(self, code_suffix, task_uuid=None, concurrency_check=True, def create_sub(self, code_suffix, task_uuid=None, concurrency_check=True,
readable_name=None): readable_name=None, resultant_state=None):
readable_name = _normalize_readable_name(readable_name, code_suffix) readable_name = _normalize_readable_name(readable_name, code_suffix)
# Check for concurrent activities # Check for concurrent activities
...@@ -117,7 +118,8 @@ class InstanceActivity(ActivityModel): ...@@ -117,7 +118,8 @@ class InstanceActivity(ActivityModel):
act = InstanceActivity( act = InstanceActivity(
activity_code=join_activity_code(self.activity_code, code_suffix), activity_code=join_activity_code(self.activity_code, code_suffix),
instance=self.instance, parent=self, resultant_state=None, instance=self.instance, parent=self,
resultant_state=resultant_state,
readable_name_data=readable_name.to_dict(), started=timezone.now(), readable_name_data=readable_name.to_dict(), started=timezone.now(),
task_uuid=task_uuid, user=self.user) task_uuid=task_uuid, user=self.user)
act.save() act.save()
...@@ -194,14 +196,15 @@ class InstanceActivity(ActivityModel): ...@@ -194,14 +196,15 @@ class InstanceActivity(ActivityModel):
@contextmanager @contextmanager
def instance_activity(code_suffix, instance, on_abort=None, on_commit=None, def instance_activity(code_suffix, instance, on_abort=None, on_commit=None,
task_uuid=None, user=None, concurrency_check=True, task_uuid=None, user=None, concurrency_check=True,
readable_name=None): readable_name=None, resultant_state=None):
"""Create a transactional context for an instance activity. """Create a transactional context for an instance activity.
""" """
if not readable_name: if not readable_name:
warn("Set readable_name", stacklevel=3) warn("Set readable_name", stacklevel=3)
act = InstanceActivity.create(code_suffix, instance, task_uuid, user, act = InstanceActivity.create(code_suffix, instance, task_uuid, user,
concurrency_check, concurrency_check,
readable_name=readable_name) readable_name=readable_name,
resultant_state=resultant_state)
return activitycontextimpl(act, on_abort=on_abort, on_commit=on_commit) return activitycontextimpl(act, on_abort=on_abort, on_commit=on_commit)
......
...@@ -203,6 +203,9 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel): ...@@ -203,6 +203,9 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
for disk in self.disks.all(): for disk in self.disks.all():
disk.destroy() disk.destroy()
def get_running_instances(self):
return Instance.active.filter(template=self, status="RUNNING")
class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
TimeStampedModel): TimeStampedModel):
......
...@@ -51,6 +51,7 @@ class InstanceOperation(Operation): ...@@ -51,6 +51,7 @@ class InstanceOperation(Operation):
concurrency_check = True concurrency_check = True
accept_states = None accept_states = None
deny_states = None deny_states = None
resultant_state = None
def __init__(self, instance): def __init__(self, instance):
super(InstanceOperation, self).__init__(subject=instance) super(InstanceOperation, self).__init__(subject=instance)
...@@ -95,12 +96,14 @@ class InstanceOperation(Operation): ...@@ -95,12 +96,14 @@ class InstanceOperation(Operation):
"provided as parameter.") "provided as parameter.")
return parent.create_sub(code_suffix=self.activity_code_suffix, return parent.create_sub(code_suffix=self.activity_code_suffix,
readable_name=name) readable_name=name,
resultant_state=self.resultant_state)
else: else:
return InstanceActivity.create( return InstanceActivity.create(
code_suffix=self.activity_code_suffix, instance=self.instance, code_suffix=self.activity_code_suffix, instance=self.instance,
readable_name=name, user=user, readable_name=name, user=user,
concurrency_check=self.concurrency_check) concurrency_check=self.concurrency_check,
resultant_state=self.resultant_state)
def is_preferred(self): def is_preferred(self):
"""If this is the recommended op in the current state of the instance. """If this is the recommended op in the current state of the instance.
...@@ -246,15 +249,13 @@ class DeployOperation(InstanceOperation): ...@@ -246,15 +249,13 @@ class DeployOperation(InstanceOperation):
"and network configuration).") "and network configuration).")
required_perms = () required_perms = ()
deny_states = ('SUSPENDED', 'RUNNING') deny_states = ('SUSPENDED', 'RUNNING')
resultant_state = 'RUNNING'
def is_preferred(self): def is_preferred(self):
return self.instance.status in (self.instance.STATUS.STOPPED, return self.instance.status in (self.instance.STATUS.STOPPED,
self.instance.STATUS.PENDING, self.instance.STATUS.PENDING,
self.instance.STATUS.ERROR) self.instance.STATUS.ERROR)
def on_commit(self, activity):
activity.resultant_state = 'RUNNING'
def _operation(self, activity, timeout=15): def _operation(self, activity, timeout=15):
# Allocate VNC port and host node # Allocate VNC port and host node
self.instance.allocate_vnc_port() self.instance.allocate_vnc_port()
...@@ -301,9 +302,7 @@ class DestroyOperation(InstanceOperation): ...@@ -301,9 +302,7 @@ class DestroyOperation(InstanceOperation):
description = _("Permanently destroy virtual machine, its network " description = _("Permanently destroy virtual machine, its network "
"settings and disks.") "settings and disks.")
required_perms = () required_perms = ()
resultant_state = 'DESTROYED'
def on_commit(self, activity):
activity.resultant_state = 'DESTROYED'
def _operation(self, activity): def _operation(self, activity):
# Destroy networks # Destroy networks
...@@ -599,9 +598,7 @@ class ShutdownOperation(InstanceOperation): ...@@ -599,9 +598,7 @@ class ShutdownOperation(InstanceOperation):
abortable = True abortable = True
required_perms = () required_perms = ()
accept_states = ('RUNNING', ) accept_states = ('RUNNING', )
resultant_state = 'STOPPED'
def on_commit(self, activity):
activity.resultant_state = 'STOPPED'
def _operation(self, task=None): def _operation(self, task=None):
self.instance.shutdown_vm(task=task) self.instance.shutdown_vm(task=task)
...@@ -625,9 +622,7 @@ class ShutOffOperation(InstanceOperation): ...@@ -625,9 +622,7 @@ class ShutOffOperation(InstanceOperation):
"of a physical machine.") "of a physical machine.")
required_perms = () required_perms = ()
accept_states = ('RUNNING', ) accept_states = ('RUNNING', )
resultant_state = 'STOPPED'
def on_commit(self, activity):
activity.resultant_state = 'STOPPED'
def _operation(self, activity): def _operation(self, activity):
# Shutdown networks # Shutdown networks
...@@ -660,6 +655,7 @@ class SleepOperation(InstanceOperation): ...@@ -660,6 +655,7 @@ class SleepOperation(InstanceOperation):
"storage resources, and keep network resources allocated.") "storage resources, and keep network resources allocated.")
required_perms = () required_perms = ()
accept_states = ('RUNNING', ) accept_states = ('RUNNING', )
resultant_state = 'SUSPENDED'
def is_preferred(self): def is_preferred(self):
return (not self.instance.is_base and return (not self.instance.is_base and
...@@ -671,9 +667,6 @@ class SleepOperation(InstanceOperation): ...@@ -671,9 +667,6 @@ class SleepOperation(InstanceOperation):
else: else:
activity.resultant_state = 'ERROR' activity.resultant_state = 'ERROR'
def on_commit(self, activity):
activity.resultant_state = 'SUSPENDED'
def _operation(self, activity, timeout=240): def _operation(self, activity, timeout=240):
# Destroy networks # Destroy networks
with activity.sub_activity('shutdown_net', readable_name=ugettext_noop( with activity.sub_activity('shutdown_net', readable_name=ugettext_noop(
...@@ -702,6 +695,7 @@ class WakeUpOperation(InstanceOperation): ...@@ -702,6 +695,7 @@ class WakeUpOperation(InstanceOperation):
"virtual machine from this state.") "virtual machine from this state.")
required_perms = () required_perms = ()
accept_states = ('SUSPENDED', ) accept_states = ('SUSPENDED', )
resultant_state = 'RUNNING'
def is_preferred(self): def is_preferred(self):
return self.instance.status == self.instance.STATUS.SUSPENDED return self.instance.status == self.instance.STATUS.SUSPENDED
...@@ -709,9 +703,6 @@ class WakeUpOperation(InstanceOperation): ...@@ -709,9 +703,6 @@ class WakeUpOperation(InstanceOperation):
def on_abort(self, activity, error): def on_abort(self, activity, error):
activity.resultant_state = 'ERROR' activity.resultant_state = 'ERROR'
def on_commit(self, activity):
activity.resultant_state = 'RUNNING'
def _operation(self, activity, timeout=60): def _operation(self, activity, timeout=60):
# Schedule vm # Schedule vm
self.instance.allocate_vnc_port() self.instance.allocate_vnc_port()
...@@ -882,6 +873,7 @@ class RecoverOperation(InstanceOperation): ...@@ -882,6 +873,7 @@ class RecoverOperation(InstanceOperation):
acl_level = "owner" acl_level = "owner"
required_perms = ('vm.recover', ) required_perms = ('vm.recover', )
accept_states = ('DESTROYED', ) accept_states = ('DESTROYED', )
resultant_state = 'PENDING'
def check_precond(self): def check_precond(self):
try: try:
...@@ -889,9 +881,6 @@ class RecoverOperation(InstanceOperation): ...@@ -889,9 +881,6 @@ class RecoverOperation(InstanceOperation):
except Instance.InstanceDestroyedError: except Instance.InstanceDestroyedError:
pass pass
def on_commit(self, activity):
activity.resultant_state = 'PENDING'
def _operation(self): def _operation(self):
for disk in self.instance.disks.all(): for disk in self.instance.disks.all():
disk.destroyed = None disk.destroyed = None
......
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