Commit 96fe17ce by Kálmán Viktor

Merge branch 'master' into feature-new-tour

Conflicts:
	circle/dashboard/static/dashboard/dashboard.css
	circle/dashboard/urls.py
parents 2fee48c3 efdebdff
......@@ -368,7 +368,7 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
from shutilwhich import which
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
INSTALLED_APPS += (
INSTALLED_APPS += (
'djangosaml2',
)
AUTHENTICATION_BACKENDS = (
......@@ -465,3 +465,6 @@ SESSION_COOKIE_NAME = "csessid%x" % (((getnode() // 139) ^
(getnode() % 983)) & 0xffff)
MAX_NODE_RAM = get_env_variable("MAX_NODE_RAM", 1024)
# Url to download the client: (e.g. http://circlecloud.org/client/download/)
CLIENT_DOWNLOAD_URL = get_env_variable('CLIENT_DOWNLOAD_URL', 'http://circlecloud.org/client/download/')
......@@ -488,7 +488,7 @@ class HumanReadableException(HumanReadableObject, Exception):
"Level should be the name of an attribute of django."
"contrib.messages (and it should be callable with "
"(request, message)). Like 'error', 'warning'.")
else:
elif not hasattr(self, "level"):
self.level = "error"
def send_message(self, request, level=None):
......
......@@ -1383,7 +1383,6 @@
"time_of_suspend": null,
"ram_size": 200,
"priority": 10,
"active_since": null,
"template": null,
"access_method": "nx",
"lease": 1,
......@@ -1413,7 +1412,6 @@
"time_of_suspend": null,
"ram_size": 200,
"priority": 10,
"active_since": null,
"template": null,
"access_method": "nx",
"lease": 1,
......
......@@ -143,25 +143,46 @@ class VmCustomizeForm(forms.Form):
self.template = kwargs.pop("template", None)
super(VmCustomizeForm, self).__init__(*args, **kwargs)
# set displayed disk and network list
self.fields['disks'].queryset = self.template.disks.all()
self.fields['networks'].queryset = Vlan.get_objects_with_level(
'user', self.user)
# set initial for disk and network list
self.initial['disks'] = self.template.disks.all()
self.initial['networks'] = InterfaceTemplate.objects.filter(
template=self.template).values_list("vlan", flat=True)
if self.user.has_perm("vm_set_resouces"):
self.allowed_fields = tuple(self.fields.keys())
# set displayed disk and network list
self.fields['disks'].queryset = self.template.disks.all()
self.fields['networks'].queryset = Vlan.get_objects_with_level(
'user', self.user)
# set initial for disk and network list
self.initial['disks'] = self.template.disks.all()
self.initial['networks'] = InterfaceTemplate.objects.filter(
template=self.template).values_list("vlan", flat=True)
# set initial for resources
self.initial['cpu_priority'] = self.template.priority
self.initial['cpu_count'] = self.template.num_cores
self.initial['ram_size'] = self.template.ram_size
# set initial for resources
self.initial['cpu_priority'] = self.template.priority
self.initial['cpu_count'] = self.template.num_cores
self.initial['ram_size'] = self.template.ram_size
else:
self.allowed_fields = ("name", "template", "customized", )
# initial name and template pk
self.initial['name'] = self.template.name
self.initial['template'] = self.template.pk
self.initial['customized'] = self.template.pk
self.initial['customized'] = True
def _clean_fields(self):
for name, field in self.fields.items():
if name in self.allowed_fields:
value = field.widget.value_from_datadict(
self.data, self.files, self.add_prefix(name))
try:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self._errors[name] = self.error_class(e.messages)
if name in self.cleaned_data:
del self.cleaned_data[name]
class GroupCreateForm(forms.ModelForm):
......
......@@ -48,7 +48,7 @@ from common.models import HumanReadableObject, create_readable, Encoder
from vm.tasks.agent_tasks import add_keys, del_keys
from vm.models.instance import ACCESS_METHODS
from .store_api import Store, NoStoreException, NotOkException
from .store_api import Store, NoStoreException, NotOkException, Timeout
from .validators import connect_command_template_validator
logger = getLogger(__name__)
......@@ -201,6 +201,11 @@ class Profile(Model):
def __unicode__(self):
return self.get_display_name()
def save(self, *args, **kwargs):
if self.org_id == "":
self.org_id = None
super(Profile, self).save(*args, **kwargs)
class Meta:
permissions = (
('use_autocomplete', _('Can use autocomplete.')),
......@@ -341,7 +346,7 @@ def update_store_profile(sender, **kwargs):
profile.disk_quota)
except NoStoreException:
logger.debug("Store is not available.")
except NotOkException:
except (NotOkException, Timeout):
logger.critical("Store is not accepting connections.")
......
......@@ -964,3 +964,7 @@ textarea[name="list-new-namelist"] {
#vm-details-start-template-tour {
margin-right: 5px;
}
#vm-activity-state {
margin-bottom: 15px;
}
......@@ -244,7 +244,7 @@ $(function () {
var search_result = []
var html = '';
for(var i in my_vms) {
if(my_vms[i].name.indexOf(input) != -1) {
if(my_vms[i].name.indexOf(input) != -1 || my_vms[i].host.indexOf(input) != -1) {
search_result.push(my_vms[i]);
}
}
......@@ -383,6 +383,19 @@ $(function () {
$('.notification-messages').load("/dashboard/notifications/");
$('#notification-button a span[class*="badge-pulse"]').remove();
});
/* on the client confirmation button fire the clientInstalledAction */
$(document).on("click", "#client-check-button", function(event) {
var connectUri = $('#connect-uri').val();
clientInstalledAction(connectUri);
return false;
});
$("#dashboard-vm-details-connect-button").click(function(event) {
var connectUri = $(this).attr("href");
clientInstalledAction(connectUri);
return false;
});
});
function generateVmHTML(pk, name, host, icon, _status, fav, is_last) {
......@@ -589,6 +602,12 @@ function addModalConfirmation(func, data) {
});
}
function clientInstalledAction(location) {
setCookie('downloaded_client', true, 365 * 24 * 60 * 60, "/");
window.location.href = location;
$('#confirmation-modal').modal("hide");
}
// for AJAX calls
/**
* Getter for user cookies
......@@ -611,6 +630,14 @@ function getCookie(name) {
return cookieValue;
}
function setCookie(name, value, seconds, path) {
if (seconds!=null) {
var today = new Date();
var expire = new Date();
expire.setTime(today.getTime() + seconds);
}
document.cookie = name+"="+escape(value)+"; expires="+expire.toUTCString()+"; path="+path;
}
/* no js compatibility */
function noJS() {
......
......@@ -222,6 +222,8 @@ function vmCustomizeLoaded() {
$(this).find("i").prop("class", "fa fa-spinner fa-spin");
if($("#create-modal")) return true;
$.ajax({
url: '/dashboard/vm/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
......
......@@ -396,8 +396,14 @@ function checkNewActivity(runs) {
}
$("#vm-details-state span").html(data['human_readable_status'].toUpperCase());
if(data['status'] == "RUNNING") {
if(data['connect_uri']) {
$("#dashboard-vm-details-connect-button").removeClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled");
} else {
if(data['connect_uri']) {
$("#dashboard-vm-details-connect-button").addClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled");
}
......
......@@ -115,7 +115,7 @@ $(function() {
$('#confirmation-modal').modal("hide");
updateStatuses(1);
/* if there are messages display them */
if(data.messages && data.messages.length > 0) {
addMessage(data.messages.join("<br />"), "danger");
......@@ -123,7 +123,7 @@ $(function() {
},
error: function(xhr, textStatus, error) {
$('#confirmation-modal').modal("hide");
if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger");
} else {
......@@ -145,7 +145,7 @@ $(function() {
// this didn't work ;;
// var th = $("this").find("th");
$(".table-sorting").hide();
$(".vm-list-table thead th i").remove();
var icon_html = '<i class="fa fa-sort-' + (data.direction == "desc" ? "desc" : "asc") + ' pull-right"></i>';
......@@ -160,13 +160,13 @@ $(function() {
});
$(document).on("click", ".mass-migrate-node", function() {
$(this).find('input[type="radio"]').prop("checked", true);
$(this).find('input[type="radio"]').prop("checked", true);
});
if(checkStatusUpdate() || $("#vm-list-table tbody tr").length >= 100) {
updateStatuses(1);
}
});
......@@ -209,10 +209,10 @@ function updateStatuses(runs) {
$(this).remove();
}
});
if(checkStatusUpdate()) {
setTimeout(
function() {updateStatuses(runs + 1)},
function() {updateStatuses(runs + 1)},
1000 + Math.exp(runs * 0.05)
);
}
......@@ -239,7 +239,7 @@ function collectIds(rows) {
for(var i = 0; i < rows.length; i++) {
ids.push(rows[i].vm);
}
return ids;
return ids;
}
function setRowColor(row) {
......
......@@ -7,6 +7,7 @@ from datetime import datetime
from django.http import Http404
from django.conf import settings
from requests import get, post, codes
from requests.exceptions import Timeout # noqa
from sizefield.utils import filesizeformat
logger = logging.getLogger(__name__)
......
......@@ -222,6 +222,7 @@ class LeaseListTable(Table):
fields = ('name', 'suspend_interval_seconds',
'delete_interval_seconds', )
prefix = "lease-"
empty_text = _("No available leases.")
class UserKeyListTable(Table):
......
......@@ -12,7 +12,7 @@
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ STATIC_URL }}/template.css">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
......
{% load i18n %}
<p>
{% blocktrans %}
To effortlessly connect to all kind of virtual machines you have to install the <strong>CIRCLE Client</strong>.
{% endblocktrans %}
</p>
<p class="text-info">
{% blocktrans %}
To install the <strong>CIRCLE Client</strong> click on the <strong>Download the Client</strong> button.
The button takes you to the installation detail page, where you can choose your operating system and start
the download or read more detailed information about the <strong>Client</strong>. The program can be installed on Windows XP (and above)
or Debian based Linux operating systems. To successfully install the client you have to have admin (root or elevated) rights.
After the installation complete clicking on the <strong>I have the Client installed</strong> button will launch the appropriate tool
designed for that connection with necessarily predefined configurations. This option will also save your answer and this prompt about
installation will not pop up again.
{% endblocktrans %}
</p>
<br>
<div class="pull-right">
<form method="POST" id="dashboard-client-check" action="">
{% csrf_token %}
<a class="btn btn-default" href="{% url "dashboard.views.detail" pk=instance.pk %}" data-dismiss="modal">{% trans "Cancel" %}</a>
<a class="btn btn-info" href="{{ client_download_url }}" traget="_blank">{% trans "Download the Client" %}</a>
<button data-dismiss="modal" id="client-check-button" type="submit" class="btn btn-success" title="{% trans "I downloaded and installed the client and I want to connect using it. This choice will be saved to your compuer" %}">
<i class="fa fa-external-link"></i> {% trans "I have the Client installed" %}
</button>
<input id="connect-uri" name="connect-uri" type="hidden" value="{% if instance.get_connect_uri %}{{ instance.get_connect_uri}}{% endif %}" />
<input name="vm" type="hidden" value="{% if instance.get_connect_uri %}{{ instance.pk}}{% endif %}" />
</form>
</div>
\ No newline at end of file
......@@ -3,10 +3,11 @@
{% load sizefieldtags %}
{% include "display-form-errors.html" with form=vm_create_form %}
<form method="POST">
<form method="POST" action="{% url "dashboard.views.vm-create" %}">
{% csrf_token %}
{{ vm_create_form.template }}
{{ vm_create_form.customized }}
<div class="row">
<div class="col-sm-12">
......@@ -23,7 +24,6 @@
</div>
{% if perms.vm.set_resources %}
{{ vm_create_form.customized }}
<div class="row">
<div class="col-sm-10">
<div class="form-group">
......
......@@ -10,8 +10,9 @@
</h3>
</div>
<div class="panel-body">
{% blocktrans with owner=instance.owner fqdn=instance.primary_host %}
{{ owner }} offered to take the ownership of virtual machine {{fqdn}}.
{% blocktrans with owner=instance.owner name=instance.name id=instance.id%}
<strong>{{ owner }}</strong> offered to take the ownership of
virtual machine <strong>{{name}} ({{id}})</strong>.
Do you accept the responsility of being the host's owner?
{% endblocktrans %}
<div class="pull-right">
......
<a data-group-pk="{{ record.pk }}"
class="btn btn-danger btn-xs real-link group-delete"
<a data-group-pk="{{ record.pk }}"
class="btn btn-danger btn-xs real-link group-delete"
href="{% url "dashboard.views.delete-group" pk=record.pk %}?next={{ request.path }}">
<i class="fa fa-trash-o"></i>
</a>
......@@ -3,10 +3,10 @@
<div class="panel-heading">
<div class="pull-right toolbar">
<div class="btn-group">
<a href="#index-graph-view" data-index-box="vm" class="btn btn-default btn-xs"
<a href="#index-graph-view" data-index-box="vm" class="btn btn-default btn-xs"
data-container="body"
title="{% trans "summary view" %}"><i class="fa fa-dashboard"></i></a>
<a href="#index-list-view" data-index-box="vm" class="btn btn-default btn-xs disabled"
<a href="#index-list-view" data-index-box="vm" class="btn btn-default btn-xs disabled"
data-container="body"
title="{% trans "list view" %}"><i class="fa fa-list"></i></a>
</div>
......@@ -25,7 +25,7 @@
<i class="fa {{ i.get_status_icon }}" title="{{ i.get_status_display }}"></i>
{{ i.name }}
</span>
<small class="text-muted"> {{ i.primary_host.hostname }}</small>
<small class="text-muted"> {{ i.short_hostname }}</small>
<div class="pull-right dashboard-vm-favourite" data-vm="{{ i.pk }}">
{% if i.fav %}
<i class="fa fa-star text-primary title-favourite" title="{% trans "Unfavourite" %}"></i>
......@@ -50,7 +50,7 @@
<div class="row">
<form action="{% url "dashboard.views.vm-list" %}" method="GET" id="dashboard-vm-search-form">
<div class="col-sm-6 col-xs-6 input-group input-group-sm">
<input id="dashboard-vm-search-input" type="text" class="form-control" name="s"
<input id="dashboard-vm-search-input" type="text" class="form-control" name="s"
placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary"><i class="fa fa-search"></i></button>
......
......@@ -10,9 +10,9 @@
</h1>
</div>
<div class="row">
<div class="col-md-5" id="vm-info-pane">
<div class="big">
<span id="vm-activity-state" class="label label-{% if object.get_status_id == 'wait' %}info{% else %}{% if object.succeeded %}success{% else %}error{% endif %}{% endif %}">
<div class="col-md-6" id="vm-info-pane">
<div class="big" id="vm-activity-state">
<span class="label label-{% if object.get_status_id == 'wait' %}info{% else %}{% if object.succeeded %}success{% else %}danger{% endif %}{% endif %}">
<span>{{ object.get_status_id|upper }}</span>
</span>
</div>
......@@ -20,7 +20,7 @@
{% include "dashboard/vm-detail/_activity-timeline.html" with active=object %}
</div>
<div class="col-md-7">
<div class="col-md-6">
<div class="panel panel-default">
<!--<div class="panel-heading"><h2 class="panel-title">{% trans "Activity" %}</h2></div> -->
<div class="panel-body">
......
......@@ -10,6 +10,12 @@
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
{% if request.user.is_superuser %}
<a href="{{ login_token }}"
class="pull-right btn btn-danger btn-xs"
title="{% trans "Log in as this user. Recommended to open in an incognito window." %}">
{% trans "Login as this user" %}</a>
{% endif %}
<a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.index" %}">{% trans "Back" %}</a>
<h3 class="no-margin">
<i class="fa fa-user"></i>
......
......@@ -23,7 +23,7 @@
{{ search_form.s }}
<div class="input-group-btn">
{{ search_form.stype }}
<button type="submit" class="btn btn-primary input-tags">
<button type="submit" class="btn btn-primary input-tags">
<i class="fa fa-search"></i>
</button>
</div>
......@@ -39,12 +39,14 @@
</div>
</div>
{% if show_lease_table %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
{% if perms.vm.create_leases %}
<a href="{% url "dashboard.views.lease-create" %}" class="pull-right btn btn-success btn-xs" style="margin-right: 10px;">
<a href="{% url "dashboard.views.lease-create" %}"
class="pull-right btn btn-success btn-xs" style="margin-right: 10px;">
<i class="fa fa-plus"></i> {% trans "new lease" %}
</a>
{% endif %}
......@@ -71,6 +73,7 @@
</div>
{% endcomment %}
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
......
......@@ -144,8 +144,21 @@
<i class="fa fa-copy" title="{% trans "Select all" %}"></i>
</span>
</div>
{% endfor %}
{% if instance.get_connect_uri %}
<div id="dashboard-vm-details-connect" class="operation-wrapper">
{% if client_download %}
<a id="dashboard-vm-details-connect-button" class="btn btn-xs btn-default operation " href="{{ instance.get_connect_uri}}" title="{% trans "Connect via the CIRCLE Client" %}">
<i class="fa fa-external-link"></i> {% trans "Connect" %}
</a>
<a href="{% url "dashboard.views.client-check" %}?vm={{ instance.pk }}">{% trans "Download client" %}</a>
{% else %}
<a id="dashboard-vm-details-connect-download-button" class="btn btn-xs btn-default operation " href="{% url "dashboard.views.client-check" %}?vm={{ instance.pk }}" title="{% trans "Download the CIRCLE Client" %}">
<i class="fa fa-external-link"></i> {% trans "Connect (download client)" %}
</a>
{% endif %}
</div>
{% endif %}
</div>
<div class="col-md-8" id="vm-detail-pane">
<div class="panel panel-default" id="vm-detail-panel">
......
......@@ -21,7 +21,7 @@
<strong>{% trans "Group actions" %}</strong>
<button id="vm-list-group-select-all" class="btn btn-info btn-xs">{% trans "Select all" %}</button>
{% for o in ops %}
<a href="{{ o.get_url }}" class="btn btn-xs btn-{{ o.effect }} mass-operation"
<a href="{{ o.get_url }}" class="btn btn-xs btn-{{ o.effect }} mass-operation"
title="{{ o.name|capfirst }}" disabled>
<i class="fa fa-{{ o.icon }}"></i>
</a>
......@@ -34,12 +34,12 @@
<div class="input-group-btn">
{{ search_form.stype }}
</div>
<label class="input-group-addon input-tags" title="{% trans "Include deleted VMs" %}"
<label class="input-group-addon input-tags" title="{% trans "Include deleted VMs" %}"
id="vm-list-search-checkbox-span" data-container="body">
{{ search_form.include_deleted }}
</label>
<div class="input-group-btn">
<button type="submit" class="btn btn-primary input-tags">
<button type="submit" class="btn btn-primary input-tags">
<i class="fa fa-search"></i>
</button>
</div>
......
{% spaceless %}
{% load django_tables2 %}
{% load i18n %}
{% if table.page %}
<div class="table-container">
{% endif %}
{% block table %}
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
{% nospaceless %}
{% block table.thead %}
<thead>
<tr>
{% for column in table.columns %}
{% if column.orderable %}
<th {{ column.attrs.th.as_html }}><a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header }}</a></th>
{% else %}
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
{% endblock table.thead %}
{% block table.tbody %}
<tbody>
{% for row in table.page.object_list|default:table.rows %} {# support pagination #}
{% block table.tbody.row %}
<tr class="{{ forloop.counter|divisibleby:2|yesno:"even,odd" }}"> {# avoid cycle for Django 1.2-1.6 compatibility #}
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>
{% endfor %}
</tr>
{% endblock table.tbody.row %}
{% empty %}
{% if table.empty_text %}
{% block table.tbody.empty_text %}
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
{% endblock table.tbody.empty_text %}
{% endif %}
{% endfor %}
</tbody>
{% endblock table.tbody %}
{% block table.tfoot %}
<tfoot></tfoot>
{% endblock table.tfoot %}
{% endnospaceless %}
</table>
{% endblock table %}
{% if table.page %}
</div>
{% endif %}
{% endspaceless %}
......@@ -46,6 +46,7 @@ from .views import (
GroupPermissionsView,
LeaseAclUpdateView,
toggle_template_tutorial,
ClientCheck, TokenLogin,
)
autocomplete_light.autodiscover()
......@@ -207,4 +208,8 @@ urlpatterns = patterns(
name="dashboard.views.store-new-directory"),
url(r"^store/refresh_toplist$", store_refresh_toplist,
name="dashboard.views.store-refresh-toplist"),
url(r"^client/check$", ClientCheck.as_view(),
name="dashboard.views.client-check"),
url(r'^token-login/(?P<token>.*)/$', TokenLogin.as_view(),
name="dashboard.views.token-login"),
)
......@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-09-03 12:57+0200\n"
"POT-Creation-Date: 2014-09-03 18:49+0200\n"
"PO-Revision-Date: 2014-09-03 12:51+0200\n"
"Last-Translator: Mate Ory <ory.mate@ik.bme.hu>\n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n"
......
......@@ -18,26 +18,37 @@
from logging import getLogger
from django.db.models import Sum
from django.utils.translation import ugettext_noop
from common.models import HumanReadableException
logger = getLogger(__name__)
class NotEnoughMemoryException(Exception):
class SchedulerError(HumanReadableException):
admin_message = None
def __init__(self, message=None):
if message is None:
message = "No node has enough memory to accomodate the guest."
def __init__(self, params=None, level=None, **kwargs):
kwargs.update(params or {})
super(SchedulerError, self).__init__(
level, self.message, self.admin_message or self.message,
kwargs)
Exception.__init__(self, message)
class NotEnoughMemoryException(SchedulerError):
message = ugettext_noop(
"The resources required for launching the virtual machine are not "
"available currently. Please try again later.")
class TraitsUnsatisfiableException(Exception):
admin_message = ugettext_noop(
"The required free memory for launching the virtual machine is not "
"available on any usable node currently. Please try again later.")
def __init__(self, message=None):
if message is None:
message = "No node can satisfy all required traits of the guest."
Exception.__init__(self, message)
class TraitsUnsatisfiableException(SchedulerError):
message = ugettext_noop(
"No node can satisfy the required traits of the "
"new vitual machine currently.")
def select_node(instance, nodes):
......@@ -77,19 +88,27 @@ def has_enough_ram(ram_size, node):
"""True, if the node has enough memory to accomodate a guest requiring
ram_size mebibytes of memory; otherwise, false.
"""
ram_size = ram_size * 1024 * 1024
try:
total = node.ram_size
used = (node.ram_usage / 100) * total
used = node.byte_ram_usage
unused = total - used
overcommit = node.ram_size_with_overcommit
reserved = node.instance_set.aggregate(r=Sum('ram_size'))['r'] or 0
reserved = (node.instance_set.aggregate(
r=Sum('ram_size'))['r'] or 0) * 1024 * 1024
free = overcommit - reserved
return ram_size < unused and ram_size < free
retval = ram_size < unused and ram_size < free
logger.debug('has_enough_ram(%d, %s)=%s (total=%s unused=%s'
' overcommit=%s free=%s free_ok=%s overcommit_ok=%s)',
ram_size, node, retval, total, unused, overcommit, free,
ram_size < unused, ram_size < free)
return retval
except TypeError as e:
logger.warning('Got incorrect monitoring data for node %s. %s',
unicode(node), unicode(e))
logger.exception('Got incorrect monitoring data for node %s. %s',
unicode(node), unicode(e))
return False
......
......@@ -6,3 +6,8 @@
text-align: center;
}
#host-detail-records-table td:first-child,
#host-detail-records-table th:first-child {
text-align: center;
width: 60px;
}
......@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.utils.translation import ugettext_lazy as _
from django_tables2 import Table, A
from django_tables2.columns import LinkColumn, TemplateColumn
......@@ -181,3 +183,20 @@ class VlanGroupTable(Table):
attrs = {'class': 'table table-striped table-condensed'}
fields = ('name', 'vlans', 'description', 'owner', )
order_by = 'name'
class HostRecordsTable(Table):
fqdn = LinkColumn(
"network.record", args=[A("pk")],
order_by=("name", ),
)
class Meta:
model = Record
attrs = {
'class': "table table-striped table-bordered",
'id': "host-detail-records-table",
}
fields = ("type", "fqdn")
order_by = ("name", )
empty_text = _("No records.")
{% load i18n %}
{% load l10n %}
{# <span style="color: #FF0000;">[{{ record.r_type }}]</span> #}
{% if record.direction == "1" %}
{% if record.direction == "in" %}
{{ record.foreign_network }}
[{% for v in record.foreign_network.vlans.all %}
{{ v.name }}{% if not forloop.last %},{% endif %}
{% endfor %}]
{% else %}
{% if record.r_type == "host" %}
{{ record.host.get_fqdn }}
......@@ -11,10 +13,10 @@
{{ record.r_type }}
{% endif %}
{% endif %}
{#<span style="color: #0000FF;"> ▸ </span>#}
<i class="fa fa-arrow-right"></i>
{% if record.direction == "0" %}
{% if record.direction == "out" %}
{{ record.foreign_network }}
{% else %}
{% if record.r_type == "host" %}
......@@ -28,11 +30,16 @@
{% endif %}
{% if record.extra %}
<span class="label label-default">{{ record.extra }}</span>
<span class="label label-default">{{ record.extra }}</span>
{% endif %}
{% if record.nat %}
<span class="label label-success">NAT
[ {{ record.dport }} <i class="fa fa-arrow-right"></i>
{{record.nat_external_port}} ]</span>
<span class="label label-success">NAT
[
{{record.nat_external_port}}
<i class="fa fa-arrow-right"></i>
{{ record.dport }}
]
{{ record.proto|upper }}
</span>
{% endif %}
......@@ -12,10 +12,10 @@
</div>
<div class="row">
<div class="col-sm-7">
<div class="col-md-6">
{% crispy form %}
</div>
<div class="col-sm-5">
<div class="col-md-6">
<div class="page-header">
<a href="{% url "network.rule_create" %}?host={{ host_pk }}" class="btn btn-success pull-right btn-xs"><i class="fa fa-plus-circle"></i> {% trans "Add new rule" %}</a>
<h3>{% trans "Rules" %}</h3>
......@@ -45,7 +45,7 @@
{% endif %}
<div class="page-header">
<h3>Add host group</h3>
<h3>{% trans "Add host group" %}</h3>
</div>
{% if not_used_groups|length == 0 %}
No more groups to add!
......@@ -64,7 +64,12 @@
</div><!-- input-group -->
</form>
{% endif %}
</div><!-- col-sm-4 -->
<div class="page-header">
<h3>{% trans "Records" %}</h3>
</div>
{% render_table records_table %}
</div><!-- col-sm-5 -->
</div><!-- row -->
{% endblock %}
......
......@@ -399,6 +399,12 @@ class HostDetail(LoginRequiredMixin, SuperuserRequiredMixin,
# set host pk (we need this for URL-s)
context['host_pk'] = self.kwargs['pk']
from network.tables import HostRecordsTable
context['records_table'] = HostRecordsTable(
Record.objects.filter(host=self.get_object()),
request=self.request, template="django_tables2/table_no_page.html"
)
return context
def get_success_url(self):
......
......@@ -11,10 +11,6 @@
body {
margin-top: 40px;
}
.container {
width: 600px;
}
ul, li {
list-style: none;
margin: 0;
......@@ -22,6 +18,8 @@
}
.container > .content {
max-width: 570px;
margin: auto;
background-color: #fff;
padding: 20px;
-webkit-border-radius: 10px 10px 10px 10px;
......@@ -38,8 +36,10 @@
}
.login-form {
margin-top: 40px;
margin: 20px auto 0 auto;
padding: 0 10px;
max-width: 250px;
}
.login-form form {
......@@ -79,3 +79,10 @@
<img src="{{ STATIC_URL}}dashboard/img/logo.png" style="height: 25px;"/>
</a>
{% endblock %}
{% block content %}
<div class="content">
{% block content_box %}{% endblock %}
</div>
{% endblock %}
......@@ -11,15 +11,14 @@
</a>
{% endblock %}
{% block content %}
<div class="content">
{% block content_box %}
<div class="row">
{% if form.password.errors or form.username.errors %}
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
{% endif %}
<div class="col-sm-{% if saml2 %}6{% else %}12{% endif %}">
<div class="col-xs-{% if saml2 %}6{% else %}12{% endif %}">
<div class="login-form">
<form action="" method="POST">
{% csrf_token %}
......@@ -28,8 +27,8 @@
</div>
</div>
{% if saml2 %}
<div class="col-sm-6">
<h4 style="padding-top: 0; margin-top: 0;">{% trans "Login with SSO" %}</h4>
<div class="col-xs-6">
<h4 style="padding-top: 0; margin-top: 20px;">{% trans "Login with SSO" %}</h4>
<a href="{% url "saml2_login" %}">{% trans "Click here!" %}</a>
</div>
{% endif %}
......
......@@ -5,17 +5,15 @@
{% block title-page %}{% trans "Password reset complete" %}{% endblock %}
{% block content %}
<div class="content">
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div class="alert alert-success">
{% trans "Password change successful!" %}
<a href="{% url "accounts.login" %}">{% trans "Click here to login" %}</a>
</div>
{% block content_box %}
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div class="alert alert-success">
{% trans "Password change successful!" %}
<a href="{% url "accounts.login" %}">{% trans "Click here to login" %}</a>
</div>
</div>
</div>
......
......@@ -5,26 +5,26 @@
{% block title-page %}{% trans "Password reset confirm" %}{% endblock %}
{% block content %}
<div>
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
{% block content_box %}
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div>
{% blocktrans %}
Please enter your new password twice so we can verify you typed it in correctly.
{% endblocktrans %}
</div>
<div class="col-sm-12">
<div style="margin: 0 0 25px 0;">
{% blocktrans %}Please enter your new password twice so we can verify you typed it in correctly!{% endblocktrans %}
</div>
{% if form %}
{% crispy form %}
{% else %}
<div class="alert alert-warning">
{% url "accounts.password-reset" as url %}
{% blocktrans with url=url %}This token is expired, please <a href="{{ url }}">request</a> a new password reset link again!{% endblocktrans %}
</div>
{% endif %}
</div>
{% if form %}
{% crispy form %}
{% else %}
<div class="alert alert-warning">
{% url "accounts.password-reset" as url %}
{% blocktrans with url=url %}This token is expired, please <a href="{{ url }}">request</a> a new password reset link again.{% endblocktrans %}
</div>
{% endif %}
</div>
</div>
{% endblock %}
......@@ -5,16 +5,14 @@
{% block title-page %}{% trans "Password reset done" %}{% endblock %}
{% block content %}
<div class="content">
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div class="pull-right"><a href="{% url "accounts.login" %}">{% trans "Back to login" %}</a></div>
{% trans "We have sent you an email about your next steps!" %}
</div>
{% block content_box %}
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div class="pull-right"><a href="{% url "accounts.login" %}">{% trans "Back to login" %}</a></div>
{% trans "We have sent you an email about your next steps." %}
</div>
</div>
{% endblock %}
......@@ -5,20 +5,20 @@
{% block title-page %}{% trans "Password reset" %}{% endblock %}
{% block content %}
<div class="content">
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div class="pull-right"><a href="{% url "accounts.login" %}">{% trans "Back to login" %}</a></div>
<h4 style="margin: 0 0 25px 0;">{% blocktrans %}Enter your email address to reset your password!{% endblocktrans %}</h4>
<form action="" method="POST">
{% csrf_token %}
{% crispy form %}
</form>
</div>
{% block content_box %}
<div class="row">
<div class="login-form-errors">
{% include "display-form-errors.html" %}
</div>
<div class="col-sm-12">
<div class="pull-right"><a href="{% url "accounts.login" %}">{% trans "Back to login" %}</a></div>
<h4 style="margin: 0 0 25px 0;">
{% blocktrans %}Enter your email address to reset your password.{% endblocktrans %}
</h4>
<form action="" method="POST">
{% csrf_token %}
{% crispy form %}
</form>
</div>
</div>
{% endblock %}
......@@ -244,10 +244,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
verbose_name=_('time of delete'),
help_text=_("Proposed time of automatic "
"deletion."))
active_since = DateTimeField(blank=True, null=True,
help_text=_("Time stamp of successful "
"boot report."),
verbose_name=_('active since'))
node = ForeignKey(Node, blank=True, null=True,
related_name='instance_set',
help_text=_("Current hypervisor of this instance."),
......@@ -532,15 +528,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
return self.primary_host.mac if self.primary_host else None
@property
def uptime(self):
"""Uptime of the instance.
"""
if self.active_since:
return timezone.now() - self.active_since
else:
return timedelta() # zero
@property
def os_type(self):
"""Get the type of the instance's operating system.
"""
......@@ -549,13 +536,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
else:
return self.template.os_type
def get_age(self):
"""Deprecated. Use uptime instead.
Get age of VM in seconds.
"""
return self.uptime.seconds
@property
def waiting(self):
"""Indicates whether the instance's waiting for an operation to finish.
......@@ -607,14 +587,19 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
port = self.get_connect_port(use_ipv6=use_ipv6)
host = self.get_connect_host(use_ipv6=use_ipv6)
proto = self.access_method
if proto == 'ssh':
proto = 'sshterm'
return ('%(proto)s:cloud:%(pw)s:%(host)s:%(port)d' %
return ('circle:%(proto)s:cloud:%(pw)s:%(host)s:%(port)d' %
{'port': port, 'proto': proto, 'pw': self.pw,
'host': host})
except:
return
@property
def short_hostname(self):
try:
return self.primary_host.hostname
except AttributeError:
return self.vm_name
def get_vm_desc(self):
"""Serialize Instance object to vmdriver.
"""
......
......@@ -34,6 +34,7 @@ from common.models import (
create_readable, humanize_exception, HumanReadableException
)
from common.operations import Operation, register_operation
from manager.scheduler import SchedulerError
from .tasks.local_tasks import (
abortable_async_instance_operation, abortable_async_node_operation,
)
......@@ -620,6 +621,17 @@ class ShutdownOperation(InstanceOperation):
self.instance.yield_node()
self.instance.yield_vnc_port()
def on_abort(self, activity, error):
if isinstance(error, TimeLimitExceeded):
activity.result = humanize_exception(ugettext_noop(
"The virtual machine did not switch off in the provided time "
"limit. Most of the time this is caused by incorrect ACPI "
"settings. You can also try to power off the machine from the "
"operating system manually."), error)
activity.resultant_state = None
else:
super(ShutdownOperation, self).on_abort(activity, error)
register_operation(ShutdownOperation)
......@@ -717,7 +729,10 @@ class WakeUpOperation(InstanceOperation):
return self.instance.status == self.instance.STATUS.SUSPENDED
def on_abort(self, activity, error):
activity.resultant_state = 'ERROR'
if isinstance(error, SchedulerError):
activity.resultant_state = None
else:
activity.resultant_state = 'ERROR'
def _operation(self, activity, timeout=60):
# Schedule vm
......
......@@ -46,7 +46,7 @@ def send_init_commands(instance, act):
with act.sub_activity('set_hostname',
readable_name=ugettext_noop('set hostname')):
set_hostname.apply_async(
queue=queue, args=(vm, instance.primary_host.hostname))
queue=queue, args=(vm, instance.short_hostname))
def send_networking_commands(instance, act):
......
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