Commit f5eb9edb by Kálmán Viktor

Merge branch 'master' into issue-397

parents 925cd76e ff91b0ff
......@@ -15,4 +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 .test_acl import TestModel, Test2Model # noqa
from django.conf import settings
# https://code.djangoproject.com/ticket/7835
if settings.SETTINGS_MODULE == 'circle.settings.test':
from .test_acl import TestModel, Test2Model # noqa
......@@ -355,6 +355,7 @@ LOCAL_APPS = (
'manager',
'acl',
'monitor',
'request',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
......@@ -449,7 +450,7 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'djangosaml2.backends.Saml2Backend',
'common.backends.Saml2Backend',
)
remote_metadata = join(SITE_ROOT, 'remote_metadata.xml')
......@@ -527,6 +528,10 @@ except:
LOCALE_PATHS = (join(SITE_ROOT, 'locale'), )
COMPANY_NAME = get_env_variable("COMPANY_NAME", "BME IK 2015")
first, last = get_env_variable(
'VNC_PORT_RANGE', '20000, 65536').replace(' ', '').split(',')
VNC_PORT_RANGE = (int(first), int(last)) # inclusive start, exclusive end
graphite_host = environ.get("GRAPHITE_HOST", None)
graphite_port = environ.get("GRAPHITE_PORT", None)
if graphite_host and graphite_port:
......@@ -555,3 +560,4 @@ ADMIN_ENABLED = False
BLACKLIST_PASSWORD = get_env_variable("BLACKLIST_PASSWORD", "")
BLACKLIST_HOOK_URL = get_env_variable("BLACKLIST_HOOK_URL", "")
REQUEST_HOOK_URL = get_env_variable("REQUEST_HOOK_URL", "")
......@@ -40,7 +40,8 @@ INSTALLED_APPS += (
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
path_to_selenium_test = os.path.expanduser('~/circle/circle/dashboard/tests/selenium')
path_to_selenium_test = os.path.join(SITE_ROOT, "dashboard/tests/selenium")
NOSE_ARGS = ['--stop', '--with-doctest', '--with-selenium-driver', '--selenium-driver=firefox', '-w%s' % path_to_selenium_test]
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
......
......@@ -56,6 +56,16 @@ LOGGING['handlers']['console'] = {'level': level,
'formatter': 'simple'}
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': level}
# don't print SQL queries
LOGGING['handlers']['null'] = {'level': "DEBUG",
'class': "django.utils.log.NullHandler"}
LOGGING['loggers']['django.db.backends'] = {
'handlers': ['null'],
'propagate': False,
'level': 'DEBUG',
}
# Forbid store usage
STORE_URL = ""
......
......@@ -38,6 +38,7 @@ urlpatterns = patterns(
url(r'^network/', include('network.urls')),
url(r'^blacklist-add/', add_blacklist_item),
url(r'^dashboard/', include('dashboard.urls')),
url(r'^request/', include('request.urls')),
# django/contrib/auth/urls.py (care when new version)
url((r'^accounts/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/'
......@@ -87,3 +88,4 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
)
handler500 = 'common.views.handler500'
handler403 = 'common.views.handler403'
# -*- coding: utf-8 -*-
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import re
from djangosaml2.backends import Saml2Backend as Saml2BackendBase
class Saml2Backend(Saml2BackendBase):
u"""
>>> b = Saml2Backend()
>>> b.clean_user_main_attribute(u'Ékezetes Enikő')
u'+00c9kezetes+0020Enik+0151'
>>> b.clean_user_main_attribute(u'Cé++')
u'C+00e9+002b+002b'
>>> b.clean_user_main_attribute(u'test')
u'test'
>>> b.clean_user_main_attribute(u'3+4')
u'3+002b4'
"""
def clean_user_main_attribute(self, main_attribute):
def replace(match):
match = match.group()
return '+%04x' % ord(match)
if isinstance(main_attribute, str):
main_attribute = main_attribute.decode('UTF-8')
assert isinstance(main_attribute, unicode)
return re.sub(r'[^\w.@-]', replace, main_attribute)
def _set_attribute(self, obj, attr, value):
if attr == 'username':
value = self.clean_user_main_attribute(value)
return super(Saml2Backend, self)._set_attribute(obj, attr, value)
......@@ -170,8 +170,8 @@ class Operation(object):
raise ImproperlyConfigured(
"Set required_perms to () if none needed.")
if not user.has_perms(cls.required_perms):
raise PermissionDenied("%s doesn't have the required permissions."
% user)
raise PermissionDenied(
u"%s doesn't have the required permissions." % user)
if cls.superuser_required and not user.is_superuser:
raise humanize_exception(ugettext_noop(
"Superuser privileges are required."), PermissionDenied())
......
......@@ -19,32 +19,42 @@ from sys import exc_info
import logging
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.template import RequestContext
from .models import HumanReadableException
logger = logging.getLogger(__name__)
def handler500(request):
cls, exception, traceback = exc_info()
logger.exception("unhandled exception")
def get_context(request, exception):
ctx = {}
if isinstance(exception, HumanReadableException):
if issubclass(exception.__class__, HumanReadableException):
try:
ctx['error'] = exception.get_user_text()
except:
pass
else:
try:
if request.user.is_superuser():
if request.user.is_superuser:
ctx['error'] = exception.get_admin_text()
else:
ctx['error'] = exception.get_user_text()
except:
pass
return ctx
def handler500(request):
cls, exception, traceback = exc_info()
logger.exception("unhandled exception")
ctx = get_context(request, exception)
try:
resp = render_to_response("500.html", ctx, RequestContext(request))
except:
resp = render_to_response("500.html", ctx)
resp.status_code = 500
return resp
def handler403(request):
cls, exception, traceback = exc_info()
ctx = get_context(request, exception)
resp = render_to_response("403.html", ctx)
resp.status_code = 403
return resp
......@@ -1395,6 +1395,7 @@
"vnc_port": 1234,
"num_cores": 2,
"status": "RUNNING",
"system": "system pls",
"modified": "2013-10-14T07:27:38.192Z"
}
},
......
......@@ -739,6 +739,7 @@ class LeaseForm(forms.ModelForm):
class Meta:
model = Lease
exclude = ()
class VmRenewForm(OperationForm):
......@@ -1604,6 +1605,7 @@ class DataStoreForm(ModelForm):
class Meta:
model = DataStore
fields = ("name", "path", "hostname", )
class DiskForm(ModelForm):
......@@ -1620,3 +1622,5 @@ class DiskForm(ModelForm):
class Meta:
model = Disk
fields = ("name", "filename", "datastore", "type", "bus", "size",
"base", "dev_num", "destroyed", "is_ready", )
......@@ -309,7 +309,7 @@ if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
attributes = kwargs.pop('attributes')
atr = settings.SAML_ORG_ID_ATTRIBUTE
try:
value = attributes[atr][0]
value = attributes[atr][0].upper()
except Exception as e:
value = None
logger.info("save_org_id couldn't find attribute. %s", unicode(e))
......@@ -339,7 +339,7 @@ if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
group, unicode(g))
g.user_set.add(sender)
for i in FutureMember.objects.filter(org_id=value):
for i in FutureMember.objects.filter(org_id__iexact=value):
i.group.user_set.add(sender)
i.delete()
......
......@@ -1272,8 +1272,46 @@ textarea[name="new_members"] {
margin-top: 20px;
}
#vm-renew-request-lease, #vm-request-resource-form {
display: none;
}
.label-100 {
display: block;
width: 100%;
}
#modify-the-resources {
font-size: 18px;
display: none;
}
#vm-request-resource-form textarea {
max-width: 500px;
height: 150px;
}
#disk-list-table {
td:last-child {
text-align: center;
}
}
#request-buttons {
form {
display: inline;
}
textarea {
resize: none;
min-height: 80px;
}
}
.nowrap {
white-space: nowrap;
}
.little-margin-bottom {
margin-bottom: 5px;
}
......@@ -38,6 +38,13 @@ $(function() {
e.preventDefault();
});
/* save as (close vnc console) */
$('.operation-save_as_template').click(function(e) {
if ($('li.active > a[href$="console"]').length > 0) {
$('a[data-toggle$="pill"][href$="#activity"]').click();
}
});
/* remove tag */
$('.vm-details-remove-tag').click(function() {
var to_remove = $.trim($(this).parent('div').text());
......@@ -223,4 +230,25 @@ $(function() {
return false;
});
$(document).on("click", "#vm-renew-request-lease-button", function(e) {
$("#vm-renew-request-lease").stop().slideToggle();
e.preventDefault();
});
$("#vm-request-resource").click(function(e) {
$(".cpu-priority-slider, .cpu-count-slider, .ram-slider").simpleSlider("setDisabled", false);
$(".ram-input, .cpu-count-input, .cpu-priority-input").prop("disabled", false);
$("#vm-details-resources-form").prop("action", $(this).prop("href"));
$("#vm-request-resource-form").show();
$("#modify-the-resources").show();
$(this).hide();
$("html, body").animate({
scrollTop: $("#modify-the-resources").offset().top - 60
});
return e.preventDefault();
});
});
......@@ -79,10 +79,26 @@
</div>
</div>
{% empty %}
{% if not template_access_types %}
{% trans "You can't start new virtual machines because no templates are shared with you." %}
{% else %}
{% trans "You can't start new virtual machines because no templates are shared with you however you can request them via the form below." %}
<hr />
{% include "request/_request-template-form.html" %}
{% endif %}
{% endfor %}
</div>
{% if templates and template_access_types %}
{% url "request.views.request-template" as request_url %}
<hr />
<p class="text-right">
{% blocktrans with url=request_url %}
Need other templates? Submit a new <a href="{{ url }}">request</a>.
{% endblocktrans %}
</p>
{% endif %}
<style>
.progress {
position: relative;
......
{% extends "dashboard/operate.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block formbuttons %}
<div class="pull-right">
<a class="btn btn-default" href="{{object.get_absolute_url}}" data-dismiss="modal">
{% trans "Cancel" %}
</a>
{% if lease_types %}
<a class="btn btn-primary" id="vm-renew-request-lease-button"
href="{% url "request.views.request-lease" vm_pk=object.pk %}">
<i class="fa fa-forward"></i>
{% trans "Request longer lease" %}
</a>
{% endif %}
<button class="btn btn-{{ opview.effect }} btn-op-form-send" type="submit" id="op-form-send">
{% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ op.name|capfirst }}
</button>
</div>
{% endblock %}
{% block extra %}
<div class="clearfix"></div>
<div id="vm-renew-request-lease">
<hr />
{% include "request/_request-lease-form.html" with form=lease_request_form vm=object %}
</div>
{% endblock %}
......@@ -22,16 +22,29 @@
{% if user.is_superuser %}
{% if ADMIN_ENABLED %}
<li>
<a href="/admin/"><i class="fa fa-cogs"></i> {% trans "Admin" %}</a>
<a href="/admin/">
<i class="fa fa-cogs"></i>
<span class="hidden-sm">{% trans "Admin" %}</span>
</a>
</li>
{% endif %}
<li>
<a href="{% url "dashboard.views.storage" %}"><i class="fa fa-database"></i>
{% trans "Storage" %}
<a href="{% url "dashboard.views.storage" %}">
<i class="fa fa-database"></i>
<span class="hidden-sm">{% trans "Storage" %}</span>
</a>
</li>
<li>
<a href="/network/"><i class="fa fa-globe"></i> {% trans "Network" %}</a>
<a href="{% url "network.index" %}">
<i class="fa fa-globe"></i>
<span class="hidden-sm">{% trans "Network" %}</span>
</a>
</li>
<li>
<a href="{% url "request.views.request-list" %}">
<i class="fa fa-phone"></i>
<span class="hidden-sm">{% trans "Requests" %}</span>
</a>
</li>
{% endif %}
<li>
......
......@@ -54,7 +54,7 @@
<dt>{% trans "result" %}</dt>
<dd><textarea class="form-control">{{object.result|get_text:user}}</textarea></dd>
<dd><textarea class="form-control" id="activity_result_text">{{object.result|get_text:user}}</textarea></dd>
<dt>{% trans "resultant state" %}</dt>
<dd>{{object.resultant_state|default:'n/a'}}</dd>
......
......@@ -16,6 +16,7 @@ Do you want to perform the following operation on
{% crispy form %}
{% endif %}
{% endblock %}
{% block formbuttons %}
<div class="pull-right">
<a class="btn btn-default" href="{{object.get_absolute_url}}"
data-dismiss="modal">{% trans "Cancel" %}</a>
......@@ -23,4 +24,7 @@ Do you want to perform the following operation on
{% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ op.name|capfirst }}
</button>
</div>
{% endblock %}
</form>
{% block extra %}{% endblock %}
......@@ -2,6 +2,7 @@
{% load staticfiles %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load arrowfilter %}
{% block title-page %}{{ profile.username}} | {% trans "Profile" %}{% endblock %}
......@@ -42,6 +43,7 @@
{% trans "Email address" %}: {{ profile.email }}
{% endif %}
</p>
<p>{% trans "Last login" %}: <span title="{{ request.user.last_login }}">{{ request.user.last_login|arrowfilter:LANGUAGE_CODE}}</span></p>
{% if request.user == profile %}
<p>
{% trans "Use email address as Gravatar profile image" %}:
......
......@@ -5,7 +5,7 @@
{% for a in activities %}
<div class="activity{% if a.pk == active.pk %} activity-active{%endif%}"
data-activity-id="{{ a.pk }}" data-activity-code="{{ a.activity_code }}">
data-activity-id="{{ a.pk }}" data-activity-code="{{ a.activity_code }}" data-timestamp="{{ a.started|date:"U" }}">
<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-{{a.icon}}{% endif %}"></i>
</span>
......
......@@ -59,10 +59,11 @@
{% if instance.is_expiring %}<i class="fa fa-warning-sign text-danger"></i>{% endif %}
<span id="vm-details-renew-op">
{% with op=op.renew %}{% if op %}
<a href="{{op.get_url}}" class="btn btn-success btn-xs
<a href="{{op.get_url}}" class="btn btn-{{op.effect}} btn-xs
operation operation-{{op.op}}">
<i class="fa fa-{{op.icon}}"></i>
{{op.name}} </a>
{{op.name}}
</a>
{% endif %}{% endwith %}
</span>
</h4>
......
......@@ -2,6 +2,10 @@
{% load sizefieldtags %}
{% load crispy_forms_tags %}
<div class="label label-info label-100" id="modify-the-resources">
{% trans "Modify the resources" %}
</div>
<form method="POST" action="{{ op.resources_change.get_url }}" id="vm-details-resources-form">
{% csrf_token %}
{% include "dashboard/_resources-sliders.html" with field_priority=resources_form.priority field_num_cores=resources_form.num_cores field_ram_size=resources_form.ram_size %}
......@@ -9,12 +13,31 @@
{% if op.resources_change %}
<button type="submit" class="btn btn-success btn-sm change-resources-button"
id="vm-details-resources-save" data-vm="{{ instance.pk }}"
{% if op.resources_change.disabled %}disabled{% endif %}>
{% if not save_resources_enabled %}disabled{% endif %}>
<i class="fa fa-floppy-o"></i> {% trans "Save resources" %}
</button>
<span class="change-resources-help"
{% if not op.resources_change.disabled %}style="display: none;"{% endif %}
>{% trans "Stop your VM to change resources." %}</span>
{% if save_resources_enabled %}style="display: none;"{% endif %}>
{% trans "Stop your VM to change resources." %}
</span>
{% else %}
<div id="vm-request-resource-form">
<div class="alert alert-info text-justify">
{% trans "Changing resources is only possible on virtual machines with STOPPED state. We suggest to turn off the VM after submitting the request otherwise it will be automatically stopped in the future when the request is accepted." %}
</div>
<div class="form-group">
<label>{% trans "Message" %}*</label>
<textarea class="form-control" name="message">{% include "request/initials/resources.html" %}</textarea>
</div>
<input type="submit" class="btn btn-success btn-sm"/>
</div>
<a href="{% url "request.views.request-resource" vm_pk=object.pk %}"
class="btn btn-primary btn-sm" id="vm-request-resource">
<i class="fa fa-tasks"></i>
{% trans "Request resources" %}
</a>
{% endif %}
</form>
......
......@@ -16,546 +16,78 @@
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from selenose.cases import SeleniumTestCase
from django.contrib.auth.models import User
from datetime import datetime
import logging
from sys import _getframe
import random
import urlparse
import re
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.by import By
from datetime import datetime
from selenium.common.exceptions import NoSuchElementException
random_pass = "".join([random.choice(
'0123456789abcdefghijklmnopqrstvwxyz') for n in xrange(10)])
random_accents = random_pass + "".join([random.choice(
u"áéíöóúűÁÉÍÖÓÜÚŰ") for n in xrange(5)])
wait_max_sec = 10
host = 'https:127.0.0.1'
client_name = 'test_%s' % random_accents
class UtilityMixin(object):
def login(self, username, password='password', location=None):
driver = self.driver
if location is None:
location = '/dashboard/'
driver.get('%s%s' % (host, location))
# Only if we aren't logged in already
if location not in urlparse.urlparse(self.driver.current_url).path:
try:
name_input = driver.find_element_by_id("id_username")
password_input = driver.find_element_by_id("id_password")
submit_input = driver.find_element_by_id("submit-id-submit")
except:
inputs = driver.find_elements_by_tag_name("input")
for current_input in inputs:
input_type = current_input.get_attribute("type")
if input_type == "text":
name_input = current_input
if input_type == "password":
password_input = current_input
if input_type == "submit":
submit_input = current_input
try:
name_input.clear()
name_input.send_keys(username)
password_input.clear()
password_input.send_keys(password)
submit_input.click()
try:
# If selenium runs only in a small (virtual) screen
driver.find_element_by_class_name('navbar-toggle').click()
WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href*='/dashboard/profile/']")))
except:
time.sleep(0.5)
except:
raise Exception('Selenium cannot find the form controls')
def list_options(self, select):
try:
option_dic = {}
select = Select(select)
for option in select.options:
key = option.get_attribute('value')
if key is not None and key:
option_dic[key] = [option.text]
return option_dic
except:
raise Exception(
'Selenium cannot list the select possibilities')
def select_option(self, select, what=None):
"""
From an HTML select imput type try to choose the specified one.
Select is a selenium web element type. What represent both the
text of the option and it's ID.
"""
try:
my_choice = None
options = self.list_options(select)
select = Select(select)
if what is not None:
for key, value in options.iteritems():
if what in key:
my_choice = key
else:
if isinstance(value, list):
for single_value in value:
if what in single_value:
my_choice = key
else:
if what in value:
my_choice = key
if my_choice is None:
my_choose_list = options.keys()
my_choice = my_choose_list[random.randint(
0, len(my_choose_list) - 1)]
select.select_by_value(my_choice)
except:
raise Exception(
'Selenium cannot select the chosen one')
def get_link_by_href(self, target_href, attributes=None):
try:
links = self.driver.find_elements_by_tag_name('a')
for link in links:
href = link.get_attribute('href')
if href is not None and href:
if target_href in href:
perfect_fit = True
if isinstance(attributes, dict):
for key, target_value in attributes.iteritems():
attr_check = link.get_attribute(key)
if attr_check is not None and attr_check:
if target_value not in attr_check:
perfect_fit = False
if perfect_fit:
return link
except:
raise Exception(
'Selenium cannot find the href=%s link' % target_href)
def click_on_link(self, link):
"""
There are situations when selenium built in click() function
doesn't work as intended, that's when this function is used.
Fires a click event via javascript injection.
"""
try:
# Javascript function to simulate a click on a link
javascript = (
"var link = arguments[0];"
"var cancelled = false;"
"if(document.createEvent) {"
" var event = document.createEvent(\"MouseEvents\");"
" event.initMouseEvent("
" \"click\", true, true, window, 0, 0, 0, 0, 0,"
" false,false,false,false,0,null);"
" cancelled = !link.dispatchEvent(event);"
"} else if(link.fireEvent) {"
" cancelled = !link.fireEvent(\"onclick\");"
"} if (!cancelled) {"
" window.location = link.href;"
"}")
self.driver.execute_script(javascript, link)
except:
raise Exception(
'Selenium cannot inject javascript to the page')
def wait_and_accept_operation(self, argument=None):
"""
Accepts the operation confirmation pop up window.
Fills out the text inputs before accepting if argument is given.
"""
try:
accept = WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CLASS_NAME, "modal-accept")))
if argument is not None:
possible = self.driver.find_elements_by_css_selector(
"div.controls > input[type='text']")
if isinstance(argument, list):
for x in range(0, len(possible)):
possible[x].clear()
possible[x].send_keys(argument[x % len(argument)])
else:
for form in possible:
form.clear()
form.send_keys(argument)
accept.click()
except:
raise Exception("Selenium cannot accept the"
" operation confirmation")
def save_template_from_vm(self, name):
try:
WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href$='/op/deploy/']")))
self.click_on_link(self.get_link_by_href("/op/deploy/"))
self.wait_and_accept_operation()
recent_deploy = self.recently(self.get_timeline_elements(
"vm.Instance.deploy"))
if not self.check_operation_result(recent_deploy):
print ("Selenium cannot deploy the "
"chosen template virtual machine")
raise Exception('Cannot deploy the virtual machine')
self.click_on_link(WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href$='/op/shut_off/']"))))
self.wait_and_accept_operation()
recent_shut_off = self.recently(self.get_timeline_elements(
"vm.Instance.shut_off"))
if not self.check_operation_result(recent_shut_off):
print ("Selenium cannot shut off the "
"chosen template virtual machine")
raise Exception('Cannot shut off the virtual machine')
self.click_on_link(WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href$='/op/save_as_template/']"))))
self.wait_and_accept_operation(name)
return name
except:
raise Exception(
'Selenium cannot save a vm as a template')
def create_base_template(self, name=None, architecture="x86-64",
method=None, op_system=None, lease=None,
network="vm"):
if name is None:
name = "template_new_%s" % client_name
if op_system is None:
op_system = "!os %s" % client_name
try:
self.driver.get('%s/dashboard/template/choose/' % host)
self.driver.find_element_by_css_selector(
"input[type='radio'][value='base_vm']").click()
self.driver.find_element_by_id(
"template-choose-next-button").click()
template_name = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'id_name')))
template_name.clear()
template_name.send_keys(name)
self.select_option(self.driver.find_element_by_id(
"id_arch"), architecture)
self.select_option(self.driver.find_element_by_id(
"id_access_method"), method)
system_name = self.driver.find_element_by_id("id_system")
system_name.clear()
system_name.send_keys(op_system)
self.select_option(self.driver.find_element_by_id(
"id_lease"), lease)
self.select_option(self.driver.find_element_by_id(
"id_networks"), network)
self.driver.find_element_by_css_selector(
"input.btn[type='submit']").click()
return self.save_template_from_vm(name)
except:
raise Exception(
'Selenium cannot create a base template virtual machine')
def get_template_id(self, name=None, from_all=False):
"""
In default settings find all templates ID in the template list.
If name is specified searches that specific template's ID
from_all sets whether to use owned templates or all of them
Returns list of the templates ID
"""
try:
self.driver.get('%s/dashboard/template/list/' % host)
css_selector_of_a_template = ("a[data-original-title]"
"[href*='/dashboard/template/']")
if from_all:
self.select_option(self.driver.find_element_by_id(
'id_stype'), "all")
self.driver.find_element_by_css_selector(
"button[type='submit']").click()
try:
WebDriverWait(self.driver, wait_max_sec).until(
ec.presence_of_element_located((
By.CSS_SELECTOR, css_selector_of_a_template)))
except:
print "Selenium could not locate any templates"
template_table = self.driver.find_element_by_css_selector(
"table[class*='template-list-table']")
templates = template_table.find_elements_by_css_selector("td.name")
found_template_ids = []
for template in templates:
if name is None or name in template.text:
try:
template_link = template.find_element_by_css_selector(
css_selector_of_a_template)
template_id = re.search(
r'\d+',
template_link.get_attribute("outerHTML")).group()
found_template_ids.append(template_id)
print ("Found '%(name)s' template's ID as %(id)s" % {
'name': template.text,
'id': template_id})
except NoSuchElementException:
pass
except:
raise
if not found_template_ids and name is not None:
print ("Selenium could not find the specified "
"%(name)s template in the list" % {
'name': name})
return found_template_ids
except:
raise Exception(
'Selenium cannot found the template\'s id')
def check_operation_result(self, operation_id, restore=True):
"""
Returns wheter the operation_id result is success (returns: boolean)
"""
try:
if restore:
url_base = urlparse.urlparse(self.driver.current_url)
url_save = ("%(host)s%(url)s" % {
'host': host,
'url': urlparse.urljoin(url_base.path, url_base.query)})
if url_base.fragment:
url_save = ("%(url)s#%(fragment)s" % {
'url': url_save,
'fragment': url_base.fragment})
self.driver.get('%(host)s/dashboard/vm/activity/%(id)s/' % {
'host': host,
'id': operation_id})
result = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, "activity_status")))
print ("%(id)s result text is '%(result)s'" % {
'id': operation_id,
'result': result.text})
if (result.text == "success"):
out = True
elif (result.text == "wait"):
time.sleep(2)
out = self.check_operation_result(operation_id, False)
else:
out = False
if restore:
print "Restoring to %s url" % url_save
self.driver.get(url_save)
return out
except:
raise Exception(
'Selenium cannot check the result of an operation')
def recently(self, timeline_dict, second=90):
try:
if isinstance(timeline_dict, dict):
for key, value in timeline_dict.iteritems():
time = datetime.strptime(key, '%Y-%m-%d %H:%M')
delta = datetime.now() - time
if delta.total_seconds() <= second:
return value
except:
raise Exception(
'Selenium cannot filter timeline activities to recent')
def get_timeline_elements(self, code=None):
try:
if code is None:
css_activity_selector = "div[data-activity-code]"
code = "all activity"
else:
css_activity_selector = ("div[data-activity-code="
"'%(code)s']" % {
'code': code})
WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR, "a[href*='#activity']"))).click()
activity_dict = {}
timeline = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, "activity-timeline")))
searched_activity = timeline.find_elements_by_css_selector(
css_activity_selector)
print "Found activity list for %s:" % code
for activity in searched_activity:
activity_id = activity.get_attribute('data-activity-id')
activity_text = activity.text
key = re.search(
r'\d+-\d+-\d+ \d+:\d+,', activity_text).group()[:-1]
print ("%(id)s @ %(activity)s" % {
'id': activity_id,
'activity': key})
activity_dict[key] = activity_id
return activity_dict
except:
raise Exception('Selenium cannot find the searched activity')
def create_template_from_base(self, delete_disk=True, name=None):
try:
if name is None:
name = "template_from_base_%s" % client_name
self.driver.get('%s/dashboard/template/choose/' % host)
choice_list = []
choices = self.driver.find_elements_by_css_selector(
"input[type='radio']")
choice_list = [item for item in choices if (
'test' not in item.get_attribute('value')
and item.get_attribute('value') != 'base_vm')]
chosen = random.randint(0, len(choice_list) - 1)
choice_list[chosen].click()
self.driver.find_element_by_id(
"template-choose-next-button").click()
if delete_disk:
self.click_on_link(
self.get_link_by_href("#resources"))
disks = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'vm-details-resources-disk')))
disk_list = disks.find_elements_by_css_selector(
"h4[class*='list-group-item-heading']")
if len(disk_list) > 0:
self.click_on_link(
self.get_link_by_href("/op/remove_disk/"))
self.wait_and_accept_operation()
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, "_activity")))
recent_remove_disk = self.recently(
self.get_timeline_elements(
"vm.Instance.remove_disk"))
if not self.check_operation_result(recent_remove_disk):
print ("Selenium cannot delete disk "
"of the chosen template")
raise Exception('Cannot delete disk')
return self.save_template_from_vm(name)
except:
raise Exception('Selenium cannot start a template from a base one')
import urlparse
def delete_template(self, template_id):
try:
self.driver.get('%s/dashboard/template/%s/' % (host, template_id))
url = urlparse.urlparse(self.driver.current_url)
self.click_on_link(
self.get_link_by_href(
"/dashboard/template/delete/%s/" % template_id))
self.wait_and_accept_operation()
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.CLASS_NAME, 'alert-success')))
url = urlparse.urlparse(self.driver.current_url)
if "/template/list/" not in url.path:
raise Exception()
except:
raise Exception('Selenium cannot delete the desired template')
from django.contrib.auth.models import User
from django.db.models import Q
def create_random_vm(self):
try:
self.driver.get('%s/dashboard/vm/create/' % host)
vm_list = []
pk = None
vm_list = self.driver.find_elements_by_class_name(
'vm-create-template-summary')
choice = random.randint(0, len(vm_list) - 1)
vm_list[choice].click()
WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CLASS_NAME, 'vm-create-start'))).click()
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.CLASS_NAME, 'alert-success')))
url = urlparse.urlparse(self.driver.current_url)
pk = re.search(r'\d+', url.path).group()
return pk
except:
raise Exception('Selenium cannot start a VM')
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
from selenose.cases import SeleniumTestCase
def view_change(self, target_box):
driver = self.driver
driver.get('%s/dashboard/' % host)
list_view = driver.find_element_by_id('%s-list-view' % target_box)
graph_view = driver.find_element_by_id('%s-graph-view' % target_box)
js_script = 'return arguments[0].style.display;'
required_attributes = {'data-index-box': target_box}
graph_view_link = self.get_link_by_href(
'#index-graph-view',
required_attributes).find_element_by_tag_name('i')
list_view_link = self.get_link_by_href(
'#index-list-view',
required_attributes).find_element_by_tag_name('i')
self.click_on_link(list_view_link)
states = [driver.execute_script(js_script, list_view),
driver.execute_script(js_script, graph_view)]
self.click_on_link(graph_view_link)
states.extend([driver.execute_script(js_script, list_view),
driver.execute_script(js_script, graph_view)])
self.click_on_link(list_view_link)
states.extend([driver.execute_script(js_script, list_view),
driver.execute_script(js_script, graph_view)])
return states
from vm.models import Instance
from .config import SeleniumConfig
from .util import CircleSeleniumMixin
def delete_vm(self, pk):
try:
# For relability reasons instead of using the JS operatation
self.driver.get("%(host)s/dashboard/vm/%(id)s/op/destroy/" % {
'host': host,
'id': pk})
self.wait_and_accept_operation()
try:
status_span = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'vm-details-state')))
WebDriverWait(status_span, wait_max_sec).until(
ec.visibility_of_element_located((
By.CLASS_NAME, 'fa-trash-o')))
except:
# Selenium can time-out by not realising the JS refresh
recent_destroy_vm = self.recently(
self.get_timeline_elements("vm.Instance.destroy"))
if not self.check_operation_result(recent_destroy_vm):
print ("Selenium cannot destroy "
"the chosen %(id)s vm" % {
'id': pk})
raise Exception('Cannot destroy the specified vm')
self.driver.get('%s/dashboard/vm/%s/' % (host, pk))
try:
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.CSS_SELECTOR,
"span[data-status*='DESTROYED']")))
return True
except:
return False
except:
raise Exception("Selenium can not destroy a VM")
conf = SeleniumConfig()
log_formatter = logging.Formatter(conf.log_format)
logger = logging.getLogger(conf.logger_name)
fileHandler = logging.handlers.RotatingFileHandler(
conf.log_file, maxBytes=conf.log_size, backupCount=conf.log_backup)
fileHandler.setFormatter(log_formatter)
fileHandler.setLevel(logging.WARNING)
logger.addHandler(fileHandler)
class VmDetailTest(UtilityMixin, SeleniumTestCase):
class BasicSeleniumTests(SeleniumTestCase, CircleSeleniumMixin):
template_ids = []
vm_ids = []
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
self.conf = conf
@classmethod
def setup_class(cls):
cls._user = User.objects.create(username=client_name,
logger.warning("Selenium test started @ %(time)s" % {
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
if conf.create_user:
logger.warning(
"Creating selenium test user %(name)s:%(password)s" % {
'name': conf.client_name,
'password': conf.random_pass})
cls._user = User.objects.create(username=conf.client_name,
is_superuser=True)
cls._user.set_password(random_accents)
cls._user.set_password(conf.random_pass)
cls._user.save()
@classmethod
def teardown_class(cls):
if conf.create_user:
for instance in Instance.objects.all().filter(
~Q(status=u'DESTROYED'), owner=cls._user):
logger.warning(
"Destroying the test virtual machine: %(id)s" % {
'id': instance.pk})
instance.destroy(system=True)
logger.warning("Deleting test user %(name)s" % {
'name': conf.client_name})
cls._user.delete()
logger.warning("Selenium test finished @ %(time)s" % {
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
def test_01_login(self):
logger.warning("Starting test %s" % _getframe().f_code.co_name)
title = 'Dashboard | CIRCLE'
location = '/dashboard/'
self.login(client_name, random_accents)
self.driver.get('%s/dashboard/' % host)
self.login()
self.driver.get('%s/dashboard/' % conf.host)
url = urlparse.urlparse(self.driver.current_url)
(self.assertIn('%s' % title, self.driver.title,
'%s is not found in the title' % title) or
......@@ -563,62 +95,69 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase):
'URL path is not equal with %s' % location))
def test_02_add_template_rights(self):
self.login(client_name, random_accents)
logger.warning("Starting test %s" % _getframe().f_code.co_name)
self.login()
template_pool = self.get_template_id(from_all=True)
if len(template_pool) > 1:
chosen = template_pool[random.randint(0, len(template_pool) - 1)]
elif len(template_pool) == 1:
chosen = template_pool[0]
else:
print "Selenium did not found any templates"
logger.exception("Selenium did not found any templates")
raise Exception(
"System did not meet required conditions to continue")
self.driver.get('%s/dashboard/template/%s/' % (host, chosen))
acces_form = self.driver.find_element_by_css_selector(
"Selenium did not found any templates")
self.driver.get('%s/dashboard/template/%s/' % (conf.host, chosen))
acces_form_css = (
"form[action*='/dashboard/template/%(template_id)s/acl/']"
"[method='post']" % {
'template_id': chosen})
acces_form = self.driver.find_element_by_css_selector(acces_form_css)
user_name = acces_form.find_element_by_css_selector(
"input[type='text'][id='id_name']")
user_status = acces_form.find_element_by_css_selector(
"select[name='level']")
user_name.clear()
user_name.send_keys(client_name)
self.select_option(user_status)
user_name.send_keys(conf.client_name)
self.select_option(user_status, 'user')
# For strange reasons clicking on submit button doesn't work anymore
acces_form.submit()
found_users = []
acl_users = self.driver.find_elements_by_css_selector(
acces_form = self.driver.find_element_by_css_selector(acces_form_css)
acl_users = acces_form.find_elements_by_css_selector(
"a[href*='/dashboard/profile/']")
for user in acl_users:
user_text = re.split(r':[ ]?', user.text)
if len(user_text) == 2:
found_name = re.search(r'[\w\W]+(?=\))', user_text[1]).group()
print ("'%(user)s' found in ACL list for template %(id)s" % {
logger.warning("'%(user)s' found in ACL "
"list for template %(id)s" % {
'user': found_name,
'id': chosen})
found_users.append(found_name)
self.assertIn(client_name, found_users,
self.assertIn(conf.client_name, found_users,
"Could not add user to template's ACL")
def test_03_able_to_create_template(self):
self.login(client_name, random_accents)
logger.warning("Starting test %s" % _getframe().f_code.co_name)
self.login()
template_list = None
create_template = self.get_link_by_href('/dashboard/template/choose/')
self.click_on_link(create_template)
WebDriverWait(self.driver, wait_max_sec).until(
WebDriverWait(self.driver, conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'confirmation-modal')))
template_list = self.driver.find_elements_by_class_name(
'template-choose-list-element')
print 'Selenium found %s template possibilities' % len(template_list)
logger.warning('Selenium found %(count)s template possibilities' % {
'count': len(template_list)})
(self.assertIsNotNone(
template_list, "Selenium can not find the create template list") or
self.assertGreater(len(template_list), 0,
"The create template list is empty"))
def test_04_create_base_template(self):
self.login(client_name, random_accents)
logger.warning("Starting test %s" % _getframe().f_code.co_name)
self.login()
created_template_id = self.get_template_id(
self.create_base_template())
found = created_template_id is not None
......@@ -629,7 +168,8 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase):
"Could not found the created template in the template list")
def test_05_create_template_from_base(self):
self.login(client_name, random_accents)
logger.warning("Starting test %s" % _getframe().f_code.co_name)
self.login()
created_template_id = self.get_template_id(
self.create_template_from_base())
found = created_template_id is not None
......@@ -640,10 +180,11 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase):
"Could not found the created template in the template list")
def test_06_delete_templates(self):
logger.warning("Starting test %s" % _getframe().f_code.co_name)
success = False
self.login(client_name, random_accents)
self.login()
for template_id in self.template_ids:
print "Deleting template %s" % template_id
logger.warning("Deleting template %s" % template_id)
self.delete_template(template_id)
existing_templates = self.get_template_id()
if len(existing_templates) == 0:
......@@ -658,52 +199,57 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase):
success, "Could not delete (all) the test template(s)")
def test_07_able_to_create_vm(self):
self.login(client_name, random_accents)
logger.warning("Starting test %s" % _getframe().f_code.co_name)
self.login()
vm_list = None
create_vm_link = self.get_link_by_href('/dashboard/vm/create/')
create_vm_link.click()
WebDriverWait(self.driver, wait_max_sec).until(
WebDriverWait(self.driver, conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'confirmation-modal')))
vm_list = self.driver.find_elements_by_class_name(
'vm-create-template-summary')
print ("Selenium found %(vm_number)s virtual machine template "
" possibilities" % {
logger.warning("Selenium found %(vm_number)s virtual machine"
" template possibilities" % {
'vm_number': len(vm_list)})
(self.assertIsNotNone(
vm_list, "Selenium can not find the VM list") or
self.assertGreater(len(vm_list), 0, "The create VM list is empty"))
def test_08_create_vm(self):
self.login(client_name, random_accents)
logger.warning("Starting test %s" % _getframe().f_code.co_name)
self.login()
pk = self.create_random_vm()
self.vm_ids.append(pk)
self.assertIsNotNone(pk, "Can not create a VM")
def test_09_vm_view_change(self):
self.login(client_name, random_accents)
logger.warning("Starting test %s" % _getframe().f_code.co_name)
self.login()
expected_states = ["", "none",
"none", "",
"block", "none"]
states = self.view_change("vm")
print 'states: [%s]' % ', '.join(map(str, states))
print 'expected: [%s]' % ', '.join(map(str, expected_states))
logger.warning('states: [%s]' % ', '.join(map(str, states)))
logger.warning('expected: [%s]' % ', '.join(map(str, expected_states)))
self.assertListEqual(states, expected_states,
"The view mode does not change for VM listing")
def test_10_node_view_change(self):
self.login(client_name, random_accents)
logger.warning("Starting test %s" % _getframe().f_code.co_name)
self.login()
expected_states = ["", "none",
"none", "",
"block", "none"]
states = self.view_change("node")
print 'states: [%s]' % ', '.join(map(str, states))
print 'expected: [%s]' % ', '.join(map(str, expected_states))
logger.warning('states: [%s]' % ', '.join(map(str, states)))
logger.warning('expected: [%s]' % ', '.join(map(str, expected_states)))
self.assertListEqual(states, expected_states,
"The view mode does not change for NODE listing")
def test_11_delete_vm(self):
self.login(client_name, random_accents)
logger.warning("Starting test %s" % _getframe().f_code.co_name)
self.login()
succes = True
for vm in self.vm_ids:
if not self.delete_vm(vm):
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import random
class SeleniumConfig(object):
# How many sec can selenium wait till certain parts of a page appears
wait_max_sec = 10
# How much sec can pass before the activity is no longer happened recently
recently_sec = 90
# Name of the logger (necessary to override test logger)
logger_name = "selenium"
# File where the log should be stored
log_file = "selenium.log"
# Log file max size in Bytes
log_size = 1024 * 1024 * 10
# Format of the log file
log_format = "%(asctime)s: %(name)s: %(levelname)s: %(message)s"
# Backup count of the logfiles
log_backup = 5
# Accented letters from which selenium can choose to name stuff
accents = u"áéíöóúűÁÉÍÖÓÜÚŰ"
# Non accented letters from which selenium can choose to name stuff
valid_chars = "0123456789abcdefghijklmnopqrstvwxyz"
# First we choose 10 random normal letters
random_pass = "".join([random.choice(
valid_chars) for n in xrange(10)])
# Then we append it with 5 random accented one
random_pass += "".join([random.choice(
accents) for n in xrange(5)])
# Then we name our client as test_%(password)s
client_name = 'test_%s' % random_pass
# Which webpage should selenium use (localhost is recommended)
host = 'https://127.0.0.1'
# In default the tests create a new user then delete it afteword
# Disable this if selenium cannot acces the database
create_user = True
"""
Note: It's possible to setup that selenium uses a distant web server
for testing. If you choose this method you must provide a distant superuser
account info for that server by overriding random_pass and client_name by
uncommenting the lines below.
"""
# client_name = "user name here"
# random_pass = "password here"
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime
import inspect
import logging
import random
import re
import time
import urlparse
from selenium.common.exceptions import (
NoSuchElementException, StaleElementReferenceException,
TimeoutException)
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.ui import WebDriverWait
from .config import SeleniumConfig
logger = logging.getLogger(SeleniumConfig.logger_name)
class SeleniumMixin(object):
def create_screenshot(self):
name = 'ss_from_%(caller_name)s.png' % {
'caller_name': inspect.stack()[1][3]}
logger.warning('Creating screenshot "%s"' % name)
self.driver.save_screenshot(name)
def get_url(self, fragment_needed=False, fragment=None):
url_base = urlparse.urlparse(self.driver.current_url)
url_save = ("%(host)s%(url)s" % {
'host': self.conf.host,
'url': urlparse.urljoin(url_base.path, url_base.query)})
if fragment is None:
fragment = url_base.fragment
else:
fragment_needed = True
if fragment_needed and fragment:
url_save = ("%(url)s#%(fragment)s" % {
'url': url_save,
'fragment': fragment})
return url_save
def list_options(self, select):
try:
option_dic = {}
select = Select(select)
for option in select.options:
key = option.get_attribute('value')
if key is not None and key:
option_dic[key] = [option.text]
return option_dic
except:
logger.exception("Selenium cannot list the"
" select possibilities")
self.create_screenshot()
raise Exception(
'Cannot list the select possibilities')
def select_option(self, select, what=None):
"""
From an HTML select imput type try to choose the specified one.
Select is a selenium web element type. What represent both the
text of the option and it's ID.
"""
try:
my_choice = None
options = self.list_options(select)
select = Select(select)
if what is not None:
for key, value in options.iteritems():
if what in key:
my_choice = key
else:
if isinstance(value, list):
for single_value in value:
if what in single_value:
my_choice = key
else:
if what in value:
my_choice = key
if my_choice is None:
my_choose_list = options.keys()
my_choice = my_choose_list[random.randint(
0, len(my_choose_list) - 1)]
select.select_by_value(my_choice)
except:
logger.exception("Selenium cannot select the chosen one")
self.create_screenshot()
raise Exception(
'Cannot select the chosen one')
def get_link_by_href(self, target_href, attributes=None):
try:
links = self.driver.find_elements_by_tag_name('a')
for link in links:
href = link.get_attribute('href')
if href is not None and href:
if target_href in href:
perfect_fit = True
if isinstance(attributes, dict):
for key, target_value in attributes.iteritems():
attr_check = link.get_attribute(key)
if attr_check is not None and attr_check:
if target_value not in attr_check:
perfect_fit = False
if perfect_fit:
return link
except:
logger.exception(
"Selenium cannot find the href=%s link" % target_href)
self.create_screenshot()
raise Exception('Cannot find the requested href')
def click_on_link(self, link):
"""
There are situations when selenium built in click() function
doesn't work as intended, that's when this function is used.
Fires a click event via javascript injection.
"""
try:
# Javascript function to simulate a click on a link
javascript = """
var link = arguments[0];
var cancelled = false;
if(document.createEvent) {
var event = document.createEvent("MouseEvents");
event.initMouseEvent(
"click", true, true, window, 0, 0, 0, 0, 0,
false,false,false,false,0,null);
cancelled = !link.dispatchEvent(event);
} else if(link.fireEvent) {
cancelled = !link.fireEvent("onclick");
} if (!cancelled) {
window.location = link.href;
}"""
self.driver.execute_script(javascript, link)
except:
logger.exception("Selenium cannot inject javascript to the page")
self.create_screenshot()
raise Exception(
'Cannot inject javascript to the page')
def get_text(self, node, tag):
"""
There are some cases where selenium default WebElement text()
method returns less then it actually could contain. Solving that
here is a simple regular expression. Give the closest html element
then specify the html tag of the enclosed text.
"""
text = ""
try:
text_whole = re.search(
r'<%(tag)s[^>]*>([^<]+)</%(tag)s>' % {
'tag': tag},
node.get_attribute("outerHTML")).group()
text_parts = text_whole.splitlines()
for part in text_parts:
if '<' not in part and '>' not in part:
text += part
text = text.replace(" ", "")
except:
return node.text
if len(node.text) >= len(text):
text = node.text
else:
logger.warning("Better text found which is '%s'" % text)
return text.strip()
class CircleSeleniumMixin(SeleniumMixin):
def login(self, location=None):
driver = self.driver
if location is None:
location = '/dashboard/'
driver.get('%s%s' % (self.conf.host, location))
# Only if we aren't logged in already
if location not in urlparse.urlparse(self.driver.current_url).path:
try:
name_input = driver.find_element_by_id("id_username")
password_input = driver.find_element_by_id("id_password")
submit_input = driver.find_element_by_id("submit-id-submit")
except:
inputs = driver.find_elements_by_tag_name("input")
for current_input in inputs:
input_type = current_input.get_attribute("type")
if input_type == "text":
name_input = current_input
if input_type == "password":
password_input = current_input
if input_type == "submit":
submit_input = current_input
try:
name_input.clear()
name_input.send_keys(self.conf.client_name)
password_input.clear()
password_input.send_keys(self.conf.random_pass)
submit_input.click()
try:
# If selenium runs only in a small (virtual) screen
driver.find_element_by_class_name('navbar-toggle').click()
WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href*='/dashboard/profile/']")))
except:
time.sleep(0.5)
except:
logger.exception("Selenium cannot find the form controls")
self.create_screenshot()
raise Exception('Cannot find the form controls')
def fallback(self, fallback_url, fallback_function):
logger.warning(
"However error was anticipated falling back to %(url)s" % {
'url': fallback_url})
self.driver.get(fallback_url)
return fallback_function()
def wait_and_accept_operation(self, argument=None, try_wait=None,
fallback_url=None):
"""
Accepts the operation confirmation pop up window.
Fills out the text inputs before accepting if argument is given.
"""
try:
accept = WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.element_to_be_clickable((
By.CLASS_NAME, "modal-accept")))
if argument is not None:
possible = self.driver.find_elements_by_css_selector(
"div.controls > input[type='text']")
if isinstance(argument, list):
for x in range(0, len(possible)):
possible[x].clear()
possible[x].send_keys(argument[x % len(argument)])
else:
for form in possible:
form.clear()
form.send_keys(argument)
accept.click()
if try_wait is not None:
WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.CSS_SELECTOR, try_wait)))
except TimeoutException:
logger.exception("Selenium cannot accept the"
" operation confirmation")
if fallback_url is not None:
self.fallback(
fallback_url,
lambda: self.wait_and_accept_operation(argument))
else:
self.create_screenshot()
raise Exception(
'Cannot accept the operation confirmation')
except:
logger.exception("Selenium cannot accept the"
" operation confirmation")
if fallback_url is not None:
self.fallback(
fallback_url,
lambda: self.wait_and_accept_operation(argument, try_wait))
else:
self.create_screenshot()
raise Exception(
'Cannot accept the operation confirmation')
def save_template_from_vm(self, name):
try:
WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href$='/op/deploy/']")))
url_save = self.get_url()
self.click_on_link(self.get_link_by_href("/op/deploy/"))
fallback_url = "%sop/deploy/" % url_save
self.wait_and_accept_operation(
try_wait="a[href$='/op/shut_off/']", fallback_url=fallback_url)
recent_deploy = self.recently(self.get_timeline_elements(
"vm.Instance.deploy", url_save))
if not self.check_operation_result(
recent_deploy, "a[href*='#activity']"):
logger.warning("Selenium cannot deploy the "
"chosen template virtual machine")
raise Exception('Cannot deploy the virtual machine')
self.click_on_link(WebDriverWait(
self.driver, self.conf.wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href$='/op/shut_off/']"))))
fallback_url = "%sop/shut_off/" % url_save
self.wait_and_accept_operation(
try_wait="a[href$='/op/deploy/']", fallback_url=fallback_url)
recent_shut_off = self.recently(self.get_timeline_elements(
"vm.Instance.shut_off", url_save))
if not self.check_operation_result(
recent_shut_off, "a[href*='#activity']"):
logger.warning("Selenium cannot shut off the "
"chosen template virtual machine")
raise Exception('Cannot shut off the virtual machine')
self.click_on_link(WebDriverWait(
self.driver, self.conf.wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href$='/op/save_as_template/']"))))
fallback_url = "%sop/save_as_template/" % url_save
self.wait_and_accept_operation(
argument=name, fallback_url=fallback_url)
recent_save_template = self.recently(self.get_timeline_elements(
"vm.Instance.save_as_template", url_save))
if not self.check_operation_result(
recent_save_template, "a[href*='#activity']"):
logger.warning("Selenium cannot save the "
"chosen virtual machine as a template")
raise Exception(
'Cannot save the virtual machine as a template')
logger.warning("Selenium created %(name)s template" % {
'name': name})
return name
except:
logger.exception("Selenium cannot save a vm as a template")
self.create_screenshot()
raise Exception(
'Cannot save a vm as a template')
def create_base_template(self, name=None, architecture="x86-64",
method=None, op_system=None, lease=None,
network="vm"):
if name is None:
name = "new_%s" % self.conf.client_name
if op_system is None:
op_system = "!os %s" % self.conf.client_name
try:
self.driver.get('%s/dashboard/template/choose/' % self.conf.host)
self.driver.find_element_by_css_selector(
"input[type='radio'][value='base_vm']").click()
self.driver.find_element_by_id(
"template-choose-next-button").click()
template_name = WebDriverWait(
self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'id_name')))
template_name.clear()
template_name.send_keys(name)
self.select_option(self.driver.find_element_by_id(
"id_arch"), architecture)
self.select_option(self.driver.find_element_by_id(
"id_access_method"), method)
system_name = self.driver.find_element_by_id("id_system")
system_name.clear()
system_name.send_keys(op_system)
self.select_option(self.driver.find_element_by_id(
"id_lease"), lease)
self.select_option(self.driver.find_element_by_id(
"id_networks"), network)
self.driver.find_element_by_css_selector(
"input.btn[type='submit']").click()
return self.save_template_from_vm(name)
except:
logger.exception("Selenium cannot create a base"
" template virtual machine")
self.create_screenshot()
raise Exception(
'Cannot create a base template virtual machine')
def get_template_id(self, name=None, from_all=False):
"""
In default settings find all templates ID in the template list.
If name is specified searches that specific template's ID
from_all sets whether to use owned templates or all of them
Returns list of the templates ID
"""
try:
self.driver.get('%s/dashboard/template/list/' % self.conf.host)
css_selector_of_a_template = ("a[data-original-title]"
"[href*='/dashboard/template/']")
if from_all:
self.select_option(self.driver.find_element_by_id(
'id_stype'), "all")
self.driver.find_element_by_css_selector(
"button[type='submit']").click()
try:
WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.presence_of_element_located((
By.CSS_SELECTOR, css_selector_of_a_template)))
except:
logger.warning("Selenium could not locate any templates")
raise Exception("Could not locate any templates")
template_table = self.driver.find_element_by_css_selector(
"table[class*='template-list-table']")
templates = template_table.find_elements_by_css_selector("td.name")
found_template_ids = []
for template in templates:
# Little magic to outsmart accented naming errors
template_name = self.get_text(template, "a")
if name is None or name in template_name:
try:
template_link = template.find_element_by_css_selector(
css_selector_of_a_template)
template_id = re.search(
r'\d+',
template_link.get_attribute("outerHTML")).group()
found_template_ids.append(template_id)
logger.warning("Found '%(name)s' "
"template's ID as %(id)s" % {
'name': template_name,
'id': template_id})
except NoSuchElementException:
pass
except:
raise
else:
logger.warning(
"Searching for %(searched)s so"
" %(name)s is dismissed" % {
'searched': name,
'name': template_name})
logger.warning(
"Dismissed template html code: %(code)s" % {
'code': template.get_attribute("outerHTML")})
if not found_template_ids and name is not None:
logger.warning("Selenium could not find the specified "
"%(name)s template in the list" % {
'name': name})
raise Exception("Could not find the specified template")
return found_template_ids
except:
logger.exception('Selenium cannot find the template\'s id')
self.create_screenshot()
raise Exception(
'Cannot find the template\'s id')
def check_operation_result(self, operation_id, restore_selector=None,
restore=True):
"""
Returns wheter the operation_id result is success (returns: boolean)
"""
try:
if restore:
url_save = self.get_url(True)
self.driver.get('%(host)s/dashboard/vm/activity/%(id)s/' % {
'host': self.conf.host,
'id': operation_id})
result = WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, "activity_status")))
logger.warning("%(id)s's result is '%(result)s'" % {
'id': operation_id,
'result': result.text})
if (result.text == "success"):
out = True
elif (result.text == "wait"):
time.sleep(2)
out = self.check_operation_result(
operation_id=operation_id, restore=False)
else:
try:
result_text = WebDriverWait(
self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, "activity_result_text")))
logger.warning(
"%(id)s's result text is: '%(result_text)s'" % {
'id': operation_id,
'result_text': result_text.text})
except:
logger.warning("Cannot read %(id)s's result text" % {
'id': operation_id})
out = False
if restore:
logger.warning("Restoring to %s url" % url_save)
self.driver.get(url_save)
if restore_selector is not None and restore_selector:
WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.CSS_SELECTOR, restore_selector)))
return out
except:
logger.exception("Selenium cannot check the"
" result of an operation")
self.create_screenshot()
raise Exception(
'Cannot check the result of an operation')
def recently(self, timeline_dict, second=None):
if second is None:
second = self.conf.recently_sec
try:
if isinstance(timeline_dict, dict):
recent = None
for key, value in timeline_dict.iteritems():
if recent is None or int(key) > int(recent):
recent = key
if len(timeline_dict) > 1:
logger.warning(
"Searching for most recent activity"
" from the received %(count)s pieces" % {
'count': len(timeline_dict)})
logger.warning("Found at %(id)s @ %(time)s" % {
'id': timeline_dict[recent],
'time': datetime.fromtimestamp(
int(recent)).strftime('%Y-%m-%d %H:%M:%S')})
logger.warning(
"Checking wheter %(id)s started in the"
" recent %(second)s seconds" % {
'id': timeline_dict[recent],
'second': second})
delta = datetime.now() - datetime.fromtimestamp(int(recent))
if delta.total_seconds() <= second:
return timeline_dict[recent]
except:
logger.exception("Selenium cannot filter timeline "
"activities to find most recent")
self.create_screenshot()
raise Exception(
'Cannot filter timeline activities to find most recent')
def get_timeline_elements(self, code=None, fallback_url=None):
try:
if code is None:
css_activity_selector = "div[data-activity-code]"
code_text = "all activity"
else:
code_text = code
css_activity_selector = ("div[data-activity-code="
"'%(code)s']" % {
'code': code})
try:
self.click_on_link(WebDriverWait(
self.driver, self.conf.wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR, "a[href*='#activity']"))))
activity_dict = {}
timeline = WebDriverWait(
self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, "activity-timeline")))
searched_activity = timeline.find_elements_by_css_selector(
css_activity_selector)
logger.warning("Found activity list for %s:" % code_text)
for activity in searched_activity:
activity_id = activity.get_attribute('data-activity-id')
key = activity.get_attribute('data-timestamp')
logger.warning("%(id)s @ %(activity)s" % {
'id': activity_id,
'activity': datetime.fromtimestamp(
int(key)).strftime('%Y-%m-%d %H:%M:%S')})
activity_dict[key] = activity_id
except StaleElementReferenceException:
logger.warning('Timeline changed while processing it')
return self.get_timeline_elements(code, fallback_url)
except TimeoutException:
logger.warning('Can not found timeline in the page')
if fallback_url is not None:
return self.fallback(
fallback_url,
lambda: self.get_timeline_elements(code))
else:
self.create_screenshot()
raise Exception('Selenium could not locate the timeline')
except:
logger.exception('Selenium cannot get timeline elemets')
self.create_screenshot()
raise Exception('Cannot get timeline elements')
if len(activity_dict) == 0:
logger.warning('Found activity list is empty')
self.create_screenshot()
raise Exception('Selenium did not found any activity')
return activity_dict
except:
logger.exception('Selenium cannot find the searched activity')
self.create_screenshot()
raise Exception('Cannot find the searched activity')
def create_template_from_base(self, delete_disk=True, name=None):
try:
if name is None:
name = "from_%s" % self.conf.client_name
self.driver.get('%s/dashboard/template/choose/' % self.conf.host)
choice_list = []
choices = self.driver.find_elements_by_css_selector(
"input[type='radio']")
choice_list = [item for item in choices if (
'test' not in item.get_attribute('value')
and item.get_attribute('value') != 'base_vm')]
chosen = random.randint(0, len(choice_list) - 1)
choice_list[chosen].click()
self.driver.find_element_by_id(
"template-choose-next-button").click()
if delete_disk:
url_save = self.get_url(fragment='activity')
self.click_on_link(
self.get_link_by_href("#resources"))
disks = WebDriverWait(
self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'vm-details-resources-disk')))
disk_list = disks.find_elements_by_css_selector(
"h4[class*='list-group-item-heading']")
if len(disk_list) > 0:
self.click_on_link(
self.get_link_by_href("/op/remove_disk/"))
self.wait_and_accept_operation(
try_wait="a[href*='#activity']")
recent_remove_disk = self.recently(
self.get_timeline_elements(
"vm.Instance.remove_disk", url_save))
if not self.check_operation_result(
recent_remove_disk, "a[href*='#activity']"):
logger.warning("Selenium cannot delete disk "
"of the chosen template")
raise Exception('Cannot delete disk')
return self.save_template_from_vm(name)
except:
logger.exception("Selenium cannot start a"
" template from a base one")
self.create_screenshot()
raise Exception(
'Cannot start a template from a base one')
def delete_template(self, template_id):
try:
self.driver.get(
'%s/dashboard/template/%s/' % (self.conf.host, template_id))
url_save = "%(host)s/dashboard/template/delete/%(pk)s/" % {
'host': self.conf.host,
'pk': template_id}
self.click_on_link(
self.get_link_by_href(
"/dashboard/template/delete/%s/" % template_id))
self.wait_and_accept_operation(fallback_url=url_save)
WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.CLASS_NAME, 'alert-success')))
url = urlparse.urlparse(self.driver.current_url)
if "/template/list/" not in url.path:
logger.warning('CIRCLE does not redirect to /template/list/')
raise Exception(
'System does not redirect to template listing')
logger.warning('Successfully deleted template: id - %(pk)s' % {
'pk': template_id})
except:
logger.exception("Selenium cannot delete the desired template")
self.create_screenshot()
raise Exception('Cannot delete the desired template')
def create_random_vm(self):
try:
self.driver.get('%s/dashboard/vm/create/' % self.conf.host)
vm_list = []
pk = None
vm_list = self.driver.find_elements_by_class_name(
'vm-create-template-summary')
choice = random.randint(0, len(vm_list) - 1)
vm_list[choice].click()
try:
WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.element_to_be_clickable((
By.CLASS_NAME, "vm-create-start"))).click()
except TimeoutException:
# Selenium can time out not findig it even though it is present
self.driver.find_element_by_tag_name('form').submit()
except:
logger.exception("Selenium could not submit create vm form")
raise Exception('Could not submit a form')
WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.CLASS_NAME, 'alert-success')))
url = urlparse.urlparse(self.driver.current_url)
pk = re.search(r'\d+', url.path).group()
return pk
except:
logger.exception("Selenium cannot start a VM")
self.create_screenshot()
raise Exception('Cannot start a VM')
def view_change(self, target_box):
driver = self.driver
driver.get('%s/dashboard/' % self.conf.host)
list_view = driver.find_element_by_id('%s-list-view' % target_box)
graph_view = driver.find_element_by_id('%s-graph-view' % target_box)
js_script = 'return arguments[0].style.display;'
required_attributes = {'data-index-box': target_box}
graph_view_link = self.get_link_by_href(
'#index-graph-view',
required_attributes).find_element_by_tag_name('i')
list_view_link = self.get_link_by_href(
'#index-list-view',
required_attributes).find_element_by_tag_name('i')
self.click_on_link(list_view_link)
states = [driver.execute_script(js_script, list_view),
driver.execute_script(js_script, graph_view)]
self.click_on_link(graph_view_link)
states.extend([driver.execute_script(js_script, list_view),
driver.execute_script(js_script, graph_view)])
self.click_on_link(list_view_link)
states.extend([driver.execute_script(js_script, list_view),
driver.execute_script(js_script, graph_view)])
return states
def delete_vm(self, pk):
try:
# For relability reasons instead of using the JS operatation
self.driver.get("%(host)s/dashboard/vm/%(id)s/op/destroy/" % {
'host': self.conf.host,
'id': pk})
self.wait_and_accept_operation(try_wait="a[href*='/op/recover/']")
try:
status_span = WebDriverWait(
self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'vm-details-state')))
WebDriverWait(status_span, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.CLASS_NAME, 'fa-trash-o')))
except:
# Selenium can time-out by not realising the JS refresh
url_save = self.get_url(fragment='activity')
recent_destroy_vm = self.recently(
self.get_timeline_elements(
"vm.Instance.destroy", url_save))
if not self.check_operation_result(
recent_destroy_vm, "a[href*='#activity']"):
logger.warning("Selenium cannot destroy "
"the chosen %(id)s vm" % {
'id': pk})
raise Exception('Cannot destroy the specified vm')
self.driver.get('%s/dashboard/vm/%s/' % (self.conf.host, pk))
try:
WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located((
By.CSS_SELECTOR,
"span[data-status*='DESTROYED']")))
logger.warning(
'Successfully deleted virtual machine: id - %(pk)s' % {
'pk': pk})
return True
except:
return False
except:
logger.exception("Selenium can not destroy a VM")
self.create_screenshot()
raise Exception("Cannot destroy a VM")
......@@ -20,8 +20,7 @@ import json
# from unittest import skip
from django.test import TestCase
from django.test.client import Client
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User, Group, Permission
from django.contrib.auth import authenticate
from common.tests.celery_mock import MockCeleryMixin
......
......@@ -228,7 +228,6 @@ urlpatterns = patterns(
url(r'^vm/opensearch.xml$', OpenSearchDescriptionView.as_view(),
name="dashboard.views.vm-opensearch"),
url(r'^storage/$', StorageDetail.as_view(),
name="dashboard.views.storage"),
url(r'^disk/(?P<pk>\d+)/$', DiskDetail.as_view(),
......
......@@ -13,3 +13,4 @@ from util import *
from vm import *
from graph import *
from storage import *
from request import *
......@@ -63,6 +63,8 @@ class GroupCodeMixin(object):
client = Saml2Client(conf, state_cache=state,
identity_cache=IdentityCache(request.session))
subject_id = _get_subject_id(request.session)
if not subject_id:
return newgroups
identity = client.users.get_identity(subject_id,
check_not_on_or_after=False)
if identity:
......@@ -144,7 +146,7 @@ class GroupDetailView(CheckedDetailView):
self.object.user_set.add(entity)
except User.DoesNotExist:
if saml_available:
FutureMember.objects.get_or_create(org_id=name,
FutureMember.objects.get_or_create(org_id=name.upper(),
group=self.object)
else:
messages.warning(request, _('User "%s" not found.') % name)
......
......@@ -441,7 +441,7 @@ class TransferTemplateOwnershipView(TransferOwnershipView):
confirm_view = TransferTemplateOwnershipConfirmView
model = InstanceTemplate
notification_msg = ugettext_noop(
'%(user)s offered you to take the ownership of '
'%(owner)s offered you to take the ownership of '
'his/her template called %(instance)s. '
'<a href="%(token)s" '
'class="btn btn-success btn-small">Accept</a>')
......
......@@ -70,7 +70,7 @@ def search_user(keyword):
return User.objects.get(username=keyword)
except User.DoesNotExist:
try:
return User.objects.get(profile__org_id=keyword)
return User.objects.get(profile__org_id__iexact=keyword)
except User.DoesNotExist:
return User.objects.get(email=keyword)
......@@ -610,7 +610,7 @@ class TransferOwnershipView(CheckedDetailView, DetailView):
new_owner.profile.notify(
ugettext_noop('Ownership offer'),
self.notification_msg,
{'instance': obj, 'token': token_path})
{'instance': obj, 'token': token_path, 'owner': request.user})
except Profile.DoesNotExist:
messages.error(request, _('Can not notify selected user.'))
else:
......@@ -665,8 +665,8 @@ class TransferOwnershipConfirmView(LoginRequiredMixin, View):
old.profile.notify(
ugettext_noop('Ownership accepted'),
ugettext_noop('Your ownership offer of %(instance)s has been '
'accepted by %(user)s.'),
{'instance': instance})
'accepted by %(owner)s.'),
{'instance': instance, 'owner': request.user})
return redirect(instance.get_absolute_url())
def get_instance(self, key, user):
......
......@@ -66,6 +66,8 @@ from ..forms import (
VmPortRemoveForm, VmPortAddForm,
VmRemoveInterfaceForm,
)
from request.models import TemplateAccessType, LeaseType
from request.forms import LeaseRequestForm, TemplateRequestForm
from ..models import Favourite
from manager.scheduler import has_traits
......@@ -171,6 +173,10 @@ class VmDetailView(GraphMixin, CheckedDetailView):
context['is_operator'] = is_operator
context['is_owner'] = is_owner
# operation also allows RUNNING (if with_shutdown is present)
context['save_resources_enabled'] = instance.status not in ("RUNNING",
"PENDING")
return context
def post(self, request, *args, **kwargs):
......@@ -651,10 +657,12 @@ class VmRenewView(FormOperationMixin, TokenOperationView, VmOperationView):
op = 'renew'
icon = 'calendar'
effect = 'info'
effect = 'success'
show_in_toolbar = False
form_class = VmRenewForm
wait_for_result = 0.5
template_name = 'dashboard/_vm-renew.html'
with_reload = True
def get_form_kwargs(self):
choices = Lease.get_objects_with_level("user", self.request.user)
......@@ -674,6 +682,12 @@ class VmRenewView(FormOperationMixin, TokenOperationView, VmOperationView):
instance.time_of_suspend)
return extra
def get_context_data(self, **kwargs):
context = super(VmRenewView, self).get_context_data(**kwargs)
context['lease_request_form'] = LeaseRequestForm(request=self.request)
context['lease_types'] = LeaseType.objects.exists()
return context
class VmStateChangeView(FormOperationMixin, VmOperationView):
op = 'emergency_change_state'
......@@ -1043,6 +1057,8 @@ class VmCreate(LoginRequiredMixin, TemplateView):
'box_title': _('Create a VM'),
'ajax_title': True,
'templates': templates.all(),
'template_access_types': TemplateAccessType.objects.exists(),
'form': TemplateRequestForm(request=request),
})
return self.render_to_response(context)
......@@ -1297,7 +1313,7 @@ class TransferInstanceOwnershipView(TransferOwnershipView):
confirm_view = TransferInstanceOwnershipConfirmView
model = Instance
notification_msg = ugettext_noop(
'%(user)s offered you to take the ownership of '
'%(owner)s offered you to take the ownership of '
'his/her virtual machine called %(instance)s. '
'<a href="%(token)s" '
'class="btn btn-success btn-small">Accept</a>')
......
......@@ -143,8 +143,8 @@ def selenium(test=""):
test = "--failed"
else:
test += " --with-id"
run("xvfb-run ./manage.py test "
"--settings=circle.settings.selenium_test %s" % test)
run('xvfb-run --server-args="-screen 0, 1920x1080x24" ./manage.py'
' test --settings=circle.settings.selenium_test %s' % test)
def pull(dir="~/circle/circle"):
......
......@@ -6,9 +6,9 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-03-05 14:32+0100\n"
"PO-Revision-Date: 2015-03-05 14:42+0116\n"
"Last-Translator: Daniel Bach <bd@doszgep.hu>\n"
"POT-Creation-Date: 2015-04-20 13:16+0200\n"
"PO-Revision-Date: 2015-04-20 13:31+0116\n"
"Last-Translator: Test Viktor <kviktor@cloud.bme.hu>\n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
......@@ -32,79 +32,79 @@ msgstr "Hiba."
#: common/models.py:72
#, python-format
msgid "Unhandled exception: %(error)s"
msgstr "Kezeletlen kivétel: %(error)s"
msgid "Unhandled exception: %(e)s: %(error)s"
msgstr "Kezeletlen kivétel: %(e)s: %(error)s"
#: common/models.py:147
#: common/models.py:148
#: dashboard/templates/dashboard/instanceactivity_detail.html:28
msgid "activity code"
msgstr "tevékenységkód"
#: common/models.py:150
#: common/models.py:151
msgid "human readable name"
msgstr "olvasható név"
#: common/models.py:151
#: common/models.py:152
msgid "Human readable name of activity."
msgstr "A tevékenység neve olvasható formában."
#: common/models.py:155
#: common/models.py:156
msgid "Celery task unique identifier."
msgstr "Celery feladat egyedi azonosítója."
#: common/models.py:156
#: common/models.py:157
msgid "task_uuid"
msgstr "feladat uuid"
#: common/models.py:157
#: common/models.py:158
#: dashboard/templates/dashboard/instanceactivity_detail.html:37
#: firewall/models.py:284 vm/models/common.py:84 vm/models/instance.py:130
#: vm/models/instance.py:211
#: firewall/models.py:284 request/models.py:226 vm/models/common.py:84
#: vm/models/instance.py:130 vm/models/instance.py:211
msgid "user"
msgstr "felhasználó"
#: common/models.py:158
#: common/models.py:159
msgid "The person who started this activity."
msgstr "A tevékenységet indító felhasználó."
#: common/models.py:159
#: common/models.py:160
msgid "started at"
msgstr "indítás ideje"
#: common/models.py:161
#: common/models.py:162
msgid "Time of activity initiation."
msgstr "A tevékenység megkezdésének időpontja."
#: common/models.py:162
#: common/models.py:163
msgid "finished at"
msgstr "befejezés ideje"
#: common/models.py:164
#: common/models.py:165
msgid "Time of activity finalization."
msgstr "A tevékenység befejeztének ideje."
#: common/models.py:166
#: common/models.py:167
msgid "True, if the activity has finished successfully."
msgstr "Igaz, ha a tevékenység sikeresen befejeződött."
#: common/models.py:168
#: common/models.py:169
#: dashboard/templates/dashboard/instanceactivity_detail.html:56
msgid "result"
msgstr "eredmény"
#: common/models.py:170
#: common/models.py:171
msgid "Human readable result of activity."
msgstr "A tevékenység eredménye olvasható formában."
#: common/models.py:535
#: common/models.py:536
msgid "Permission Denied"
msgstr "Hozzáférés megtagadva"
#: common/models.py:537
#: common/models.py:538
msgid "Unknown error"
msgstr "Ismeretlen hiba"
#: common/models.py:538
#: common/models.py:539
#, python-format
msgid "Unknown error: %(ex)s"
msgstr "Ismeretlen hiba: %(ex)s"
......@@ -138,13 +138,13 @@ msgstr "szerver"
msgid "realtime"
msgstr "valós idejű"
#: dashboard/forms.py:93 dashboard/forms.py:810 dashboard/forms.py:900
#: dashboard/forms.py:1351 dashboard/tables.py:268
#: dashboard/forms.py:93 dashboard/forms.py:811 dashboard/forms.py:901
#: dashboard/forms.py:1352 dashboard/tables.py:270
#: dashboard/templates/dashboard/_vm-create-2.html:20
#: dashboard/templates/dashboard/vm-detail/home.html:9
#: dashboard/templates/dashboard/vm-list.html:62 firewall/models.py:296
#: network/templates/network/index.html:24
#: network/templates/network/switch-port-edit.html:45
#: network/templates/network/switch-port-edit.html:45 request/models.py:57
msgid "Name"
msgstr "Név"
......@@ -195,13 +195,15 @@ msgstr ""
#: network/templates/network/switch-port-create.html:8
#: network/templates/network/vlan-create.html:8
#: network/templates/network/vlan-group-create.html:8
#: request/templates/request/lease-type-form.html:7
#: request/templates/request/template-type-form.html:7
msgid "Create"
msgstr "Létrehozás"
#: dashboard/forms.py:282 dashboard/forms.py:1231 dashboard/forms.py:1248
#: dashboard/forms.py:1283 dashboard/forms.py:1321 dashboard/forms.py:1364
#: dashboard/forms.py:1405 dashboard/forms.py:1425 dashboard/forms.py:1488
#: dashboard/forms.py:1600
#: dashboard/forms.py:282 dashboard/forms.py:1232 dashboard/forms.py:1249
#: dashboard/forms.py:1284 dashboard/forms.py:1322 dashboard/forms.py:1365
#: dashboard/forms.py:1406 dashboard/forms.py:1426 dashboard/forms.py:1489
#: dashboard/forms.py:1601
#: dashboard/templates/dashboard/_manage_access.html:73
#: dashboard/templates/dashboard/connect-command-create.html:37
#: dashboard/templates/dashboard/connect-command-edit.html:37
......@@ -209,21 +211,22 @@ msgstr "Létrehozás"
#: dashboard/templates/dashboard/lease-edit.html:96
#: dashboard/templates/dashboard/template-tx-owner.html:12
#: dashboard/templates/dashboard/vm-detail/tx-owner.html:12
#: network/forms.py:86 network/forms.py:100 network/forms.py:122
#: network/forms.py:162 network/forms.py:187 network/forms.py:226
#: network/forms.py:247 network/forms.py:298 network/forms.py:323
#: network/forms.py:88 network/forms.py:103 network/forms.py:126
#: network/forms.py:167 network/forms.py:195 network/forms.py:236
#: network/forms.py:261 network/forms.py:313 network/forms.py:342
#: request/forms.py:38 request/forms.py:54
msgid "Save"
msgstr "Mentés"
#: dashboard/forms.py:312 dashboard/forms.py:1028
#: dashboard/forms.py:312 dashboard/forms.py:1029
#: dashboard/templates/dashboard/_vm-remove-port.html:15
#: dashboard/templates/dashboard/vm-detail.html:102 network/views.py:659
msgid "Host"
msgstr "Gép"
#: dashboard/forms.py:383 dashboard/forms.py:781 dashboard/forms.py:991
#: dashboard/forms.py:383 dashboard/forms.py:782 dashboard/forms.py:992
#: dashboard/templates/dashboard/node-detail.html:5
#: dashboard/templates/dashboard/vm-detail/home.html:117
#: dashboard/templates/dashboard/vm-detail/home.html:118
#: dashboard/templates/dashboard/vm-list.html:87
msgid "Node"
msgstr "Csomópont"
......@@ -261,24 +264,24 @@ msgstr "Törlés ideje"
msgid "Save changes"
msgstr "Változások mentése"
#: dashboard/forms.py:747
#: dashboard/forms.py:748
msgid "Set expiration times even if they are shorter than the current value."
msgstr ""
"Akkor is állítsa át a lejárati időket, ha rövidebbek lesznek a jelenleginél."
#: dashboard/forms.py:750
#: dashboard/forms.py:751
msgid "Save selected lease."
msgstr "Kiválasztott bérlet mentése."
#: dashboard/forms.py:759
#: dashboard/forms.py:760
msgid "Length"
msgstr "Hossz"
#: dashboard/forms.py:767
#: dashboard/forms.py:768
msgid "Live migration"
msgstr "Live migration"
#: dashboard/forms.py:769
#: dashboard/forms.py:770
msgid ""
"Live migration is a way of moving virtual machines between hosts with a "
"service interruption of at most some seconds. Please note that it can take "
......@@ -289,200 +292,201 @@ msgstr ""
"hogy ez terhelt gépek esetén sokáig tarthat és nagy hálózati forgalommal "
"jár."
#: dashboard/forms.py:787
#: dashboard/forms.py:788
msgid "Forcibly interrupt all running activities."
msgstr "Futó tevékenységek erőltetett befejezése."
#: dashboard/forms.py:788
#: dashboard/forms.py:789
msgid "Set all activities to finished state, but don't interrupt any tasks."
msgstr ""
"Minden tevékenység befejezettre állítása (a feladatok megszakítása nélkül)."
#: dashboard/forms.py:791
#: dashboard/forms.py:792
msgid "New status"
msgstr "Új állapot"
#: dashboard/forms.py:792
#: dashboard/forms.py:793
msgid "Reset node"
msgstr "Csomópont visszaállítása"
#: dashboard/forms.py:806
#: dashboard/forms.py:807
msgid "use emergency state change"
msgstr "vész-állapotváltás használata"
#: dashboard/forms.py:812 dashboard/forms.py:832
#: dashboard/forms.py:813 dashboard/forms.py:833
#: dashboard/templates/dashboard/store/_list-box.html:117
msgid "Size"
msgstr "Méret"
#: dashboard/forms.py:813
#: dashboard/forms.py:814
msgid "Size of disk to create in bytes or with units like MB or GB."
msgstr "Létrehozandó lemez mérete byte-okban vagy mértékegységgel (MB, GB)."
#: dashboard/forms.py:825 dashboard/forms.py:854
#: dashboard/forms.py:826 dashboard/forms.py:855
msgid "Invalid format, you can use GB or MB!"
msgstr "Érvénytelen formátum. „GB” és „MB” is használható."
#: dashboard/forms.py:833
#: dashboard/forms.py:834
msgid "Size to resize the disk in bytes or with units like MB or GB."
msgstr "A lemez kívánt mérete byte-okban vagy mértékegységgel (MB, GB)."
#: dashboard/forms.py:844 dashboard/forms.py:880
#: dashboard/forms.py:845 dashboard/forms.py:881
#: dashboard/templates/dashboard/storage/disk.html:7
#: dashboard/templates/dashboard/storage/disk.html:24
msgid "Disk"
msgstr "Lemez"
#: dashboard/forms.py:857
#: dashboard/forms.py:858
msgid "Disk size must be greater than the actual size."
msgstr "A lemez mérete nagyobb kell legyen a jelenleginél."
#: dashboard/forms.py:866 dashboard/forms.py:891
#: dashboard/forms.py:867 dashboard/forms.py:892
#, python-format
msgid "<label>Disk:</label> %s"
msgstr "<label>Lemez:</label> %s"
#: dashboard/forms.py:901
#: dashboard/forms.py:902
msgid "URL"
msgstr "URL"
#: dashboard/forms.py:911
#: dashboard/forms.py:912
msgid "Could not find filename in URL, please specify a name explicitly."
msgstr "Az URL-ben nem található fájlnév. Kérem adja meg explicite."
#: dashboard/forms.py:925
#: dashboard/forms.py:926
msgid "Interface"
msgstr "Interfészek"
#: dashboard/forms.py:937
#: dashboard/forms.py:938
#, python-brace-format
msgid "<label>Vlan:</label> {0}"
msgstr "<label>Vlan:</label> {0}"
#: dashboard/forms.py:952
#: dashboard/forms.py:953
#: dashboard/templates/dashboard/_vm-remove-port.html:17
#: dashboard/templates/dashboard/node-detail/resources.html:28
#: network/views.py:658
msgid "Vlan"
msgstr "Vlan"
#: dashboard/forms.py:955
#: dashboard/forms.py:956
msgid "No more networks."
msgstr "Nincs több hálózat."
#: dashboard/forms.py:976
#: dashboard/forms.py:977
#, python-format
msgid " (missing_traits: %s)"
msgstr "(hiányzó jellemzők: %s)"
#: dashboard/forms.py:992
#: dashboard/forms.py:993
msgid ""
"Deploy virtual machine to this node (blank allows scheduling automatically)."
msgstr ""
"A virtuális gép elindítása ezen a csomóponton (üresen hagyva automatikus "
"ütemezés)."
#: dashboard/forms.py:1009 dashboard/forms.py:1015
#: dashboard/forms.py:1010 dashboard/forms.py:1016
#: dashboard/templates/dashboard/_vm-remove-port.html:13
msgid "Port"
msgstr "Port"
#: dashboard/forms.py:1018 dashboard/templates/dashboard/vm-detail.html:100
#: dashboard/forms.py:1019 dashboard/templates/dashboard/vm-detail.html:100
msgid "Protocol"
msgstr "Protokoll"
#: dashboard/forms.py:1040
#: dashboard/forms.py:1041
#, python-brace-format
msgid "<label>Host:</label> {0}"
msgstr "<label>Gép:</label> {0}"
#: dashboard/forms.py:1068 dashboard/templates/dashboard/profile.html:36
#: dashboard/forms.py:1069 dashboard/templates/dashboard/profile.html:37
#: dashboard/templates/dashboard/vm-detail.html:118
msgid "Username"
msgstr "Felhasználónév"
#: dashboard/forms.py:1082 dashboard/templates/dashboard/vm-detail.html:120
#: dashboard/forms.py:1083 dashboard/templates/dashboard/vm-detail.html:120
msgid "Password"
msgstr "Jelszó"
#: dashboard/forms.py:1087
#: dashboard/forms.py:1088
msgid "Sign in"
msgstr "Bejelentkezés"
#: dashboard/forms.py:1110 dashboard/templates/dashboard/profile.html:42
#: dashboard/forms.py:1111 dashboard/templates/dashboard/profile.html:43
msgid "Email address"
msgstr "E-mail cím"
#: dashboard/forms.py:1115
#: dashboard/forms.py:1116
msgid "Reset password"
msgstr "Új jelszó"
#: dashboard/forms.py:1131 dashboard/forms.py:1257
#: dashboard/forms.py:1132 dashboard/forms.py:1258
msgid "Change password"
msgstr "Jelszóváltoztatás"
#: dashboard/forms.py:1203
#: dashboard/forms.py:1204
msgid "Add trait"
msgstr "Jellemző hozzáadása"
#: dashboard/forms.py:1220
#: dashboard/forms.py:1221
msgid "Preferred language"
msgstr "Választott nyelv"
#: dashboard/forms.py:1272 dashboard/templates/dashboard/group-list.html:14
#: dashboard/forms.py:1273 dashboard/templates/dashboard/group-list.html:14
#: dashboard/templates/dashboard/index-groups.html:7
#: dashboard/templates/dashboard/profile.html:60
#: dashboard/templates/dashboard/profile.html:62
#: dashboard/templates/dashboard/vm-detail/network.html:40
#: network/templates/network/host-edit.html:32 templates/info/help.html:192
msgid "Groups"
msgstr "Csoportok"
#: dashboard/forms.py:1298
#: dashboard/forms.py:1299
msgid "Instance limit"
msgstr "Példány limit"
#: dashboard/forms.py:1328 dashboard/templates/dashboard/lease-edit.html:86
#: dashboard/forms.py:1329 dashboard/templates/dashboard/lease-edit.html:86
msgid "Name of group or user"
msgstr "Csoport vagy felhasználó neve"
#: dashboard/forms.py:1336 dashboard/forms.py:1345
#: dashboard/forms.py:1337 dashboard/forms.py:1346
msgid "Name of user"
msgstr "Felhasználó neve"
#: dashboard/forms.py:1338 dashboard/forms.py:1347
#: dashboard/forms.py:1339 dashboard/forms.py:1348
msgid "E-mail address or identifier of user"
msgstr "A felhasználó e-mail címe vagy azonosítója"
#: dashboard/forms.py:1353
#: dashboard/forms.py:1354
msgid "Key"
msgstr "Kulcs"
#: dashboard/forms.py:1354
#: dashboard/forms.py:1355
msgid "For example: ssh-rsa AAAAB3NzaC1yc2ED..."
msgstr "Például: ssh-rsa AAAAB3NzaC1yc2ED…"
#: dashboard/forms.py:1434
#: dashboard/forms.py:1435
msgid "permissions"
msgstr "jogosultságok"
#: dashboard/forms.py:1531
#: dashboard/forms.py:1532
msgid "owned"
msgstr "saját"
#: dashboard/forms.py:1532
#: dashboard/forms.py:1533
msgid "shared"
msgstr "osztott"
#: dashboard/forms.py:1533
#: dashboard/forms.py:1534
msgid "all"
msgstr "összes"
#: dashboard/forms.py:1540 dashboard/forms.py:1564 dashboard/forms.py:1583
#: dashboard/forms.py:1541 dashboard/forms.py:1565 dashboard/forms.py:1584
#: dashboard/templates/dashboard/index-groups.html:23
#: dashboard/templates/dashboard/index-nodes.html:40
#: dashboard/templates/dashboard/index-templates.html:40
#: dashboard/templates/dashboard/index-users.html:28
#: dashboard/templates/dashboard/index-vm.html:64
#: dashboard/templates/dashboard/storage/detail.html:91
#: network/templates/network/host-list.html:39
msgid "Search..."
msgstr "Keresés..."
......@@ -521,8 +525,8 @@ msgstr "elérés módja"
msgid "Type of the remote access method."
msgstr "Távoli elérési mód típusa."
#: dashboard/models.py:121 firewall/models.py:528 firewall/models.py:554
#: firewall/models.py:936 firewall/models.py:975 firewall/models.py:995
#: dashboard/models.py:121 firewall/models.py:533 firewall/models.py:564
#: firewall/models.py:951 firewall/models.py:995 firewall/models.py:1020
#: storage/models.py:49 storage/models.py:120 vm/models/common.py:65
#: vm/models/common.py:89 vm/models/common.py:165 vm/models/instance.py:134
#: vm/models/instance.py:224 vm/models/node.py:121
......@@ -545,69 +549,69 @@ msgstr ""
"Sablon a csatlakozási parancshoz. Elérhető paraméterek: username, password, "
"host, port."
#: dashboard/models.py:137
#: dashboard/models.py:140
msgid "preferred language"
msgstr "választott nyelv"
#: dashboard/models.py:143 dashboard/models.py:228
#: dashboard/models.py:146 dashboard/models.py:232
msgid "Unique identifier of the person, e.g. a student number."
msgstr "A személy egyedi azonosítója, például hallgatói azonosító."
#: dashboard/models.py:146
#: dashboard/models.py:149
msgid "Use Gravatar"
msgstr "Gravatar használata"
#: dashboard/models.py:147
#: dashboard/models.py:150
msgid "Whether to use email address as Gravatar profile image"
msgstr "Használható-e az e-mail cím a Gravatar profilkép betöltésére"
#: dashboard/models.py:149
#: dashboard/models.py:152
msgid "Email notifications"
msgstr "E-mail értesítések"
#: dashboard/models.py:150
#: dashboard/models.py:153
msgid "Whether user wants to get digested email notifications."
msgstr "A felhasználó kéri-e tömbösített e-mail értesítések küldését."
#: dashboard/models.py:153
#: dashboard/models.py:156
msgid "Samba password"
msgstr "Samba jelszó"
#: dashboard/models.py:155
#: dashboard/models.py:158
msgid "Generated password for accessing store from virtual machines."
msgstr "A tárhely virtuális gépekről való eléréséhez generált jelszó."
#: dashboard/models.py:160
#: dashboard/models.py:163
msgid "disk quota"
msgstr "lemezkvóta"
#: dashboard/models.py:162
#: dashboard/models.py:165
msgid "Disk quota in mebibytes."
msgstr "Lemezkvóta mebibyte-okban."
#: dashboard/models.py:222
#: dashboard/models.py:226
msgid "Can use autocomplete."
msgstr "Használhat automatikus kiegészítést."
#: dashboard/models.py:240 firewall/models.py:285 vm/models/common.py:85
#: vm/models/instance.py:131 vm/models/instance.py:212
#: dashboard/models.py:245 firewall/models.py:285 request/models.py:227
#: vm/models/common.py:85 vm/models/instance.py:131 vm/models/instance.py:212
msgid "operator"
msgstr "operátor"
#: dashboard/models.py:241 firewall/models.py:105 firewall/models.py:390
#: firewall/models.py:537 firewall/models.py:559 firewall/models.py:623
#: firewall/models.py:976 firewall/models.py:1004 vm/models/common.py:86
#: dashboard/models.py:246 firewall/models.py:105 firewall/models.py:390
#: firewall/models.py:542 firewall/models.py:569 firewall/models.py:638
#: firewall/models.py:996 firewall/models.py:1029 vm/models/common.py:86
#: vm/models/instance.py:132 vm/models/instance.py:213
msgid "owner"
msgstr "tulajdonos"
#: dashboard/models.py:247
#: dashboard/models.py:252
msgid "Unique identifier of the group at the organization."
msgstr "A csoport egyedi szervezeti azonosítója."
#. Translators: [T] as Template
#: dashboard/tables.py:53 dashboard/tables.py:308
#: dashboard/templates/dashboard/vm-detail/home.html:130
#: dashboard/tables.py:53 dashboard/tables.py:310
#: dashboard/templates/dashboard/vm-detail/home.html:131
msgid "Template"
msgstr "Sablon"
......@@ -616,7 +620,8 @@ msgid "[T]"
msgstr "[S]"
#. Translators: [VM] as Virtual Machine
#: dashboard/tables.py:56
#: dashboard/tables.py:56 request/templates/request/request-lease.html:17
#: request/templates/request/request-resource.html:20
msgid "Virtual machine"
msgstr "Virtuális gép"
......@@ -628,7 +633,8 @@ msgstr ""
msgid "Overcommit"
msgstr "Túlfoglalás"
#: dashboard/tables.py:73
#: dashboard/tables.py:73 request/tables.py:35
#: request/templates/request/detail.html:71
msgid "Status"
msgstr "Állapot"
......@@ -649,35 +655,36 @@ msgstr "Elérhető minionok"
msgid "Number of users"
msgstr "Felhasználók száma"
#: dashboard/tables.py:134 dashboard/templates/dashboard/base.html:25
#: dashboard/tables.py:134 dashboard/templates/dashboard/base.html:27
msgid "Admin"
msgstr "Adminisztráció"
#: dashboard/tables.py:141 dashboard/tables.py:218 dashboard/tables.py:249
#: dashboard/tables.py:283 dashboard/tables.py:312 network/tables.py:272
#: dashboard/tables.py:141 dashboard/tables.py:220 dashboard/tables.py:251
#: dashboard/tables.py:285 dashboard/tables.py:314 network/tables.py:272
msgid "Actions"
msgstr "Műveletek"
#: dashboard/tables.py:163 dashboard/templates/dashboard/profile.html:37
#: dashboard/tables.py:164 dashboard/templates/dashboard/profile.html:38
msgid "Organization ID"
msgstr "Címtári azonosító"
#: dashboard/tables.py:168
#: dashboard/tables.py:169
msgid "<abbr data-placement=\"left\" title=\"Superuser status\">SU</abbr>"
msgstr "<abbr data-placement=\"left\" title=\"Rendszergazda státusz\">SU</abbr>"
#: dashboard/tables.py:188 dashboard/templates/dashboard/_vm-create-2.html:38
#: dashboard/tables.py:190 dashboard/templates/dashboard/_vm-create-2.html:38
#: dashboard/templates/dashboard/node-detail.html:72
#: dashboard/templates/dashboard/vm-detail.html:200
msgid "Resources"
msgstr "Erőforrások"
#: dashboard/tables.py:194 dashboard/templates/dashboard/vm-list.html:74
#: dashboard/tables.py:196 dashboard/templates/dashboard/vm-list.html:74
#: request/forms.py:85 request/models.py:148 request/tables.py:63
#: vm/models/instance.py:104
msgid "Lease"
msgstr "Bérlet"
#: dashboard/tables.py:205 dashboard/templates/dashboard/template-edit.html:85
#: dashboard/tables.py:207 dashboard/templates/dashboard/template-edit.html:85
#: dashboard/templates/dashboard/template-edit.html:133
#: dashboard/templates/dashboard/vm-detail/access.html:2
#: dashboard/templates/dashboard/vm-detail/access.html:37
......@@ -685,31 +692,31 @@ msgstr "Bérlet"
msgid "Owner"
msgstr "Tulajdonos"
#: dashboard/tables.py:210 dashboard/tables.py:278
#: dashboard/tables.py:212 dashboard/tables.py:280
msgid "Created at"
msgstr "Létrehozva"
#: dashboard/tables.py:214
#: dashboard/tables.py:216
msgid "Running"
msgstr "Fut"
#: dashboard/tables.py:261
#: dashboard/tables.py:263
msgid "No available leases."
msgstr "Nincs elérhető bérlési mód."
#: dashboard/tables.py:273
#: dashboard/tables.py:275
msgid "Fingerprint"
msgstr "Ujjlenyomat"
#: dashboard/tables.py:294
#: dashboard/tables.py:296
msgid "You haven't added any public keys yet."
msgstr "Még nem adott meg publikus kulcsot."
#: dashboard/tables.py:304
#: dashboard/tables.py:306
msgid "Access method"
msgstr "Elérés módja"
#: dashboard/tables.py:325
#: dashboard/tables.py:327
msgid ""
"You don't have any custom connection commands yet. You can specify commands "
"to be displayed on VM detail pages instead of the defaults."
......@@ -717,18 +724,25 @@ msgstr ""
"Még nincs egyedi csatlakozási parancsa. Az itt megadott parancsok fognak "
"megjelenni a VM-részletező oldalon az alapértelmezettek helyett."
#: dashboard/tables.py:334 dashboard/templates/dashboard/vm-list.html:58
#: dashboard/tables.py:336 dashboard/templates/dashboard/vm-list.html:58
#: request/tables.py:31 request/tables.py:61 request/tables.py:77
msgid "ID"
msgstr "ID"
#: dashboard/tables.py:339 dashboard/templates/dashboard/storage/disk.html:18
#: dashboard/tables.py:341 dashboard/templates/dashboard/storage/disk.html:18
msgid "Appliance"
msgstr "Felhasználás"
#: dashboard/tables.py:343
#: dashboard/tables.py:345
msgid "ready"
msgstr "kész"
#: dashboard/tables.py:356
#, fuzzy
#| msgid "No related rules found."
msgid "No disk found."
msgstr "Nincs kapcsolódó szabály."
#: dashboard/tasks/local_periodic_tasks.py:60
#, python-format
msgid "%d new notification"
......@@ -789,10 +803,11 @@ msgstr ""
" "
#: dashboard/templates/dashboard/_client-check.html:23
#: dashboard/templates/dashboard/_vm-renew.html:9
#: dashboard/templates/dashboard/confirm/ajax-delete.html:19
#: dashboard/templates/dashboard/confirm/base-delete.html:33
#: dashboard/templates/dashboard/mass-operate.html:33
#: dashboard/templates/dashboard/operate.html:21
#: dashboard/templates/dashboard/operate.html:22
#: dashboard/templates/dashboard/store/remove.html:34
msgid "Cancel"
msgstr "Mégsem"
......@@ -949,22 +964,22 @@ msgstr "Memória"
#: dashboard/templates/dashboard/_vm-create-1.html:39
#: dashboard/templates/dashboard/_vm-create-2.html:49
#: dashboard/templates/dashboard/storage/detail.html:68
#: dashboard/templates/dashboard/vm-detail/resources.html:25
#: dashboard/templates/dashboard/storage/detail.html:70
#: dashboard/templates/dashboard/vm-detail/resources.html:48
msgid "Disks"
msgstr "Lemezek"
#: dashboard/templates/dashboard/_vm-create-1.html:46
#: dashboard/templates/dashboard/_vm-create-2.html:65
#: dashboard/templates/dashboard/base.html:34
#: dashboard/templates/dashboard/base.html:40
#: dashboard/templates/dashboard/vm-detail.html:214
#: dashboard/views/graph.py:198 dashboard/views/graph.py:221
#: network/forms.py:142 network/templates/network/base.html:7
#: network/forms.py:147 network/templates/network/base.html:7
msgid "Network"
msgstr "Hálózat"
#: dashboard/templates/dashboard/_vm-create-1.html:52
#: network/templates/network/index.html:46
#: network/templates/network/index.html:46 request/tables.py:43
msgid "Type"
msgstr "Típus"
......@@ -978,19 +993,37 @@ msgstr "Testreszabás"
msgid "Start"
msgstr "Indítás"
#: dashboard/templates/dashboard/_vm-create-1.html:82
#: dashboard/templates/dashboard/_vm-create-1.html:83
msgid ""
"You can't start new virtual machines because no templates are shared with "
"you."
msgstr ""
"Még nem tud virtuális gépet indítani, mivel egy sablonhoz sincs hozzáférése."
#: dashboard/templates/dashboard/_vm-create-1.html:85
msgid ""
"You can't start new virtual machines because no templates are shared with "
"you however you can request them via the form below."
msgstr ""
"Még nem tud virtuális gépet indítani, mivel egy sablonhoz sincs hozzáférése "
"azonban a lenti űrlap segítségével igényelhet."
#: dashboard/templates/dashboard/_vm-create-1.html:96
#, python-format
msgid ""
"\n"
" Need other templates? Submit a new <a href=\"%(url)s\">request</a>.\n"
" "
msgstr ""
"\n"
"Másfajta sablonra van szüksége? <a href=\"%(url)s\">Igényeljen</a>."
#: dashboard/templates/dashboard/_vm-create-2.html:30
msgid "Amount"
msgstr "Mennyiség"
#: dashboard/templates/dashboard/_vm-create-2.html:56
#: dashboard/templates/dashboard/vm-detail/resources.html:35
#: dashboard/templates/dashboard/vm-detail/resources.html:58
msgid "No disks are added."
msgstr "Egy lemez sincs hozzáadva."
......@@ -1043,10 +1076,14 @@ msgid "Node traits"
msgstr "Csomópontjellemzők"
#: dashboard/templates/dashboard/_vm-migrate.html:46
#: dashboard/templates/dashboard/vm-detail/resources.html:52
#: dashboard/templates/dashboard/vm-detail/resources.html:75
msgid "Required traits"
msgstr "Elvárt jellemzők"
#: dashboard/templates/dashboard/_vm-renew.html:15
msgid "Request longer lease"
msgstr "Hosszabb bérlet igénylése"
#: dashboard/templates/dashboard/_vm-save.html:7
msgid ""
"\n"
......@@ -1059,28 +1096,34 @@ msgstr ""
msgid "Name of template"
msgstr "Sablon neve"
#: dashboard/templates/dashboard/base.html:30
#: dashboard/templates/dashboard/base.html:34
#: dashboard/templates/dashboard/storage/detail.html:7
#: dashboard/templates/dashboard/storage/disk.html:7
msgid "Storage"
msgstr "Tárhely"
#: dashboard/templates/dashboard/base.html:45
#: dashboard/templates/dashboard/base.html:46
#: request/templates/request/list.html:6
#: request/templates/request/list.html:17
msgid "Requests"
msgstr "Igénylések"
#: dashboard/templates/dashboard/base.html:58
msgid "Log out"
msgstr "Kijelentkezés"
#: dashboard/templates/dashboard/base.html:50
#: dashboard/templates/dashboard/base.html:60
#: dashboard/templates/dashboard/base.html:63
#: dashboard/templates/dashboard/base.html:73
#: dashboard/templates/dashboard/notifications.html:4
#: dashboard/templates/dashboard/notifications.html:24
msgid "Notifications"
msgstr "Értesítések"
#: dashboard/templates/dashboard/base.html:68
#: dashboard/templates/dashboard/base.html:81
msgid "Loading..."
msgstr "Betöltés..."
#: dashboard/templates/dashboard/base.html:74
#: dashboard/templates/dashboard/base.html:87
msgid "Log in "
msgstr "Bejelentkezés"
......@@ -1114,6 +1157,8 @@ msgstr ""
#: dashboard/templates/dashboard/template-list/column-lease-actions.html:5
#: dashboard/templates/dashboard/template-list/column-template-actions.html:9
#: dashboard/templates/dashboard/userkey-list/column-userkey-actions.html:5
#: request/templates/request/lease-type-form.html:21
#: request/templates/request/template-type-form.html:21
msgid "Delete"
msgstr "Törlés"
......@@ -1193,13 +1238,16 @@ msgstr "Parancssablon létrehozása"
#: dashboard/templates/dashboard/connect-command-edit.html:13
#: dashboard/templates/dashboard/lease-create.html:13
#: dashboard/templates/dashboard/lease-edit.html:12
#: dashboard/templates/dashboard/profile.html:23
#: dashboard/templates/dashboard/profile.html:24
#: dashboard/templates/dashboard/template-edit.html:16
#: dashboard/templates/dashboard/userkey-create.html:13
#: dashboard/templates/dashboard/userkey-edit.html:14 network/forms.py:65
#: network/forms.py:87 network/forms.py:101 network/forms.py:123
#: network/forms.py:163 network/forms.py:188 network/forms.py:227
#: network/forms.py:248 network/forms.py:299 network/forms.py:324
#: network/forms.py:89 network/forms.py:104 network/forms.py:127
#: network/forms.py:168 network/forms.py:196 network/forms.py:237
#: network/forms.py:262 network/forms.py:314 network/forms.py:343
#: request/templates/request/detail.html:17
#: request/templates/request/lease-type-form.html:25
#: request/templates/request/template-type-form.html:25
msgid "Back"
msgstr "Vissza"
......@@ -1362,8 +1410,8 @@ msgstr ""
#: dashboard/templates/dashboard/index-templates.html:7
#: dashboard/templates/dashboard/template-list.html:6
#: dashboard/templates/dashboard/template-list.html:17
#: templates/info/help.html:156
#: dashboard/templates/dashboard/template-list.html:17 request/models.py:162
#: request/tables.py:81 templates/info/help.html:156
msgid "Templates"
msgstr "Sablonok"
......@@ -1515,7 +1563,7 @@ msgid "time"
msgstr "idő"
#: dashboard/templates/dashboard/instanceactivity_detail.html:40
#: firewall/models.py:1000 network/tables.py:181
#: firewall/models.py:1025 network/tables.py:181
msgid "type"
msgstr "típus"
......@@ -1557,6 +1605,7 @@ msgid "failed"
msgstr "meghiúsult"
#: dashboard/templates/dashboard/instanceactivity_detail.html:78
#: dashboard/views/storage.py:59
msgid "none"
msgstr "nincs"
......@@ -1629,6 +1678,16 @@ msgstr "Kezdőoldal"
msgid "Virtual Machines"
msgstr "Virtuális gépek"
#: dashboard/templates/dashboard/node-detail/_activity-timeline.html:47
#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:65
msgid "Show less activities"
msgstr "Kevesebb tevékenység megjelenítése"
#: dashboard/templates/dashboard/node-detail/_activity-timeline.html:49
#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:67
msgid "Show all activities"
msgstr "Összes tevékenység megjelenítése"
#: dashboard/templates/dashboard/node-detail/home.html:4
msgid "Traits the node provides"
msgstr "A csomópont által biztosított jellemzők"
......@@ -1646,7 +1705,7 @@ msgid "CPU cores"
msgstr "CPU-magok"
#: dashboard/templates/dashboard/node-detail/resources.html:7
#: vm/models/common.py:43
#: request/models.py:175 vm/models/common.py:43
msgid "RAM size"
msgstr "RAM-méret"
......@@ -1663,6 +1722,7 @@ msgid "Host online"
msgstr "Gép elérhető"
#: dashboard/templates/dashboard/node-detail/resources.html:13
#: request/templates/request/detail.html:79
msgid "Priority"
msgstr "Prioritás"
......@@ -1728,61 +1788,66 @@ msgstr ""
"Biztosan végrehajtja a(z) <strong>%(op)s</strong> műveletet\n"
"a következőn: <a data-dismiss=\"modal\" href=\"%(url)s\">%(obj)s</a>?\n"
#: dashboard/templates/dashboard/profile.html:6
#: dashboard/templates/dashboard/profile.html:7
#: dashboard/templates/dashboard/profile_form.html:6
msgid "Profile"
msgstr "Profil"
#: dashboard/templates/dashboard/profile.html:17
#: dashboard/templates/dashboard/profile.html:18
msgid "Log in as this user. Recommended to open in an incognito window."
msgstr ""
"Bejelentkezés a felhasználó nevében. Ajánlott inkognitóablakban megnyitni."
#: dashboard/templates/dashboard/profile.html:19
#: dashboard/templates/dashboard/profile.html:20
msgid "Login as this user"
msgstr "Bejelentkezés a felhasználó nevében"
#: dashboard/templates/dashboard/profile.html:38
#: dashboard/templates/dashboard/profile.html:39
msgid "First name"
msgstr "Keresztnév"
#: dashboard/templates/dashboard/profile.html:39
#: dashboard/templates/dashboard/profile.html:40
msgid "Last name"
msgstr "Vezetéknév"
#: dashboard/templates/dashboard/profile.html:47
#: dashboard/templates/dashboard/profile.html:46
#| msgid "Back to login"
msgid "Last login"
msgstr "Utolsó belépés"
#: dashboard/templates/dashboard/profile.html:49
msgid "Use email address as Gravatar profile image"
msgstr "E-mail cím használata a Gravatar profilkép betöltésére"
#: dashboard/templates/dashboard/profile.html:50
#: dashboard/templates/dashboard/profile.html:52
msgid "What's Gravatar?"
msgstr "Mi az a Gravatar?"
#: dashboard/templates/dashboard/profile.html:52
#: dashboard/templates/dashboard/profile.html:54
msgid "Change my preferences"
msgstr "Személyes beállítások"
#: dashboard/templates/dashboard/profile.html:66
#: dashboard/templates/dashboard/profile.html:68
msgid "This user is not in any group."
msgstr "A felhasználó nem tagja csoportnak."
#: dashboard/templates/dashboard/profile.html:75
#: dashboard/templates/dashboard/profile.html:77
msgid "Virtual machines owned by the user"
msgstr "A felhasználó virtuális gépei"
#: dashboard/templates/dashboard/profile.html:87
#: dashboard/templates/dashboard/profile.html:89
msgid "This user have no virtual machines."
msgstr "A felhasználónak nincs virtuális gépe."
#: dashboard/templates/dashboard/profile.html:96
#: dashboard/templates/dashboard/profile.html:98
msgid "Virtual machines with access"
msgstr "Elérhető virtuális gépek"
#: dashboard/templates/dashboard/profile.html:108
#: dashboard/templates/dashboard/profile.html:110
msgid "This user have no access to any virtual machine."
msgstr "A felhasználónak egy géphez sincs hozzáférése."
#: dashboard/templates/dashboard/profile.html:122
#: dashboard/templates/dashboard/profile.html:124
msgid "Edit user"
msgstr "Felhasználó szerkesztése"
......@@ -1840,7 +1905,7 @@ msgid "disk objects without images files"
msgstr "lemezkép nélküli lemez objektumok"
#: dashboard/templates/dashboard/storage/detail.html:47
#: dashboard/templates/dashboard/storage/detail.html:57
#: dashboard/templates/dashboard/storage/detail.html:59
msgid "None"
msgstr "Nincs"
......@@ -1852,6 +1917,19 @@ msgstr "Árva lemezek"
msgid "image files without disk object in the database"
msgstr ""
#: dashboard/templates/dashboard/storage/detail.html:76
#: network/templates/network/record-list.html:21
msgid "Filter by type"
msgstr "Típus szerinti szűrés"
#: dashboard/templates/dashboard/storage/detail.html:78
#: network/templates/network/host-list.html:26
#: network/templates/network/record-list.html:22
#: network/templates/network/rule-list.html:23
#: request/templates/request/list.html:22
msgid "ALL"
msgstr "MIND"
#: dashboard/templates/dashboard/store/_list-box.html:9
#: dashboard/templates/dashboard/store/_list-box.html:27
#: dashboard/templates/dashboard/store/upload.html:4
......@@ -1943,7 +2021,7 @@ msgstr "Felsorolás"
#: dashboard/templates/dashboard/store/list.html:5
#: dashboard/templates/dashboard/store/upload.html:4
#: dashboard/templates/dashboard/vm-detail/home.html:147
#: dashboard/templates/dashboard/vm-detail/home.html:148
msgid "Store"
msgstr "Tárhely"
......@@ -2009,7 +2087,8 @@ msgid "Currently uploading to"
msgstr "Feltöltés helye:"
#: dashboard/templates/dashboard/template-edit.html:7
#: vm/models/instance.py:157 vm/models/instance.py:230 vm/models/network.py:44
#: dashboard/views/storage.py:58 vm/models/instance.py:157
#: vm/models/instance.py:230 vm/models/network.py:44
msgid "template"
msgstr "sablon"
......@@ -2049,7 +2128,7 @@ msgid "Access level rights"
msgstr "Hozzáférési jogosultsági szintek"
#: dashboard/templates/dashboard/template-edit.html:121
#: dashboard/templates/dashboard/vm-detail/access.html:27
#: dashboard/templates/dashboard/vm-detail/access.html:27 request/tables.py:39
msgid "User"
msgstr "Felhasználó"
......@@ -2283,14 +2362,6 @@ msgstr "Hozzáférés"
msgid "Abort"
msgstr "Megszakítás"
#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:65
msgid "Show less activities"
msgstr "Kevesebb tevékenység megjelenítése"
#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:67
msgid "Show all activities"
msgstr "Összes tevékenység megjelenítése"
#: dashboard/templates/dashboard/vm-detail/_network-port-add.html:17
msgid "Add"
msgstr "Hozzáadás"
......@@ -2379,23 +2450,23 @@ msgstr "Frissítés"
msgid "Expiration"
msgstr "Lejárat"
#: dashboard/templates/dashboard/vm-detail/home.html:70
#: dashboard/templates/dashboard/vm-detail/home.html:71
msgid "Suspended at:"
msgstr "Felfüggesztve:"
#: dashboard/templates/dashboard/vm-detail/home.html:76
#: dashboard/templates/dashboard/vm-detail/home.html:77
msgid "Destroyed at:"
msgstr "Megsemmisítve:"
#: dashboard/templates/dashboard/vm-detail/home.html:85
#: dashboard/templates/dashboard/vm-detail/home.html:86
msgid "Tags"
msgstr "Címkék"
#: dashboard/templates/dashboard/vm-detail/home.html:98
#: dashboard/templates/dashboard/vm-detail/home.html:99
msgid "No tag added."
msgstr "Nincs címke."
#: dashboard/templates/dashboard/vm-detail/home.html:110
#: dashboard/templates/dashboard/vm-detail/home.html:111
msgid "Add tag"
msgstr "Címke hozzáadása"
......@@ -2416,12 +2487,12 @@ msgid "edit"
msgstr "szerkesztés"
#: dashboard/templates/dashboard/vm-detail/network.html:37
#: firewall/models.py:595
#: firewall/models.py:610
msgid "IPv4 address"
msgstr "IPv4 cím"
#: dashboard/templates/dashboard/vm-detail/network.html:38
#: firewall/models.py:605
#: firewall/models.py:620
msgid "IPv6 address"
msgstr "IPv6 cím"
......@@ -2430,12 +2501,12 @@ msgid "DNS name"
msgstr "DNS név"
#: dashboard/templates/dashboard/vm-detail/network.html:52
#: network/forms.py:269
#: network/forms.py:284
msgid "IPv4"
msgstr "IPv4"
#: dashboard/templates/dashboard/vm-detail/network.html:53
#: network/forms.py:276
#: network/forms.py:291
msgid "IPv6"
msgstr "IPv6"
......@@ -2452,15 +2523,38 @@ msgstr "A VM-nek nincs IPv6 címe."
msgid "Edit raw data"
msgstr "Nyers adat szerkesztése"
#: dashboard/templates/dashboard/vm-detail/resources.html:13
#: dashboard/templates/dashboard/vm-detail/resources.html:7
msgid "Modify the resources"
msgstr "Módosítsa az erőforrásokat"
#: dashboard/templates/dashboard/vm-detail/resources.html:17
msgid "Save resources"
msgstr "Erőforrások mentése"
#: dashboard/templates/dashboard/vm-detail/resources.html:17
#: dashboard/templates/dashboard/vm-detail/resources.html:21
msgid "Stop your VM to change resources."
msgstr "Állítsa le a VM-et az erőforrások módosításához."
#: dashboard/templates/dashboard/vm-detail/resources.html:64
#: dashboard/templates/dashboard/vm-detail/resources.html:26
msgid ""
"Changing resources is only possible on virtual machines with STOPPED state. "
"We suggest to turn off the VM after submitting the request otherwise it will"
" be automatically stopped in the future when the request is accepted."
msgstr ""
"Erőforrás módosítás csak LEÁLLÍTVA állapotú gépen lehetséges. A virtuális "
"gépet ajánlott leállítani a kérés beküldése után másképpen valamikor a "
"jövőben, a kérés elfogadásakor lesz automatikusan leállítva."
#: dashboard/templates/dashboard/vm-detail/resources.html:29
#: request/forms.py:79 request/models.py:82
msgid "Message"
msgstr "Üzenet"
#: dashboard/templates/dashboard/vm-detail/resources.html:38
msgid "Request resources"
msgstr "Erőforrások igénylése"
#: dashboard/templates/dashboard/vm-detail/resources.html:87
msgid "Raw data"
msgstr "Nyers adat"
......@@ -2511,6 +2605,8 @@ msgid ""
" %(current)s/%(total)s\n"
" "
msgstr ""
"\n"
"%(current)s/%(total)s"
#: dashboard/validators.py:74
msgid "Invalid template string."
......@@ -2536,59 +2632,67 @@ msgstr "példányok száma"
msgid "Allocated memory (bytes)"
msgstr "Foglalt memória (byte)"
#: dashboard/views/group.py:150
#: dashboard/views/group.py:152
#, python-format
msgid "User \"%s\" not found."
msgstr "Nem található „%s” felhasználó."
#: dashboard/views/group.py:164
#: dashboard/views/group.py:166
msgid "Group successfully renamed."
msgstr "A csoport átnevezésre került."
#: dashboard/views/group.py:233
#: dashboard/views/group.py:235
msgid "Member successfully removed from group."
msgstr "A csoporttag eltávolításra került."
#: dashboard/views/group.py:266
#: dashboard/views/group.py:268
msgid "Future user successfully removed from group."
msgstr "A leendő csoporttag eltávolításra került."
#: dashboard/views/group.py:288
#: dashboard/views/group.py:290
msgid "Group successfully deleted."
msgstr "A csoport törlésre került."
#: dashboard/views/group.py:317
#: dashboard/views/group.py:319
msgid "Create a Group"
msgstr "Csoport létrehozása"
#: dashboard/views/group.py:333
#: dashboard/views/group.py:335
msgid "Group successfully created."
msgstr "A csoport létrehozásra került."
#: dashboard/views/group.py:347
#: dashboard/views/group.py:349
msgid "Group is successfully updated."
msgstr "A csoport frissítésre került."
#: dashboard/views/node.py:127
#: dashboard/views/node.py:128
msgid "Node successfully renamed."
msgstr "A csomópont átnevezésre került."
#: dashboard/views/node.py:217
#: dashboard/views/node.py:218
msgid "Create a node"
msgstr "Új csomópont hozzáadása"
#: dashboard/views/node.py:241
#: dashboard/views/node.py:242
msgid "Node successfully created."
msgstr "A csomópont létrehozásra került."
#: dashboard/views/node.py:252
#: dashboard/views/node.py:253
msgid "Node successfully deleted."
msgstr "A csomópont törlésre került."
#: dashboard/views/node.py:288
#: dashboard/views/node.py:289
msgid "Trait successfully added to node."
msgstr "A csomópontjellemző hozzáadásra került."
#: dashboard/views/storage.py:51
msgid "The DataStore is offline."
msgstr "Az adattár nem elérhető."
#: dashboard/views/storage.py:57
msgid "virtual machine"
msgstr "virtuális gép"
#: dashboard/views/store.py:73
msgid "No store."
msgstr "Nincs tárhely."
......@@ -2686,11 +2790,11 @@ msgstr ""
#: dashboard/views/template.py:444
#, python-format
msgid ""
"%(user)s offered you to take the ownership of his/her template called "
"%(owner)s offered you to take the ownership of his/her template called "
"%(instance)s. <a href=\"%(token)s\" class=\"btn btn-success btn-"
"small\">Accept</a>"
msgstr ""
"%(user)s át kívánja ruházni %(instance)s nevű sablonját Önre. <a "
"%(owner)s át kívánja ruházni %(instance)s nevű sablonját Önre. <a "
"href=\"%(token)s\" class=\"btn btn-success btn-small\">Elfogadás</a>"
#: dashboard/views/user.py:150
......@@ -2856,79 +2960,80 @@ msgstr "Átruházás elfogadva"
#: dashboard/views/util.py:667
#, python-format
msgid "Your ownership offer of %(instance)s has been accepted by %(user)s."
msgstr "%(instance)s gépre vonatkozó átruházási ajánlatát elfogadta %(user)s."
msgid "Your ownership offer of %(instance)s has been accepted by %(owner)s."
msgstr ""
"%(instance)s gépre vonatkozó átruházási ajánlatát elfogadta %(owner)s."
#: dashboard/views/util.py:716
msgid "Only the owners can delete the selected object."
msgstr "Csak a tulajdonos törölheti a kiválasztott objektumot."
#: dashboard/views/vm.py:88
#: dashboard/views/vm.py:90
msgid "console access"
msgstr "konzolhozzáférés"
#: dashboard/views/vm.py:197
#: dashboard/views/vm.py:203
msgid "VM successfully renamed."
msgstr "A virtuális gép átnevezésre került."
#: dashboard/views/vm.py:221
#: dashboard/views/vm.py:227
msgid "VM description successfully updated."
msgstr "A VM leírása megváltoztatásra került."
#: dashboard/views/vm.py:609
#: dashboard/views/vm.py:615
msgid "The token has expired."
msgstr "A token lejárt."
#: dashboard/views/vm.py:827
#: dashboard/views/vm.py:841
#, python-format
msgid "Failed to execute %(op)s operation on instance %(instance)s."
msgstr "%(op)s végrehajtása meghiúsult a következőn: %(instance)s."
#: dashboard/views/vm.py:843
#: dashboard/views/vm.py:857
#, python-format
msgid "You are not permitted to execute %(op)s on instance %(instance)s."
msgstr "Nem engedélyezett a(z) %(op)s végrehajtása a(z) %(instance)s gépen."
#: dashboard/views/vm.py:1035
#: dashboard/views/vm.py:1049
msgid "Customize VM"
msgstr "VM testreszabása"
#: dashboard/views/vm.py:1043
#: dashboard/views/vm.py:1057
msgid "Create a VM"
msgstr "VM létrehozása"
#: dashboard/views/vm.py:1096
#: dashboard/views/vm.py:1112
#, python-format
msgid "Successfully created %(count)d VM."
msgid_plural "Successfully created %(count)d VMs."
msgstr[0] "%(count)d VM létrehozásra került."
msgstr[1] "%(count)d VM létrehozásra került."
#: dashboard/views/vm.py:1101
#: dashboard/views/vm.py:1117
msgid "VM successfully created."
msgstr "VM létrehozásra került."
#: dashboard/views/vm.py:1132
#: dashboard/views/vm.py:1148
#, python-format
msgid "Instance limit (%d) exceeded."
msgstr "A példányok létrehozási korlátját (%d) túllépte."
#: dashboard/views/vm.py:1200
#: dashboard/views/vm.py:1216
msgid "About CIRCLE Client"
msgstr "A CIRCLE kliensről"
#: dashboard/views/vm.py:1290
#: dashboard/views/vm.py:1306
msgid "transfer ownership"
msgstr "tulajdon átruházása"
#: dashboard/views/vm.py:1300
#: dashboard/views/vm.py:1316
#, python-format
msgid ""
"%(user)s offered you to take the ownership of his/her virtual machine called"
" %(instance)s. <a href=\"%(token)s\" class=\"btn btn-success btn-"
"%(owner)s offered you to take the ownership of his/her virtual machine "
"called %(instance)s. <a href=\"%(token)s\" class=\"btn btn-success btn-"
"small\">Accept</a>"
msgstr ""
"%(user)s át kívánja ruházni %(instance)s nevű virtuális gépét Önre. <a "
"%(owner)s át kívánja ruházni %(instance)s nevű virtuális gépét Önre. <a "
"href=\"%(token)s\" class=\"btn btn-success btn-small\">Elfogadás</a>"
#: firewall/fields.py:42
......@@ -3024,9 +3129,9 @@ msgstr "irány"
msgid "If the rule matches egress or ingress packets."
msgstr "A szabály kimenő vagy bejövő csomagokra illeszkedik."
#: firewall/models.py:73 firewall/models.py:345 firewall/models.py:534
#: firewall/models.py:556 firewall/models.py:612 firewall/models.py:982
#: firewall/models.py:1005 firewall/models.py:1074 vm/models/instance.py:136
#: firewall/models.py:73 firewall/models.py:345 firewall/models.py:539
#: firewall/models.py:566 firewall/models.py:627 firewall/models.py:1002
#: firewall/models.py:1030 firewall/models.py:1101 vm/models/instance.py:136
#: vm/models/instance.py:226
msgid "description"
msgstr "leírás"
......@@ -3114,17 +3219,17 @@ msgstr "Célport számának átírása a megadottra NAT esetén."
msgid "external IPv4 address"
msgstr "külső IPv4 cím"
#: firewall/models.py:123 firewall/models.py:388 firewall/models.py:539
#: firewall/models.py:561 firewall/models.py:631
#: firewall/models.py:123 firewall/models.py:388 firewall/models.py:544
#: firewall/models.py:571 firewall/models.py:646
msgid "created at"
msgstr "létrehozva"
#: firewall/models.py:126 firewall/models.py:392 firewall/models.py:541
#: firewall/models.py:563 firewall/models.py:633
#: firewall/models.py:126 firewall/models.py:392 firewall/models.py:546
#: firewall/models.py:573 firewall/models.py:648
msgid "modified at"
msgstr "módosítva"
#: firewall/models.py:129 firewall/models.py:620
#: firewall/models.py:129 firewall/models.py:395 firewall/models.py:635
#: network/templates/network/vlan-create.html:8
#: network/templates/network/vlan-edit.html:8 vm/models/network.py:39
#: vm/models/network.py:67
......@@ -3135,7 +3240,8 @@ msgstr "vlan"
msgid "Vlan the rule applies to (if type is vlan)."
msgstr "Erre a vlanra vonatkozik a szabály (ha a típus vlan)."
#: firewall/models.py:134 network/templates/network/vlan-group-create.html:8
#: firewall/models.py:134 firewall/models.py:549
#: network/templates/network/vlan-group-create.html:8
#: network/templates/network/vlan-group-edit.html:8
msgid "vlan group"
msgstr "vlan-csoport"
......@@ -3144,7 +3250,7 @@ msgstr "vlan-csoport"
msgid "Group of vlans the rule applies to (if type is vlan)."
msgstr "Erre a vlan-csoportra vonatkozik a szabály (ha a típus vlan)."
#: firewall/models.py:138 firewall/models.py:998 firewall/models.py:1115
#: firewall/models.py:138 firewall/models.py:1023 firewall/models.py:1152
#: network/templates/network/host-create.html:8
#: network/templates/network/host-edit.html:8 vm/models/network.py:69
#: vm/models/node.py:126
......@@ -3155,7 +3261,8 @@ msgstr "gép"
msgid "Host the rule applies to (if type is host)."
msgstr "Erre a gépre vonatkozik a szabály (ha a típus gép)."
#: firewall/models.py:142 network/templates/network/group-create.html:8
#: firewall/models.py:142 firewall/models.py:576
#: network/templates/network/group-create.html:8
#: network/templates/network/group-edit.html:8
msgid "host group"
msgstr "gépcsoport"
......@@ -3164,7 +3271,8 @@ msgstr "gépcsoport"
msgid "Group of hosts the rule applies to (if type is host)."
msgstr "Erre a gépcsoportra vonatkozik a szabály (ha a típus gép)."
#: firewall/models.py:146 network/templates/network/firewall-create.html:6
#: firewall/models.py:146 firewall/models.py:954
#: network/templates/network/firewall-create.html:6
#: network/templates/network/firewall-edit.html:7
msgid "firewall"
msgstr "tűzfal"
......@@ -3350,52 +3458,60 @@ msgstr ""
"tiltásához adja meg a „manual” értéket, engedélyezéséhez az első és utolsó "
"érvényes címet szóközzel elválasztva."
#: firewall/models.py:399
#: firewall/models.py:396 firewall/models.py:536
msgid "vlans"
msgstr "vlanok"
#: firewall/models.py:404
msgid "You cannot specify an IPv6 template if there is no IPv6 network set."
msgstr "Nem adhat meg IPv6 sablont, ha nincs IPv6 hálózat beállítva."
#: firewall/models.py:405
#: firewall/models.py:410
#, python-format
msgid "%(ip6)s (translated from %(ip4)s) is outside of the IPv6 network."
msgstr "%(ip6)s (ebből képezve: %(ip4)s) kívül esik az IPv6 hálózaton."
#: firewall/models.py:449
#: firewall/models.py:454
msgid "IPv6 network is too small to map IPv4 addresses to it."
msgstr "Az IPv6 hálózat túl kicsi az IPv4 címek leképezéséhez."
#: firewall/models.py:506
#: firewall/models.py:511
msgid "All IP addresses are already in use."
msgstr "Minden IP cím használatban van."
#: firewall/models.py:529 firewall/models.py:555
#: firewall/models.py:534 firewall/models.py:565
msgid "The name of the group."
msgstr "A csoport neve."
#: firewall/models.py:531
msgid "vlans"
msgstr "vlanok"
#: firewall/models.py:532
#: firewall/models.py:537
msgid "The vlans which are members of the group."
msgstr "A csoport tagjait képező vlanok."
#: firewall/models.py:535 firewall/models.py:557
#: firewall/models.py:540 firewall/models.py:567
msgid "Description of the group."
msgstr "A csoport leírása."
#: firewall/models.py:578 network/tables.py:139 storage/models.py:52
#: firewall/models.py:550
msgid "vlan groups"
msgstr "vlan-csoportok"
#: firewall/models.py:577
msgid "host groups"
msgstr "gépcsoportok"
#: firewall/models.py:593 network/tables.py:139 storage/models.py:52
msgid "hostname"
msgstr "gépnév"
#: firewall/models.py:579
#: firewall/models.py:594
msgid "The alphanumeric hostname of the host, the first part of the FQDN."
msgstr "A gép alfanumerikus gépneve, az FQDN első része."
#: firewall/models.py:585
#: firewall/models.py:600
msgid "reverse"
msgstr "reverz"
#: firewall/models.py:586
#: firewall/models.py:601
msgid ""
"The fully qualified reverse hostname of the host, if different than "
"hostname.domain."
......@@ -3403,137 +3519,165 @@ msgstr ""
"A gép teljes reverz tartományneve, amennyiben különbözik ettől: "
"gépnév.tartomány."
#: firewall/models.py:590 network/tables.py:138
#: firewall/models.py:605 network/tables.py:138
msgid "MAC address"
msgstr "MAC cím"
#: firewall/models.py:591
#: firewall/models.py:606
msgid ""
"The MAC (Ethernet) address of the network interface. For example: "
"99:AA:BB:CC:DD:EE."
msgstr "A hálózati interfész MAC (Ethernet) címe. Például 99:AA:BB:CC:DD:EE."
#: firewall/models.py:596
#: firewall/models.py:611
msgid "The real IPv4 address of the host, for example 10.5.1.34."
msgstr "A gép valódi IPv4 címe, például 10.5.1.34."
#: firewall/models.py:600
#: firewall/models.py:615
msgid "WAN IPv4 address"
msgstr "WAN IPv4 cím"
#: firewall/models.py:601
#: firewall/models.py:616
msgid ""
"The public IPv4 address of the host on the wide area network, if different."
msgstr "A gép nyilvános IPv4 címe a nagy kiterjedésű hálózaton, ha eltér."
#: firewall/models.py:606
#: firewall/models.py:621
msgid "The global IPv6 address of the host, for example 2001:db:88:200::10."
msgstr "A gép globális IPv6 címe, például 2001:db:88:200::10."
#: firewall/models.py:608
#: firewall/models.py:623
msgid "shared IP"
msgstr "osztott IP"
#: firewall/models.py:610
#: firewall/models.py:625
msgid "If the given WAN IPv4 address is used by multiple hosts."
msgstr "A WAN IPv4 címet több gép használja-e."
#: firewall/models.py:613
#: firewall/models.py:628
msgid "What is this host for, what kind of machine is it."
msgstr "Mi a gép célja, milyen gép ez."
#: firewall/models.py:616
#: firewall/models.py:631
msgid "Notes"
msgstr "Jegyzetek"
#: firewall/models.py:617
#: firewall/models.py:632
msgid "location"
msgstr "elhelyezés"
#: firewall/models.py:619
#: firewall/models.py:634
msgid "The physical location of the machine."
msgstr "A gép fizikai helye."
#: firewall/models.py:622
#: firewall/models.py:637
msgid "Vlan network that the host is part of."
msgstr "Az a vlan hálózat, amelynek a gép része."
#: firewall/models.py:625
#: firewall/models.py:640
msgid "The person responsible for this host."
msgstr "A gépért felelős személy."
#: firewall/models.py:627
#: firewall/models.py:642
msgid "groups"
msgstr "csoportok"
#: firewall/models.py:629
#: firewall/models.py:644
msgid "Host groups the machine is part of."
msgstr "Gépcsoportok, amelyeknek tagja a gép."
#: firewall/models.py:682
#: firewall/models.py:697
msgid "If shared_ip has been checked, external_ipv4 has to be unique."
msgstr ""
"Amennyiben az osztott IP mező igaz, a külső IPv4 cím mező egyedi kell "
"legyen."
#: firewall/models.py:685
#: firewall/models.py:700
msgid "You can't use another host's NAT'd address as your own IPv4."
msgstr "Nem használható másik gép NAT-olt címe saját IPv4 címként."
#: firewall/models.py:790
#: firewall/models.py:805
#, python-format
msgid "All %s ports are already in use."
msgstr "Minden %s port használatban van."
#: firewall/models.py:808
#: firewall/models.py:823
#, python-format
msgid "Port %(proto)s %(public)s is already in use."
msgstr "A(z) %(public)s %(proto)s port használatban van."
#: firewall/models.py:978 firewall/models.py:1007 firewall/models.py:1076
#: firewall/models.py:1103 firewall/models.py:1124
#: firewall/models.py:955
msgid "firewalls"
msgstr "tűzfalak"
#: firewall/models.py:998 firewall/models.py:1032 firewall/models.py:1103
#: firewall/models.py:1135 firewall/models.py:1161
msgid "created_at"
msgstr "létrehozva"
#: firewall/models.py:980 firewall/models.py:1009 firewall/models.py:1078
#: firewall/models.py:1105 firewall/models.py:1126
#: firewall/models.py:1000 firewall/models.py:1034 firewall/models.py:1105
#: firewall/models.py:1137 firewall/models.py:1163
msgid "modified_at"
msgstr "módosítva"
#: firewall/models.py:981 firewall/models.py:1003
#: firewall/models.py:1001 firewall/models.py:1028
msgid "ttl"
msgstr "ttl"
#: firewall/models.py:996 network/templates/network/domain-create.html:8
#: firewall/models.py:1005 firewall/models.py:1021
#: network/templates/network/domain-create.html:8
#: network/templates/network/domain-edit.html:8
msgid "domain"
msgstr "tartomány"
#: firewall/models.py:1002
#: firewall/models.py:1006
msgid "domains"
msgstr "tartományok"
#: firewall/models.py:1027
msgid "address"
msgstr "cím"
#: firewall/models.py:1024
#: firewall/models.py:1049
msgid "Address must be specified!"
msgstr "A cím megadása kötelező."
#: firewall/models.py:1037
#: firewall/models.py:1062
msgid "Unknown record type."
msgstr "Ismeretlen rekordtípus."
#: firewall/models.py:1070
#: firewall/models.py:1086 network/templates/network/record-create.html:8
#: network/templates/network/record-edit.html:8
msgid "record"
msgstr "rekord"
#: firewall/models.py:1087
msgid "records"
msgstr "rekordok"
#: firewall/models.py:1097
msgid "untagged vlan"
msgstr "untagged vlan"
#: firewall/models.py:1073
#: firewall/models.py:1100
msgid "tagged vlans"
msgstr "tagged vlanok"
#: firewall/models.py:1095
#: firewall/models.py:1108 firewall/models.py:1133
#: network/templates/network/switch-port-create.html:8
#: network/templates/network/switch-port-edit.html:8
msgid "switch port"
msgstr "switch port"
#: firewall/models.py:1109
msgid "switch ports"
msgstr "switch portok"
#: firewall/models.py:1127
msgid "interface"
msgstr "interfész"
#: firewall/models.py:1096
#: firewall/models.py:1128
msgid ""
"The name of network interface the gateway should serve this network on. For "
"example eth2."
......@@ -3541,39 +3685,43 @@ msgstr ""
"Azon hálózati interfész nevve, amelyen az útválasztó ezt a hálózatot "
"kiszolgálja. Például eth2."
#: firewall/models.py:1101 network/templates/network/switch-port-create.html:8
#: network/templates/network/switch-port-edit.html:8
msgid "switch port"
msgstr "switch port"
#: firewall/models.py:1140
#, fuzzy
#| msgid "No ethernet device"
msgid "ethernet device"
msgstr "Nincs ethernet-eszköz"
#: firewall/models.py:1117
#: firewall/models.py:1141
#, fuzzy
#| msgid "No ethernet device"
msgid "ethernet devices"
msgstr "Nincs ethernet-eszköz"
#: firewall/models.py:1154
msgid "reason"
msgstr "indok"
#: firewall/models.py:1119
#: firewall/models.py:1156
msgid "short message"
msgstr "rövid üzenet"
#: firewall/models.py:1122
#: firewall/models.py:1159
msgid "whitelisted"
msgstr ""
#: firewall/models.py:1128
#| msgid "Expiration"
#: firewall/models.py:1165
msgid "expires at"
msgstr "lejár"
#: firewall/models.py:1138
#: firewall/models.py:1175
msgid "blacklist item"
msgstr "tiltólista eleme"
#: firewall/models.py:1139 network/templates/network/blacklist-create.html:8
#: network/templates/network/blacklist-edit.html:8
msgid "blacklist"
msgstr "tiltólista"
#: firewall/models.py:1176
msgid "blacklist items"
msgstr "tiltólista elemek"
#: firewall/views.py:79
#| msgid "Enter a valid IP address. %s"
msgid "Invalid IP address."
msgstr "Érvénytelen IP cím."
......@@ -3605,31 +3753,31 @@ msgstr ""
"Egy csomópont sem biztosítja a virtuális gép indításához szükséges "
"jellemzőket."
#: network/forms.py:146
#: network/forms.py:151
msgid "Generate random address."
msgstr "Véletlenszerű cím generálása."
#: network/forms.py:149
#: network/forms.py:154
msgid "Generate IPv6 pair of IPv4 address."
msgstr "IPv4-es cím IPv6-os párjának generálása."
#: network/forms.py:154
#: network/forms.py:159
msgid "Information"
msgstr "Információ"
#: network/forms.py:217
#: network/forms.py:227
msgid "External"
msgstr "Külső"
#: network/forms.py:280
#: network/forms.py:295
msgid "Generate sensible template."
msgstr "Ésszerű sablon generálása."
#: network/forms.py:284
#: network/forms.py:299
msgid "Domain name service"
msgstr "DNS szolgáltatás"
#: network/forms.py:289
#: network/forms.py:304
msgid "Info"
msgstr "Infó"
......@@ -3661,6 +3809,11 @@ msgstr "Nincs kapcsolódó szabály."
msgid "dashboard"
msgstr "műszerfal"
#: network/templates/network/blacklist-create.html:8
#: network/templates/network/blacklist-edit.html:8
msgid "blacklist"
msgstr "tiltólista"
#: network/templates/network/blacklist-create.html:12
msgid "Create a blacklist item"
msgstr "Tiltólista elemének létrehozása"
......@@ -3887,12 +4040,6 @@ msgstr "összes gép"
msgid "Filter by vlans"
msgstr "Vlan szerinti szűrés"
#: network/templates/network/host-list.html:26
#: network/templates/network/record-list.html:22
#: network/templates/network/rule-list.html:23
msgid "ALL"
msgstr "MIND"
#: network/templates/network/index.html:11 templates/info/help.html:32
msgid "Dashboard"
msgstr "Műszerfal"
......@@ -3930,15 +4077,11 @@ msgstr "Legutóbbi tiltólisták"
msgid "IP"
msgstr "IP"
#: network/templates/network/index.html:45
#: network/templates/network/index.html:45 request/models.py:83
#: request/templates/request/detail.html:127
msgid "Reason"
msgstr "Indok"
#: network/templates/network/record-create.html:8
#: network/templates/network/record-edit.html:8
msgid "record"
msgstr "rekord"
#: network/templates/network/record-create.html:12
#: network/templates/network/record-list.html:11
msgid "Create a new record"
......@@ -3952,10 +4095,6 @@ msgstr "Rekord törlése"
msgid "list of all records"
msgstr "összes rekord"
#: network/templates/network/record-list.html:21
msgid "Filter by type"
msgstr "Típus szerinti szűrés"
#: network/templates/network/rule-create.html:12
#: network/templates/network/rule-list.html:12
msgid "Create a new rule"
......@@ -4026,35 +4165,29 @@ msgstr "összes vlan"
#: network/views.py:140
#, python-format
#| msgid "Successfully modified blacklist item%(ipv4)s - %(type)s!"
msgid "Successfully modified blacklist item %(ipv4)s."
msgstr "Tiltólista eleme sikeresen módosítva (%(ipv4)s)."
#: network/views.py:157
#, python-format
#| msgid "Successfully created blacklist item %(ipv4)s - %(type)s!"
msgid "Successfully created blacklist item %(ipv4)s"
msgstr "Tiltólista eleme sikeresen létrehozva (%(ipv4)s)."
#: network/views.py:192
#, python-format
#| msgid "Successfully modified domain %(name)s!"
msgid "Successfully modified domain %(name)s."
msgstr "A(z) %(name)s tartománynév módosításra került."
#: network/views.py:219
#, python-format
#| msgid "Successfully created domain %(name)s!"
msgid "Successfully created domain %(name)s."
msgstr "A(z) %(name)s tartománynév létrehozásra került."
#: network/views.py:236 network/views.py:563 network/views.py:845
#| msgid "Object name does not match!"
msgid "Object name does not match."
msgstr "Az objektum neve nem egyezik."
#: network/views.py:240
#| msgid "Domain successfully deleted!"
msgid "Domain successfully deleted."
msgstr "A tartománynév törlésre került."
......@@ -4072,40 +4205,33 @@ msgstr "A tűzfal létrehozásra került."
#: network/views.py:350
#, python-format
#| msgid "Successfully created host group %(name)s!"
msgid "Successfully created host group %(name)s."
msgstr "%(name)s gépcsoport létrehozásra került."
#: network/views.py:358
#, python-format
#| msgid "Successfully modified host group %(name)s!"
msgid "Successfully modified host group %(name)s."
msgstr "%(name)s gépcsoport módosításra került."
#: network/views.py:442
#, python-format
#| msgid "Successfully modified host %(hostname)s!"
msgid "Successfully modified host %(hostname)s."
msgstr "%(hostname)s gép módosításra került."
#: network/views.py:515
#, python-format
#| msgid "Successfully created host %(hostname)s!"
msgid "Successfully created host %(hostname)s."
msgstr "%(hostname)s gép létrehozásra került."
#: network/views.py:567
#| msgid "Host successfully deleted!"
msgid "Host successfully deleted."
msgstr "A gép törlésre került."
#: network/views.py:597
#| msgid "Successfully modified record!"
msgid "Successfully modified record."
msgstr "A rekord módosításra került."
#: network/views.py:616
#| msgid "Successfully created record!"
msgid "Successfully created record."
msgstr "A rekord létrehozásra került."
......@@ -4122,69 +4248,57 @@ msgid "Firewall"
msgstr "Tűzfal"
#: network/views.py:682
#| msgid "Successfully modified rule!"
msgid "Successfully modified rule."
msgstr "A szabály módosításra került."
#: network/views.py:702
#| msgid "Successfully created rule!"
msgid "Successfully created rule."
msgstr "A szabály létrehozásra került."
#: network/views.py:738
#| msgid "Succesfully modified switch port!"
msgid "Succesfully modified switch port."
msgstr "A switch-port módosításra került."
#: network/views.py:757
#| msgid "Successfully created switch port!"
msgid "Successfully created switch port."
msgstr "A switch-port létrehozásra került."
#: network/views.py:804
#, python-format
#| msgid "Succesfully modified vlan %(name)s!"
msgid "Succesfully modified vlan %(name)s."
msgstr "A(z) %(name)s vlan módosításra került."
#: network/views.py:824
#, python-format
#| msgid "Successfully created vlan %(name)s!"
msgid "Successfully created vlan %(name)s."
msgstr "A(z) %(name)s vlan létrehozásra került."
#: network/views.py:849
#| msgid "Vlan successfully deleted!"
msgid "Vlan successfully deleted."
msgstr "A vlan törlésre került."
#: network/views.py:891
#, python-format
#| msgid "Successfully modified vlan group %(name)s!"
msgid "Successfully modified vlan group %(name)s."
msgstr "A(z) %(name)s vlan-csoport módosításra került."
#: network/views.py:904
#, python-format
#| msgid "Successfully created vlan group %(name)s!"
msgid "Successfully created vlan group %(name)s."
msgstr "A(z) %(name)s vlan-csoport módosításra került."
#: network/views.py:936
#, python-format
#| msgid "Successfully removed %(host)s from %(group)s group!"
msgid "Successfully removed %(host)s from %(group)s group."
msgstr "A(z) %(host)s csoport törlésre került a(z) %(group)s csoportból."
#: network/views.py:952
#, python-format
#| msgid "Successfully added %(host)s to group %(group)s!"
msgid "Successfully added %(host)s to group %(group)s."
msgstr "A(z) %(host)s csoport hozzáadásra került a(z) %(group)s csoporthoz."
#: network/views.py:971
#, python-format
#| msgid "Successfully deleted ethernet device %(name)s!"
msgid "Successfully deleted ethernet device %(name)s."
msgstr "A(z) %(name)s ethernet-eszköz törlésre került."
......@@ -4194,15 +4308,304 @@ msgid "Successfully added %(name)s to this switch port"
msgstr "%(name)s hozzáadásra került a switch-porthoz."
#: network/views.py:996
#| msgid "Ethernet device name cannot be empty!"
msgid "Ethernet device name cannot be empty."
msgstr "Az ethernet-eszköz megadása kötelező."
#: network/views.py:999
#| msgid "There is already an ethernet device with that name!"
msgid "There is already an ethernet device with that name."
msgstr "Már létezik a megadott nevű ethernet-eszköz."
#: request/forms.py:76
msgid "Template share"
msgstr "Sablon megosztás"
#: request/models.py:68 vm/models/instance.py:221
msgid "pending"
msgstr "függő"
#: request/models.py:69
msgid "accepted"
msgstr "elfogadott"
#: request/models.py:70
msgid "declined"
msgstr "elutasított"
#: request/models.py:77
msgid "resource request"
msgstr "erőforrás igénylés"
#: request/models.py:78
msgid "lease request"
msgstr "bérlet igénylés"
#: request/models.py:79
msgid "template access request"
msgstr "sablon hozzáférés igénylés"
#: request/models.py:126
msgid "Request accepted"
msgstr "Kérés elfogadva"
#: request/models.py:137
#, python-format
msgid ""
"Your <a href=\"%(url)s\">request</a> was declined because of the following "
"reason: %(reason)s"
msgstr ""
"Az <a href=\"%(url)s\">igénylése</a> el lett utasítva az alábbi indokkal: "
"%(reason)s"
#: request/models.py:142
msgid "Request declined"
msgstr "Igénylés elutasítva"
#: request/models.py:151 vm/models/common.py:154
#, python-format
msgid "%(name)s (suspend: %(s)s, remove: %(r)s)"
msgstr "%(name)s (felfüggesztés: %(s)s, törlés: %(r)s)"
#: request/models.py:171 vm/models/common.py:39
msgid "number of cores"
msgstr "magok száma"
#: request/models.py:172 vm/models/common.py:40
msgid "Number of virtual CPU cores available to the virtual machine."
msgstr "A virtuális gép számára elérhető CPU-magok száma."
#: request/models.py:176 vm/models/common.py:44
msgid "Mebibytes of memory."
msgstr "Memória mebibyte-okban."
#: request/models.py:178 vm/models/common.py:52 vm/models/node.py:124
msgid "priority"
msgstr "prioritás"
#: request/models.py:179 vm/models/common.py:53
msgid "CPU priority."
msgstr "CPU prioritás."
#: request/models.py:191
#, python-format
msgid ""
"The resources of <a href=\"%(url)s\">%(name)s</a> were changed. Number of "
"cores: %(num_cores)d, RAM size: <span class=\"nowrap\">%(ram_size)d "
"MiB</span>, CPU priority: %(priority)d/100."
msgstr ""
"<a href=\"%(url)s\">%(name)s</a> erőforrásai módosultak. Magok száma: "
"%(num_cores)d, RAM-mennyiség: <span class=\"nowrap\">%(ram_size)d "
"MiB</span>, CPU prioritás: %(priority)d/100."
#: request/models.py:215
#, python-format
msgid ""
"The lease of <a href=\"%(url)s\">%(name)s</a> got extended. (suspend: "
"%(suspend)s, remove: %(remove)s)"
msgstr ""
"<a href=\"%(url)s\">%(name)s</a> bérlete meghosszabbítva. (felfüggesztés: "
"%(suspend)s, törlés: %(remove)s)"
#: request/models.py:243
#, python-format
msgid "You got access to the following template: %s"
msgid_plural "You got access to the following templates: %s"
msgstr[0] "Az alábbi sablonhoz szerzett hozzáférést: %s"
msgstr[1] "Az alábbi sablonokhoz szerzett hozzáférést: %s"
#: request/models.py:254
#, python-format
msgid ""
"A new <a href=\"%(request_url)s\">%(request_type)s</a> was submitted by <a "
"href=\"%(user_url)s\">%(display_name)s</a>."
msgstr ""
"Egy új <a href=\"%(request_url)s\">%(request_type)s</a> lett beküldve <a "
"href=\"%(user_url)s\">%(display_name)s</a> által."
#: request/models.py:265
#, python-format
msgid "New %(request_type)s"
msgstr "Új %(request_type)s"
#: request/models.py:269
msgid "Request submitted"
msgstr "Igénylés beküldve"
#: request/models.py:270
#, python-format
msgid ""
"You can view the request's status at this <a "
"href=\"%(request_url)s\">link</a>."
msgstr ""
"Az igénylés állapota megtekinthető az alábbi <a "
"href=\"%(request_url)s\">linken</a>."
#: request/tables.py:53
msgid "No more requests."
msgstr "Nincs több igénylés."
#: request/templates/request/_request-template-form.html:9
#: request/templates/request/detail.html:64
msgid "Level"
msgstr "Szint"
#: request/templates/request/_request-template-form.html:16
msgid "For users who want to share the template with others."
msgstr ""
"Azon felhasználók számára akik a sablonokat meg szeretnék osztani másokkal."
#: request/templates/request/_request-template-form.html:18
msgid "For users who want to start a virtual machine."
msgstr "Azon felhasználók számára akik virtuális gépet akarnak indítani."
#: request/templates/request/detail.html:7
msgid "Request"
msgstr "Igénylés"
#: request/templates/request/detail.html:42
#: request/templates/request/detail.html:69
msgid "VM name"
msgstr "Virtuális gép neve"
#: request/templates/request/detail.html:44
#: request/templates/request/detail.html:76
msgid "VM description"
msgstr "Virtuális gép leírása"
#: request/templates/request/detail.html:46
msgid "Current lease"
msgstr "Jelenlegi bérlet"
#: request/templates/request/detail.html:48
msgid "Requested lease"
msgstr "Igényelt bérlet"
#: request/templates/request/detail.html:54
msgid "Template type"
msgstr "Sablon típus"
#: request/templates/request/detail.html:80
msgid "(old values in parentheses)"
msgstr "(régi értékek zárójelben)"
#: request/templates/request/detail.html:83
msgid "Number of cores"
msgstr "Magok száma"
#: request/templates/request/detail.html:85
msgid "Ram size"
msgstr "RAM-méret"
#: request/templates/request/detail.html:99
msgid "Reason (sent to the user if the request is declined)"
msgstr "Indok (elutasítás esetén a felhasználó megkapja)"
#: request/templates/request/detail.html:103
msgid "Decline"
msgstr "Elutasít"
#: request/templates/request/detail.html:107
msgid "You can't accept this request because of the VM's state."
msgstr "A virtuális gép állapota miatt a kérés nem elfogadható."
#: request/templates/request/detail.html:114
msgid "Accept"
msgstr "Elfogadás"
#: request/templates/request/detail.html:122
#, python-format
msgid ""
"\n"
" Closed %(closed)s by <a href=\"%(user.profile.get_absolute_url)s\">%(user)s</a>\n"
" "
msgstr ""
"\n"
"Lezárva <a href=\"%(user.profile.get_absolute_url)s\">%(user)s</a> által %(closed)s"
#: request/templates/request/lease-type-form.html:8
msgid "lease type"
msgstr "bérlet típus"
#: request/templates/request/lease-type-form.html:33
msgid "New lease type"
msgstr "Új bérlet típus"
#: request/templates/request/list.html:15
#: request/templates/request/type-list.html:6
#: request/templates/request/type-list.html:24
msgid "Request types"
msgstr "Kérés típusok"
#: request/templates/request/list.html:21
msgid "Filter by status"
msgstr "Állapot szerinti szűrés"
#: request/templates/request/request-lease.html:12
msgid "Request new lease"
msgstr "Új bérlet igénylése"
#: request/templates/request/request-resource.html:13
#: request/templates/request/request-resource.html:29
msgid "Request new resources"
msgstr "Új erőforrások igénylése"
#: request/templates/request/request-template.html:12
msgid "Request template access"
msgstr "Sablon hozzáférés igénylése"
#: request/templates/request/template-type-form.html:8
msgid "template access type"
msgstr "sablon hozzáférés típus"
#: request/templates/request/template-type-form.html:33
msgid "New Template Access type"
msgstr "Új sablon hozzáférés típus"
#: request/templates/request/type-list.html:17
msgid "new lease type"
msgstr "új bérlet típus"
#: request/templates/request/type-list.html:21
msgid "new template access type"
msgstr "új sablon hozzáférés típus"
#: request/templates/request/type-list.html:28
#, fuzzy
#| msgid "The system cannot be used for illegal activites.\n"
msgid ""
"\n"
" Lease types are used for sharing leases. User can request longer ones via these.\n"
" "
msgstr ""
"\n"
"A rendszer nem használható jogszerűtlen célokra."
#: request/templates/request/type-list.html:37
msgid ""
"\n"
" Using template access types users can request multiple templates with user with operator or user level access.\n"
" "
msgstr ""
#: request/views.py:106
#| msgid "Template successfully deleted."
msgid "Template access type successfully updated."
msgstr "A sablon hozzáférés típus frissítésre került."
#: request/views.py:114
#| msgid "Interface successfully created."
msgid "New template access type successfully created."
msgstr "A sablon hozzáférés típus létrehozásra került."
#: request/views.py:131
#| msgid "Lease successfully deleted."
msgid "Lease type successfully updated."
msgstr "A bérlet típus frissítésre kerül."
#: request/views.py:139
#| msgid "Node successfully created."
msgid "New lease type successfully created."
msgstr "A bérlet típus létrehozása került."
#: storage/models.py:50
msgid "path"
msgstr "útvonal"
......@@ -4321,18 +4724,22 @@ msgstr ""
msgid "Operation aborted by user."
msgstr "A műveletet a felhasználó megszakította."
#: templates/403.html:6 templates/500.html:6
msgid ":("
msgstr ":("
#: templates/403.html:18
msgid "Forbidden"
msgstr ""
#: templates/404.html:4 templates/404.html.py:6
msgid "Page not found"
msgstr "Az oldal nem található"
#: templates/404.html:9
#: templates/404.html:15
msgid "This page does not exist."
msgstr "Az oldal nem létezik."
#: templates/500.html:6
msgid ":("
msgstr ":("
#: templates/500.html:18
msgid "Internal Server Error... Please leave the server alone..."
msgstr "Kiszolgálóoldali hiba. Ne bántsa a szervert."
......@@ -4909,18 +5316,6 @@ msgstr ""
"A menedzser újraindítása miatt a tevékenység lezárásra került. Próbálja "
"újra."
#: vm/models/common.py:39
msgid "number of cores"
msgstr "magok száma"
#: vm/models/common.py:40
msgid "Number of virtual CPU cores available to the virtual machine."
msgstr "A virtuális gép számára elérhető CPU-magok száma."
#: vm/models/common.py:44
msgid "Mebibytes of memory."
msgstr "Memória mebibyte-okban."
#: vm/models/common.py:46
msgid "maximal RAM size"
msgstr "maximális RAM-méret"
......@@ -4933,14 +5328,6 @@ msgstr "Felső memóriaméret-korlát ballooning esetén."
msgid "architecture"
msgstr "architektúra"
#: vm/models/common.py:52 vm/models/node.py:124
msgid "priority"
msgstr "prioritás"
#: vm/models/common.py:53
msgid "CPU priority."
msgstr "CPU prioritás."
#: vm/models/common.py:66
msgid "Name of base resource configuration."
msgstr "Alap erőforráskonfiguráció neve."
......@@ -4969,11 +5356,6 @@ msgstr "Létrehozhat bérlési módot."
msgid "never"
msgstr "soha"
#: vm/models/common.py:154
#, python-format
msgid "%(name)s (suspend: %(s)s, remove: %(r)s)"
msgstr "%(name)s (felfüggesztés: %(s)s, törlés: %(r)s)"
#: vm/models/instance.py:99
msgid "Primary remote access method."
msgstr "Elsődleges távoli elérési mód."
......@@ -5083,10 +5465,6 @@ msgstr "felfüggesztve"
msgid "error"
msgstr "hiba"
#: vm/models/instance.py:221
msgid "pending"
msgstr "függő"
#: vm/models/instance.py:222
msgid "destroyed"
msgstr "megsemmisítve"
......@@ -5455,73 +5833,73 @@ msgstr ""
msgid "virtual machine successfully deployed to node: %(node)s"
msgstr "a virtuális gép sikeresen elindítva a következő csomóponton: %(node)s"
#: vm/operations.py:389 vm/operations.py:564 vm/operations.py:930
#: vm/operations.py:394 vm/operations.py:572 vm/operations.py:938
msgid "deploy network"
msgstr "hálózati kapcsolat létrehozása"
#: vm/operations.py:401 vm/operations.py:582 vm/operations.py:681
#: vm/operations.py:406 vm/operations.py:590 vm/operations.py:689
msgid "wait operating system loading"
msgstr "várakozás az operációs rendszer betöltésére"
#: vm/operations.py:406
#: vm/operations.py:411
msgid "deploy vm"
msgstr "vm indítása"
#: vm/operations.py:407
#: vm/operations.py:412
msgid "Deploy virtual machine."
msgstr "Virtuális gép létrehozása."
#: vm/operations.py:416
#: vm/operations.py:421
msgid "deploy virtual machine"
msgstr "virtuális gép létrehozása"
#: vm/operations.py:417
#: vm/operations.py:422
#, python-format
msgid "deploy vm to %(node)s"
msgstr "vm létrehozása: %(node)s"
#: vm/operations.py:423
#: vm/operations.py:428
msgid "deploy disks"
msgstr "lemez létrehozása"
#: vm/operations.py:424
#: vm/operations.py:429
msgid "Deploy all associated disks."
msgstr "Csatolt lemezek létrehozása."
#: vm/operations.py:441
#: vm/operations.py:446
msgid "boot virtual machine"
msgstr "virtuális gép indítása"
#: vm/operations.py:449
#: vm/operations.py:454
msgid "destroy"
msgstr "megsemmisítés"
#: vm/operations.py:450
#: vm/operations.py:455
msgid "Permanently destroy virtual machine, its network settings and disks."
msgstr ""
"Virtuális gép és lemezeinek, hálózati beállításainak végleges eltávolítása."
#: vm/operations.py:459
#: vm/operations.py:467
msgid "destroy network"
msgstr "hálózat megsemmisítése"
#: vm/operations.py:470
#: vm/operations.py:478
msgid "destroy disks"
msgstr "lemez megsemmisítése"
#: vm/operations.py:489
#: vm/operations.py:497
msgid "destroy virtual machine"
msgstr "virtuális gép megsemmisítése"
#: vm/operations.py:497
#: vm/operations.py:505
msgid "removing memory dump"
msgstr "memóriamentés törlése"
#: vm/operations.py:511
#: vm/operations.py:519
msgid "migrate"
msgstr "migrálás"
#: vm/operations.py:512
#: vm/operations.py:520
msgid ""
"Move a running virtual machine to an other worker node keeping its full "
"state."
......@@ -5529,39 +5907,39 @@ msgstr ""
"A virtuális gép mozgatása egy másik számítási csomópontra állapotának "
"megtartásával."
#: vm/operations.py:529
#: vm/operations.py:537
msgid "redeploy network (rollback)"
msgstr "hálózati kapcsolat újraépítése (visszagörgetés)"
#: vm/operations.py:536
#: vm/operations.py:544
msgid "schedule"
msgstr "ütemezés"
#: vm/operations.py:543
#: vm/operations.py:551
#, python-format
msgid "migrate to %(node)s"
msgstr "migrálás %(node)s csomópontra"
#: vm/operations.py:554 vm/operations.py:879
#: vm/operations.py:562 vm/operations.py:887
msgid "shutdown network"
msgstr "hálózati kapcsolat leállítása"
#: vm/operations.py:571
#: vm/operations.py:579
msgid "reboot"
msgstr "újraindítás"
#: vm/operations.py:572
#: vm/operations.py:580
msgid ""
"Warm reboot virtual machine by sending Ctrl+Alt+Del signal to its console."
msgstr ""
"Virtuális gép újraindítása a konzoljára a Ctrl+Alt+Del kombináció "
"küldésével."
#: vm/operations.py:588
#: vm/operations.py:596
msgid "remove interface"
msgstr "interfész törlése"
#: vm/operations.py:589
#: vm/operations.py:597
msgid ""
"Remove the specified network interface and erase IP address allocations, "
"related firewall rules and hostnames."
......@@ -5569,68 +5947,68 @@ msgstr ""
"A kiválasztott hálózati interfész eltávolítása, a foglalt IP címek, "
"tűzfalszabályok és gépnevek törlése."
#: vm/operations.py:605
#: vm/operations.py:613
#, python-format
msgid "remove %(vlan)s interface"
msgstr "%(vlan)s interfész törlése"
#: vm/operations.py:612
#: vm/operations.py:620
msgid "close port"
msgstr "port bezárása"
#: vm/operations.py:613
#: vm/operations.py:621
msgid "Close the specified port."
msgstr "A megadott port bezárása."
#: vm/operations.py:622
#: vm/operations.py:630
#, python-format
msgid "close %(proto)s/%(port)d on %(host)s"
msgstr "%(proto)s/%(port)d bezárása ezen: %(host)s"
#: vm/operations.py:630
#: vm/operations.py:638
msgid "open port"
msgstr "port nyitása"
#: vm/operations.py:631
#: vm/operations.py:639
msgid "Open the specified port."
msgstr "A megadott port kinyitása."
#: vm/operations.py:640
#: vm/operations.py:648
#, python-format
msgid "open %(proto)s/%(port)d on %(host)s"
msgstr "%(proto)s/%(port)d kinyitása ezen: %(host)s"
#: vm/operations.py:647
#: vm/operations.py:655
msgid "remove disk"
msgstr "lemez eltávolítása"
#: vm/operations.py:648
#: vm/operations.py:656
msgid ""
"Remove the specified disk from the virtual machine, and destroy the data."
msgstr "A megadott lemez eltávolítása a virtuális gépből és az adat törlése."
#: vm/operations.py:658
#: vm/operations.py:666
msgid "destroy disk"
msgstr "lemez megsemmisítése"
#: vm/operations.py:664
#: vm/operations.py:672
#, python-format
msgid "remove disk %(name)s"
msgstr "%(name)s lemez eltávolítása"
#: vm/operations.py:671 vm/operations.py:1082
#: vm/operations.py:679 vm/operations.py:1090
msgid "reset"
msgstr "reset"
#: vm/operations.py:672
#: vm/operations.py:680
msgid "Cold reboot virtual machine (power cycle)."
msgstr "Virtuális gép hideg újraindítása (hálózati tápellátás megszakítása)."
#: vm/operations.py:687
#: vm/operations.py:695
msgid "save as template"
msgstr "mentés sablonként"
#: vm/operations.py:688
#: vm/operations.py:696
msgid ""
"Save virtual machine as a template so they can be shared with users and "
"groups. Anyone who has access to a template (and to the networks it uses) "
......@@ -5640,16 +6018,16 @@ msgstr ""
"felhasználókkal és csoportokkal. Mindenki, aki hozzáférést kap egy sablonhoz"
" (és az általa használt hálózatokhoz), képes lesz egy példányát elindítani."
#: vm/operations.py:764
#: vm/operations.py:772
#, python-format
msgid "saving disk %(name)s"
msgstr "%(name)s lemez mentése"
#: vm/operations.py:796
#: vm/operations.py:804
msgid "shutdown"
msgstr "leállítás"
#: vm/operations.py:797
#: vm/operations.py:805
msgid ""
"Try to halt virtual machine by a standard ACPI signal, allowing the "
"operating system to keep a consistent state. The operation will fail if the "
......@@ -5659,7 +6037,7 @@ msgstr ""
"operációs rendszer számár a szabályos leállást. A művelet meghiúsul, ha a "
"gép nem áll le."
#: vm/operations.py:816
#: vm/operations.py:824
msgid ""
"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 "
......@@ -5669,11 +6047,11 @@ msgstr ""
" ez a nem megfelelő ACPI beállítások miatt van. Megpróbálhatja a gépet az "
"operációs rendszerből, kézzel leállítani."
#: vm/operations.py:828
#: vm/operations.py:836
msgid "shut off"
msgstr "kikapcsolás"
#: vm/operations.py:829
#: vm/operations.py:837
msgid ""
"Forcibly halt a virtual machine without notifying the operating system. This"
" operation will even work in cases when shutdown does not, but the operating"
......@@ -5686,11 +6064,11 @@ msgstr ""
"rendszer és a fájlrendszer sérülhet, adatvesztés történhet. A művelet hatása"
" hasonló, mint egy fizikai gép tápellátásának megszüntetése."
#: vm/operations.py:852
#: vm/operations.py:860
msgid "sleep"
msgstr "altatás"
#: vm/operations.py:853
#: vm/operations.py:861
msgid ""
"Suspend virtual machine. This means the machine is stopped and its memory is"
" saved to disk, so if the machine is waked up, all the applications will "
......@@ -5706,15 +6084,15 @@ msgstr ""
"megállhatnak visszaállítás után. A felfüggesztés ideje alatt a virtuális gép"
" csak tárterületet és hálózati erőforrásokat foglal."
#: vm/operations.py:887
#: vm/operations.py:895
msgid "suspend virtual machine"
msgstr "virtuális gép felfüggesztése"
#: vm/operations.py:901
#: vm/operations.py:909
msgid "wake up"
msgstr "virtuális gép ébresztése"
#: vm/operations.py:902
#: vm/operations.py:910
msgid ""
"Wake up sleeping (suspended) virtual machine. This will load the saved "
"memory of the system and start the virtual machine from this state."
......@@ -5722,15 +6100,15 @@ msgstr ""
"Alvó (felfüggesztett) gép ébresztése: az elmentett memóriatartalom "
"visszatöltése és a virtuális gép indítása ebből a mentett állapotból."
#: vm/operations.py:941
#: vm/operations.py:949
msgid "resume virtual machine"
msgstr "virtuális gép ébresztése"
#: vm/operations.py:955
#: vm/operations.py:963
msgid "renew"
msgstr "megújítás"
#: vm/operations.py:956
#: vm/operations.py:964
msgid ""
"Virtual machines are suspended and destroyed after they expire. This "
"operation renews expiration times according to the lease type. If the "
......@@ -5740,7 +6118,7 @@ msgstr ""
" a művelet megújítja a bérletet a kiválasztott típusnak megfelelően. Ha egy "
"gép közeledik a lejárathoz, a tulajdonost értesítjük."
#: vm/operations.py:969
#: vm/operations.py:977
msgid ""
"Renewing the machine with the selected lease would result in its suspension "
"time get earlier than before."
......@@ -5748,7 +6126,7 @@ msgstr ""
"A gép megújítása a kiválasztott bérleti mód mellett a felfüggesztési időt "
"korábbra állította volna, mint a jelenlegi érték."
#: vm/operations.py:974
#: vm/operations.py:982
msgid ""
"Renewing the machine with the selected lease would result in its delete time"
" get earlier than before."
......@@ -5756,17 +6134,17 @@ msgstr ""
"A gép megújítása a kiválasztott bérleti mód mellett a törlési időt korábbra "
"állította volna, mint a jelenlegi érték."
#: vm/operations.py:982
#: vm/operations.py:990
#, python-format
msgid "Renewed to suspend at %(suspend)s and destroy at %(delete)s."
msgstr ""
"Megújítás után felfüggesztés ideje: %(suspend)s, a törlésé: %(delete)s."
#: vm/operations.py:989
#: vm/operations.py:997
msgid "emergency state change"
msgstr "vész-állapotváltás"
#: vm/operations.py:990
#: vm/operations.py:998
msgid ""
"Change the virtual machine state to NOSTATE. This should only be used if "
"manual intervention was needed in the virtualization layer, and the machine "
......@@ -5777,15 +6155,15 @@ msgstr ""
"rétegben, és úgy szeretné a gépet újból elindítani, hogy ne vesszenek el "
"lemezei vagy hálózati erőforrásai."
#: vm/operations.py:1003
#: vm/operations.py:1011
msgid "Activity is forcibly interrupted."
msgstr "A tevékenység erőszakos megszakításra került."
#: vm/operations.py:1018
#: vm/operations.py:1026
msgid "redeploy"
msgstr "újbóli létrehozás"
#: vm/operations.py:1019
#: vm/operations.py:1027
msgid ""
"Change the virtual machine state to NOSTATE and redeploy the VM. This "
"operation allows starting machines formerly running on a failed node."
......@@ -5794,52 +6172,58 @@ msgstr ""
"létrehozása. Ez a művelet lehetővé teszi olyan gépek elindítását, amelyek "
"korábban egy meghibásodott csomóponton futnak."
#: vm/operations.py:1055
#: vm/operations.py:1063
msgid "You cannot call this operation on an offline node."
msgstr "Nem hívható ez a művelet elérhetetlen csomópontra."
#: vm/operations.py:1083
#: vm/operations.py:1091
msgid "Disable missing node and redeploy all instances on other ones."
msgstr "Hiányzó csomópont letiltása és az összes példány elindítása a többin."
#: vm/operations.py:1093
#: vm/operations.py:1101
msgid "You cannot reset a disabled or online node."
msgstr "Tiltott vagy elérhető csomópont resetelése nem lehetséges."
#: vm/operations.py:1101 vm/operations.py:1121
#, python-format
msgid "migrate %(instance)s (%(pk)s)"
#: vm/operations.py:1106
#, fuzzy, python-format
#| msgid "migrate %(instance)s (%(pk)s)"
msgid "redeploy %(instance)s (%(pk)s)"
msgstr "%(instance)s (%(pk)s) migrálása"
#: vm/operations.py:1110
#: vm/operations.py:1119
msgid "flush"
msgstr "ürítés"
#: vm/operations.py:1111
#: vm/operations.py:1120
msgid "Passivate node and move all instances to other ones."
msgstr ""
"A csomópont passzívra állítása és az összes példány másikakra mozgatása."
#: vm/operations.py:1130
#, python-format
msgid "migrate %(instance)s (%(pk)s)"
msgstr "%(instance)s (%(pk)s) migrálása"
#: vm/operations.py:1139
msgid "activate"
msgstr "aktiválás"
#: vm/operations.py:1131
#: vm/operations.py:1140
msgid ""
"Make node active, i.e. scheduler is allowed to deploy virtual machines to "
"it."
msgstr ""
"Csomópont aktívvá tétele: az ütemező indíthat virtuális gépeket rajta."
#: vm/operations.py:1139
#: vm/operations.py:1148
msgid "You cannot activate an active node."
msgstr "Aktív csomópont aktiválása nem lehetséges."
#: vm/operations.py:1151
#: vm/operations.py:1160
msgid "passivate"
msgstr "passziválás"
#: vm/operations.py:1152
#: vm/operations.py:1161
msgid ""
"Make node passive, i.e. scheduler is denied to deploy virtual machines to "
"it, but remaining instances and the ones manually migrated will continue "
......@@ -5848,31 +6232,31 @@ msgstr ""
"Csomópont passzívvá tétele: az ütemező nem indíthat rajta virtuális gépeket,"
" azonban a megmaradt példányok és a kézzel idemigráltak tovább működnek."
#: vm/operations.py:1160
#: vm/operations.py:1169
msgid "You cannot passivate a passive node."
msgstr "Passzív csomópont passziválása nem lehetséges."
#: vm/operations.py:1173
#: vm/operations.py:1182
msgid "disable"
msgstr "tiltás"
#: vm/operations.py:1174
#: vm/operations.py:1183
msgid "Disable node."
msgstr "Csomópont tiltása."
#: vm/operations.py:1181
#: vm/operations.py:1190
msgid "You cannot disable a disabled node."
msgstr "Tiltott csomópont tiltása nem lehetséges."
#: vm/operations.py:1184
#: vm/operations.py:1193
msgid "You cannot disable a node which is hosting instances."
msgstr "Nem tiltható le olyan csomópont, amelyen még futnak példányok."
#: vm/operations.py:1197
#: vm/operations.py:1206
msgid "update node"
msgstr "csomópont frissítése"
#: vm/operations.py:1198
#: vm/operations.py:1207
msgid ""
"Upgrade or install node software (vmdriver, agentdriver, monitor-client) "
"with Salt."
......@@ -5880,41 +6264,41 @@ msgstr ""
"Csomópont frissítése vagy telepítése (vmdriver, agentdriver, monitor-client)"
" Salt segítségével."
#: vm/operations.py:1216
#: vm/operations.py:1225
#, python-format
msgid "No minions matched the target (%(target)s). Data: (%(data)s)"
msgstr ""
#: vm/operations.py:1221 vm/operations.py:1232
#: vm/operations.py:1230 vm/operations.py:1241
#, python-format
msgid "Unhandled exception: %(msg)s"
msgstr "Kezeletlen kivétel: %(msg)s"
#: vm/operations.py:1228
#: vm/operations.py:1237
msgid "upgrade packages"
msgstr "csomagok frissítése"
#: vm/operations.py:1243
#: vm/operations.py:1252
#, python-format
msgid "Upgraded: %(upgraded)s, Installed: %(installed)s, Removed: %(removed)s"
msgstr ""
"Frissítve: %(upgraded)s, Telepítve: %(installed)s, Törölve: %(removed)s"
#: vm/operations.py:1256
#: vm/operations.py:1265
#, python-format
msgid "Changes: %(changes)s Comment: %(comment)s"
msgstr ""
#: vm/operations.py:1264
#: vm/operations.py:1273
#, python-format
msgid "Failed: %(failed)s"
msgstr "Meghiúsult: %(failed)s"
#: vm/operations.py:1270
#: vm/operations.py:1279
msgid "screenshot"
msgstr "képernyőkép"
#: vm/operations.py:1271
#: vm/operations.py:1280
msgid ""
"Get a screenshot about the virtual machine's console. A key will be pressed "
"on the keyboard to stop screensaver."
......@@ -5922,11 +6306,11 @@ msgstr ""
"Képernyőkép készítése a virtuális gép konzoljáról. Egy billentyűnyomást "
"követően készül a kép a képernyővédő miatt."
#: vm/operations.py:1283
#: vm/operations.py:1292
msgid "recover"
msgstr "visszaállítás"
#: vm/operations.py:1284
#: vm/operations.py:1293
msgid ""
"Try to recover virtual machine disks from destroyed state. Network resources"
" (allocations) are already lost, so you will have to manually add interfaces"
......@@ -5936,19 +6320,19 @@ msgstr ""
"hálózati erőforrások foglalásai már végleg elvesztek, így az interfészeket "
"kézzel kell a visszaállítás után pótolni."
#: vm/operations.py:1301
#: vm/operations.py:1310
msgid "recover instance"
msgstr "példány helyreállítása"
#: vm/operations.py:1324
#: vm/operations.py:1333
msgid "resources change"
msgstr "erőforrások módosítása"
#: vm/operations.py:1325
#: vm/operations.py:1334
msgid "Change resources of a stopped virtual machine."
msgstr "Leállított virtuális gép erőforrásainak változtatása."
#: vm/operations.py:1342
#: vm/operations.py:1361
#, python-format
msgid ""
"Priority: %(priority)s, Num cores: %(num_cores)s, Ram size: %(ram_size)s"
......@@ -5956,11 +6340,11 @@ msgstr ""
"Prioritás: %(priority)s, magok száma: %(num_cores)s, memória mérete: "
"%(ram_size)s"
#: vm/operations.py:1351
#: vm/operations.py:1370
msgid "password reset"
msgstr "jelszó visszaállítása"
#: vm/operations.py:1352
#: vm/operations.py:1371
msgid ""
"Generate and set a new login password on the virtual machine. This operation"
" requires the agent running. Resetting the password is not warranted to "
......@@ -5970,52 +6354,52 @@ msgstr ""
"művelet megköveteli az ügynök futását. A jelszó átállítása nem garantálja a "
"sikeres belépést, mivel más beállítások is megakadályozhatják ezt."
#: vm/operations.py:1376
#: vm/operations.py:1395
msgid "agent"
msgstr "ügynök"
#: vm/operations.py:1417
#: vm/operations.py:1436
msgid "starting"
msgstr "indítás"
#: vm/operations.py:1435
#: vm/operations.py:1454
msgid "wait agent restarting"
msgstr "várakozás az ügynök újraindulására"
#: vm/operations.py:1452
#: vm/operations.py:1471
msgid "cleanup"
msgstr "takarítás"
#: vm/operations.py:1458
#: vm/operations.py:1477
msgid "set time"
msgstr "óra beállítása"
#: vm/operations.py:1469
#: vm/operations.py:1488
msgid "set hostname"
msgstr "gépnév beállítása"
#: vm/operations.py:1480
#: vm/operations.py:1499
msgid "restart networking"
msgstr "hálózat újratöltése"
#: vm/operations.py:1486
#: vm/operations.py:1505
msgid "change ip"
msgstr "IP cím beállítása"
#: vm/operations.py:1501
#: vm/operations.py:1520
msgid "update agent"
msgstr "ügynök frissítése"
#: vm/operations.py:1507
#: vm/operations.py:1526
#, python-format
msgid "update agent to %(version)s"
msgstr "ügynökfrissítés erre: %(version)s"
#: vm/operations.py:1590
#: vm/operations.py:1609
msgid "mount store"
msgstr "tárhely csatolása"
#: vm/operations.py:1592
#: vm/operations.py:1611
msgid ""
"This operation attaches your personal file store. Other users who have "
"access to this machine can see these files as well."
......@@ -6023,28 +6407,28 @@ msgstr ""
"Ez a művelet csatolja az ön személyes tárhelyét. A gép más felhasználói is "
"elérhetik fájljait."
#: vm/operations.py:1626
#: vm/operations.py:1645
msgid "attach disk"
msgstr "lemez csatolása"
#: vm/operations.py:1637
#: vm/operations.py:1656
msgid "Resource was not found."
msgstr "Nem található az erőforrás."
#: vm/operations.py:1638
#: vm/operations.py:1657
#, python-format
msgid "Resource was not found. %(exception)s"
msgstr "Nem található az erőforrás. %(exception)s"
#: vm/operations.py:1647
#: vm/operations.py:1666
msgid "detach disk"
msgstr "lemez leválasztása"
#: vm/operations.py:1662
#: vm/operations.py:1681
msgid "attach network"
msgstr "hálózat csatolása"
#: vm/operations.py:1669
#: vm/operations.py:1688
msgid "detach network"
msgstr "hálózat lecsatolása"
......@@ -6080,10 +6464,13 @@ msgstr ""
"<a href=\"%(url)s\">%(instance)s</a> gépe felfüggesztésre került, mivel "
"lejárt. Felébresztheti vagy megsemmisítheti."
#: vm/tests/test_models.py:219
#: vm/tests/test_models.py:221
msgid "x"
msgstr "x"
#~ msgid "resources request"
#~ msgstr "erőforrás igénylés"
#~ msgid ""
#~ "Dear %s, you've signed in as administrator!<br />Reloading in 10 seconds..."
#~ msgstr "Kedves %s, Ön rendszergazda!<br />Újratöltés 10 másodpercen belül..."
......@@ -6339,9 +6726,6 @@ msgstr "x"
#~ msgid "Recover virtual machine from destroyed state."
#~ msgstr "Megsemmisített virtuális gép visszaállítása."
#~ msgid "Change resources"
#~ msgstr "Erőforrások módosítása"
#~ msgid "VM successfully deleted."
#~ msgstr "A VM törlésre került."
......@@ -6397,9 +6781,6 @@ msgstr "x"
#~ msgid "Are you sure?"
#~ msgstr "Biztos benne?"
#~ msgid "Accept"
#~ msgstr "Elfogadás"
#~ msgid ""
#~ "\n"
#~ "Please, either <a href=\"%(token)s\">renew</a> or <a href=\"%(url)s\">destroy</a>\n"
......
......@@ -69,6 +69,8 @@ class BlacklistItemForm(ModelForm):
class Meta:
model = BlacklistItem
fields = ("ipv4", "host", "expires_at", "whitelisted", "reason",
"snort_message", )
class DomainForm(ModelForm):
......@@ -90,6 +92,7 @@ class DomainForm(ModelForm):
class Meta:
model = Domain
fields = ("name", "ttl", "owner", )
class FirewallForm(ModelForm):
......@@ -105,6 +108,7 @@ class FirewallForm(ModelForm):
class Meta:
model = Firewall
fields = ("name", )
class GroupForm(ModelForm):
......@@ -126,6 +130,7 @@ class GroupForm(ModelForm):
class Meta:
model = Group
fields = ("name", "description", "owner", )
class HostForm(ModelForm):
......@@ -165,6 +170,9 @@ class HostForm(ModelForm):
class Meta:
model = Host
fields = ("hostname", "reverse", "mac", "vlan", "shared_ip", "ipv4",
"ipv6", "external_ipv4", "description", "location",
"comment", "owner", )
class RecordForm(ModelForm):
......@@ -191,6 +199,8 @@ class RecordForm(ModelForm):
class Meta:
model = Record
fields = ("type", "host", "name", "domain", "address", "ttl",
"description", "owner", )
class RuleForm(ModelForm):
......@@ -230,6 +240,10 @@ class RuleForm(ModelForm):
class Meta:
model = Rule
fields = ("direction", "description", "foreign_network", "dport",
"sport", "weight", "proto", "extra", "action", "owner",
"nat", "nat_external_port", "nat_external_ipv4", "vlan",
"vlangroup", "host", "hostgroup", "firewall", )
class SwitchPortForm(ModelForm):
......@@ -252,6 +266,7 @@ class SwitchPortForm(ModelForm):
class Meta:
model = SwitchPort
fields = ("untagged_vlan", "tagged_vlans", "description", )
class VlanForm(ModelForm):
......@@ -305,6 +320,10 @@ class VlanForm(ModelForm):
widgets = {
'ipv6_template': widgets.TextInput,
}
fields = ("name", "vid", "network_type", "managed", "network4",
"snat_to", "snat_ip", "dhcp_pool", "network6",
"ipv6_template", "host_ipv6_prefixlen", "domain",
"reverse_domain", "description", "comment", "owner", )
class VlanGroupForm(ModelForm):
......@@ -328,3 +347,4 @@ class VlanGroupForm(ModelForm):
class Meta:
model = VlanGroup
fields = ("name", "vlans", "description", "owner", )
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# 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.forms import (
ModelForm, ModelChoiceField, ChoiceField, Form, CharField, RadioSelect,
Textarea,
)
from django.utils.translation import ugettext_lazy as _
from django.template import RequestContext
from django.template.loader import render_to_string
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from request.models import (
LeaseType, TemplateAccessType, TemplateAccessAction,
)
from dashboard.forms import VmResourcesForm
class LeaseTypeForm(ModelForm):
@property
def helper(self):
helper = FormHelper()
helper.add_input(Submit("submit", _("Save"),
css_class="btn btn-success", ))
return helper
class Meta:
model = LeaseType
fields = ["name", "lease", ]
class TemplateAccessTypeForm(ModelForm):
def __init__(self, *args, **kwargs):
super(TemplateAccessTypeForm, self).__init__(*args, **kwargs)
@property
def helper(self):
helper = FormHelper()
helper.add_input(Submit("submit", _("Save"),
css_class="btn btn-success", ))
return helper
class Meta:
model = TemplateAccessType
fields = ["name", "templates", ]
class InitialFromFileMixin(object):
def __init__(self, *args, **kwargs):
request = kwargs.pop("request", None)
super(InitialFromFileMixin, self).__init__(*args, **kwargs)
self.initial['message'] = render_to_string(
self.initial_template,
RequestContext(request, {}),
)
class TemplateRequestForm(InitialFromFileMixin, Form):
template = ModelChoiceField(TemplateAccessType.objects.all(),
label=_("Template share"))
level = ChoiceField(TemplateAccessAction.LEVELS, widget=RadioSelect,
initial=TemplateAccessAction.LEVELS.user)
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/template.html"
class LeaseRequestForm(InitialFromFileMixin, Form):
lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease"))
message = CharField(widget=Textarea)
initial_template = "request/initials/lease.html"
class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm):
message = CharField(widget=Textarea)
initial_template = "request/initials/resources.html"
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
from django.conf import settings
import model_utils.fields
import django.core.validators
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0001_initial'),
('vm', '0002_interface_model'),
]
operations = [
migrations.CreateModel(
name='ExtendLeaseAction',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('instance', models.ForeignKey(to='vm.Instance')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='LeaseType',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=25)),
('lease', models.ForeignKey(to='vm.Lease')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Request',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('status', models.CharField(default=b'PENDING', max_length=10, choices=[(b'PENDING', 'pending'), (b'ACCEPTED', 'accepted'), (b'DECLINED', 'declined')])),
('type', models.CharField(max_length=10, choices=[(b'resource', 'resource request'), (b'lease', 'lease request'), (b'template', 'template access')])),
('message', models.TextField(verbose_name='Message')),
('reason', models.TextField(verbose_name='Reason')),
('object_id', models.IntegerField()),
('closed_by', models.ForeignKey(related_name='closed_by', to=settings.AUTH_USER_MODEL, null=True)),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
('user', models.ForeignKey(related_name='user', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='ResourceChangeAction',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('num_cores', models.IntegerField(help_text='Number of virtual CPU cores available to the virtual machine.', verbose_name='number of cores', validators=[django.core.validators.MinValueValidator(0)])),
('ram_size', models.IntegerField(help_text='Mebibytes of memory.', verbose_name='RAM size', validators=[django.core.validators.MinValueValidator(0)])),
('priority', models.IntegerField(help_text='CPU priority.', verbose_name='priority', validators=[django.core.validators.MinValueValidator(0)])),
('instance', models.ForeignKey(to='vm.Instance')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='TemplateAccessAction',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('level', models.CharField(default=b'user', max_length=10, choices=[(b'user', 'user'), (b'operator', 'operator')])),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='TemplateAccessType',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=25)),
('templates', models.ManyToManyField(to='vm.InstanceTemplate')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.AddField(
model_name='templateaccessaction',
name='template_type',
field=models.ForeignKey(to='request.TemplateAccessType'),
preserve_default=True,
),
migrations.AddField(
model_name='templateaccessaction',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
preserve_default=True,
),
migrations.AddField(
model_name='extendleaseaction',
name='lease_type',
field=models.ForeignKey(to='request.LeaseType'),
preserve_default=True,
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('request', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='leasetype',
name='lease',
field=models.ForeignKey(verbose_name='Lease', to='vm.Lease'),
preserve_default=True,
),
migrations.AlterField(
model_name='leasetype',
name='name',
field=models.CharField(max_length=25, verbose_name='Name'),
preserve_default=True,
),
migrations.AlterField(
model_name='templateaccesstype',
name='name',
field=models.CharField(max_length=25, verbose_name='Name'),
preserve_default=True,
),
migrations.AlterField(
model_name='templateaccesstype',
name='templates',
field=models.ManyToManyField(to='vm.InstanceTemplate', verbose_name='Templates'),
preserve_default=True,
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('request', '0002_auto_20150407_1117'),
]
operations = [
migrations.AlterField(
model_name='leasetype',
name='name',
field=models.CharField(max_length=100, verbose_name='Name'),
preserve_default=True,
),
migrations.AlterField(
model_name='request',
name='type',
field=models.CharField(max_length=10, choices=[(b'resource', 'resource request'), (b'lease', 'lease request'), (b'template', 'template access request')]),
preserve_default=True,
),
migrations.AlterField(
model_name='templateaccesstype',
name='name',
field=models.CharField(max_length=100, verbose_name='Name'),
preserve_default=True,
),
]
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import json
import logging
from django.db.models import (
Model, CharField, IntegerField, TextField, ForeignKey, ManyToManyField,
)
from django.db.models.signals import post_save
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
from django.utils.translation import (
ugettext_lazy as _, ugettext_noop, ungettext
)
from django.core.urlresolvers import reverse
import requests
from model_utils.models import TimeStampedModel
from model_utils import Choices
from vm.models import Instance, InstanceTemplate, Lease
logger = logging.getLogger(__name__)
class RequestAction(Model):
def accept(self):
raise NotImplementedError
@property
def accept_msg(self):
raise NotImplementedError
class Meta:
abstract = True
class RequestType(Model):
name = CharField(max_length=100, verbose_name=_("Name"))
def __unicode__(self):
return self.name
class Meta:
abstract = True
class Request(TimeStampedModel):
STATUSES = Choices(
('PENDING', _('pending')),
('ACCEPTED', _('accepted')),
('DECLINED', _('declined')),
)
status = CharField(choices=STATUSES, default=STATUSES.PENDING,
max_length=10)
user = ForeignKey(User, related_name="user")
closed_by = ForeignKey(User, related_name="closed_by", null=True)
TYPES = Choices(
('resource', _('resource request')),
('lease', _("lease request")),
('template', _("template access request")),
)
type = CharField(choices=TYPES, max_length=10)
message = TextField(verbose_name=_("Message"))
reason = TextField(verbose_name=_("Reason"))
content_type = ForeignKey(ContentType)
object_id = IntegerField()
action = GenericForeignKey("content_type", "object_id")
def get_absolute_url(self):
return reverse("request.views.request-detail", kwargs={'pk': self.pk})
def get_readable_status(self):
return self.STATUSES[self.status]
def get_readable_type(self):
return self.TYPES[self.type]
def get_request_icon(self):
return {
'resource': "tasks",
'lease': "clock-o",
'template': "puzzle-piece"
}.get(self.type)
def get_effect(self):
return {
"PENDING": "warning",
"ACCEPTED": "success",
"DECLINED": "danger",
}.get(self.status)
def get_status_icon(self):
return {
"PENDING": "exclamation-triangle",
"ACCEPTED": "check",
"DECLINED": "times",
}.get(self.status)
def accept(self, user):
self.action.accept(user)
self.status = "ACCEPTED"
self.closed_by = user
self.save()
self.user.profile.notify(
ugettext_noop("Request accepted"),
self.action.accept_msg
)
def decline(self, user, reason):
self.status = "DECLINED"
self.closed_by = user
self.reason = reason
self.save()
decline_msg = ugettext_noop(
'Your <a href="%(url)s">request</a> was declined because of the '
'following reason: %(reason)s'
)
self.user.profile.notify(
ugettext_noop("Request declined"),
decline_msg, url=self.get_absolute_url(), reason=self.reason,
)
class LeaseType(RequestType):
lease = ForeignKey(Lease, verbose_name=_("Lease"))
def __unicode__(self):
return _("%(name)s (suspend: %(s)s, remove: %(r)s)") % {
'name': self.name,
's': self.lease.get_readable_suspend_time(),
'r': self.lease.get_readable_delete_time()}
def get_absolute_url(self):
return reverse("request.views.lease-type-detail",
kwargs={'pk': self.pk})
class TemplateAccessType(RequestType):
templates = ManyToManyField(InstanceTemplate, verbose_name=_("Templates"))
def get_absolute_url(self):
return reverse("request.views.template-type-detail",
kwargs={'pk': self.pk})
class ResourceChangeAction(RequestAction):
instance = ForeignKey(Instance)
num_cores = IntegerField(verbose_name=_('number of cores'),
help_text=_('Number of virtual CPU cores '
'available to the virtual machine.'),
validators=[MinValueValidator(0)])
ram_size = IntegerField(verbose_name=_('RAM size'),
help_text=_('Mebibytes of memory.'),
validators=[MinValueValidator(0)])
priority = IntegerField(verbose_name=_('priority'),
help_text=_('CPU priority.'),
validators=[MinValueValidator(0)])
def accept(self, user):
self.instance.resources_change.async(
user=user, num_cores=self.num_cores, ram_size=self.ram_size,
max_ram_size=self.ram_size, priority=self.priority,
with_shutdown=True)
@property
def accept_msg(self):
return _(
'The resources of <a href="%(url)s">%(name)s</a> were changed. '
'Number of cores: %(num_cores)d, RAM size: '
'<span class="nowrap">%(ram_size)d MiB</span>, '
'CPU priority: %(priority)d/100.'
) % {
'url': self.instance.get_absolute_url(),
'name': self.instance.name,
'num_cores': self.num_cores,
'ram_size': self.ram_size,
'priority': self.priority,
}
class ExtendLeaseAction(RequestAction):
instance = ForeignKey(Instance)
lease_type = ForeignKey(LeaseType)
def accept(self, user):
self.instance.renew(lease=self.lease_type.lease, save=True, force=True,
user=user)
@property
def accept_msg(self):
return _(
'The lease of <a href="%(url)s">%(name)s</a> got extended. '
'(suspend: %(suspend)s, remove: %(remove)s)'
) % {'name': self.instance.name,
'url': self.instance.get_absolute_url(),
'suspend': self.lease_type.lease.get_readable_suspend_time(),
'remove': self.lease_type.lease.get_readable_delete_time(), }
class TemplateAccessAction(RequestAction):
template_type = ForeignKey(TemplateAccessType)
LEVELS = Choices(
('user', _('user')),
('operator', _('operator')),
)
level = CharField(choices=LEVELS, default=LEVELS.user,
max_length=10)
user = ForeignKey(User)
def get_readable_level(self):
return self.LEVELS[self.level]
def accept(self, user):
for t in self.template_type.templates.all():
t.set_user_level(self.user, self.level)
@property
def accept_msg(self):
return ungettext(
"You got access to the following template: %s",
"You got access to the following templates: %s",
self.template_type.templates.count()
) % ", ".join([x.name for x in self.template_type.templates.all()])
def send_notifications(sender, instance, created, **kwargs):
if not created:
return
notification_msg = ugettext_noop(
'A new <a href="%(request_url)s">%(request_type)s</a> was submitted '
'by <a href="%(user_url)s">%(display_name)s</a>.')
context = {
'display_name': instance.user.profile.get_display_name(),
'user_url': instance.user.profile.get_absolute_url(),
'request_url': instance.get_absolute_url(),
'request_type': u"%s" % instance.get_readable_type()
}
for u in User.objects.filter(is_superuser=True):
u.profile.notify(
ugettext_noop("New %(request_type)s"), notification_msg, context
)
instance.user.profile.notify(
ugettext_noop("Request submitted"),
ugettext_noop('You can view the request\'s status at this '
'<a href="%(request_url)s">link</a>.'), context
)
if settings.REQUEST_HOOK_URL:
context.update({
'object_kind': "request",
'site_url': settings.DJANGO_URL,
})
try:
r = requests.post(settings.REQUEST_HOOK_URL, timeout=3,
data=json.dumps(context, indent=2))
r.raise_for_status()
except requests.RequestException as e:
logger.warning("Error in HTTP POST: %s. url: %s params: %s",
str(e), settings.REQUEST_HOOK_URL, context)
else:
logger.info("Successful HTTP POST. url: %s params: %s",
settings.REQUEST_HOOK_URL, context)
post_save.connect(send_notifications, sender=Request)
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# 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 (
Column, TemplateColumn, LinkColumn
)
from request.models import Request, LeaseType, TemplateAccessType
class RequestTable(Table):
pk = LinkColumn(
'request.views.request-detail',
args=[A('pk')],
verbose_name=_("ID"),
)
status = TemplateColumn(
template_name="request/columns/status.html",
verbose_name=_("Status"),
)
user = TemplateColumn(
template_name="request/columns/user.html",
verbose_name=_("User"),
)
type = TemplateColumn(
template_name="request/columns/type.html",
verbose_name=_("Type"),
)
class Meta:
model = Request
template = "django_tables2/with_pagination.html"
attrs = {'class': ('table table-bordered table-striped table-hover'),
'id': "request-list-table"}
fields = ("pk", "status", "type", "user", )
order_by = ("-pk", )
empty_text = _("No more requests.")
per_page = 10
class LeaseTypeTable(Table):
pk = LinkColumn(
'request.views.lease-type-detail',
args=[A('pk')],
verbose_name=_("ID"),
)
lease = Column(verbose_name=_("Lease"))
class Meta:
model = LeaseType
attrs = {'class': "table table-bordered table-striped table-hover"}
fields = ('pk', 'name', 'lease', )
prefix = "lease-"
template = "django_tables2/with_pagination.html"
class TemplateAccessTypeTable(Table):
pk = LinkColumn(
'request.views.template-type-detail',
args=[A('pk')],
verbose_name=_("ID"),
)
templates = TemplateColumn(
template_name="request/columns/templates.html",
verbose_name=_("Templates"),
)
class Meta:
model = TemplateAccessType
attrs = {'class': "table table-bordered table-striped table-hover"}
fields = ('pk', 'name', 'templates', )
prefix = "template-"
template = "django_tables2/with_pagination.html"
{% load i18n %}
{% load crispy_forms_tags %}
<form action="{% url "request.views.request-lease" vm_pk=vm.pk %}" method="POST">
{% include "display-form-errors.html" %}
{% csrf_token %}
{{ form.lease|as_crispy_field }}
{{ form.message|as_crispy_field }}
<input type="submit" class="btn btn-primary"/>
</form>
{% load i18n %}
{% load crispy_forms_tags %}
<form action="{% url "request.views.request-template" %}" method="POST">
{% include "display-form-errors.html" %}
{% csrf_token %}
{{ form.template|as_crispy_field }}
<div style="font-weight: bold;">{% trans "Level" %}*</div>
{% for radio in form.level %}
<div class="myradio" style="display: inline-block; padding-left: 20px;">
<label>
{{ radio }}
<div class="text-muted" style="padding-left: 16px; font-weight: normal;">
{% if forloop.last %}
{% trans "For users who want to share the template with others." %}
{% else %}
{% trans "For users who want to start a virtual machine." %}
{% endif %}
</div>
</label>
</div>
{% endfor %}
{{ form.message|as_crispy_field }}
<input type="submit" class="btn btn-primary"/>
</form>
<span class="label label-{{ record.get_effect }}" style="font-size: 1.2em;">
<i class="fa fa-{{ record.get_status_icon }}"></i>
{{ record.get_readable_status|upper }}
</span>
{% for t in record.templates.all %}
<a href="{% url "dashboard.views.template-detail" pk=t.pk %}">
{{ t.name }}</a>
{% if not forloop.last %} | {% endif %}
{% endfor %}
<i class="fa fa-{{ record.get_request_icon }}"></i>
{{ record.get_readable_type|capfirst }}
<img src="{{ record.user.profile.get_avatar_url }}" width="20" height="20"/>
<a href="{{ record.user.profile.get_absolute_url }}">
{{ record.user.profile.get_display_name }}
</a>
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% load arrowfilter %}
{% block title-page %}{% trans "Request" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
{% if request.user.is_superuser %}
<a href="{% url "request.views.request-list" %}" class="btn btn-default btn-xs pull-right">
{% trans "Back" %}
</a>
{% endif %}
<h3 class="no-margin">
<i class="fa fa-{{ object.get_request_icon }}"></i>
{{ object.get_readable_type|capfirst }}
</h3>
</div>
<div class="panel-body">
<div class="label label-{{ object.get_effect }} pull-right" style="font-size: 1.5em; margin-top: 10px;">
<i class="fa fa-{{ object.get_status_icon }}"></i>
{{ object.get_readable_status|upper }}
</div>
<p>
<img src="{{ object.user.profile.get_avatar_url }}" width="50" height="50"/>
<a href="{{ object.user.profile.get_absolute_url }}">
{{ object.user.profile.get_display_name }}
</a>
</p>
<p>
<pre>{{ object.message }}</pre>
</p>
<hr />
{% if object.type == "lease" %}
<dl>
<dt>{% trans "VM name" %}</dt>
<dd><a href="{{ action.instance.get_absolute_url }}">{{ action.instance.name }}</a></dd>
<dt>{% trans "VM description" %}</dt>
<dd>{{ action.instance.description }}</dd>
<dt>{% trans "Current lease" %}</dt>
<dd>{{ action.instance.lease }}</dd>
<dt>{% trans "Requested lease" %}</dt>
<dd>{{ action.lease_type.lease }}</dd>
</dl>
{% elif object.type == "template" %}
<dl>
<dt>
{% trans "Template type" %}:
<span style="font-weight: normal;">{{ action.template_type.name }}</span>
</dt>
<dd>
<ul>
{% for t in action.template_type.templates.all %}
<li><a href="{{ t.get_absolute_url }}">{{ t }}</a></li>
{% endfor %}
</ul>
</dd>
<dt>{% trans "Level" %}<dt>
<dd>{{ action.get_readable_level }}</dd>
</dl>
{% elif object.type == "resource" %}
<dl>
<dt>{% trans "VM name" %}</dt>
<dd><a href="{{ action.instance.get_absolute_url }}">{{ action.instance.name }}</a></dd>
<dt>{% trans "Status" %}</dt>
<dd>
<i class="fa {{ action.instance.get_status_icon }}"></i>
{{ action.instance.get_status_display|upper }}
</dd>
<dt>{% trans "VM description" %}</dt>
<dd>{{ action.instance.description }}</dd>
<dt>
{% trans "Priority" %}
<span class="text-muted" style="font-weight: normal;">{% trans "(old values in parentheses)" %}</span>
</dt>
<dd>{{ action.priority }} ({{ action.instance.priority }})</dd>
<dt>{% trans "Number of cores" %}</dt>
<dd>{{ action.num_cores }} ({{ action.instance.num_cores }})</dd>
<dt>{% trans "Ram size" %}</dt>
<dd>{{ action.ram_size }} ({{ action.instance.ram_size }}) MiB</dd>
</dl>
{% else %}
hacks!!!
{% endif %}
{% if object.status == "PENDING" and request.user.is_superuser %}
<hr />
<div class="pull-right" id="request-buttons">
<form method="POST">
{% csrf_token %}
<p>
<textarea class="form-control" placeholder="{% trans "Reason (sent to the user if the request is declined)" %}" name="reason"></textarea>
</p>
<button class="btn btn-danger" type="submit">
<i class="fa fa-thumbs-down"></i>
{% trans "Decline" %}
</button>
</form>
{% if object.type == "resource" and action.instance.status not in accept_states %}
{% trans "You can't accept this request because of the VM's state." %}
{% else %}
<form method="POST">
{% csrf_token %}
<input type="hidden" name="accept" value="1"/>
<button class="btn btn-success">
<i class="fa fa-thumbs-up"></i>
{% trans "Accept" %}
</button>
</form>
{% endif %}
</div>
{% endif %}
{% if object.status != "PENDING" %}
<div class="text-right">
{% blocktrans with closed=object.modified|arrowfilter:LANGUAGE_CODE user=object.closed_by.profile.get_display_name %}
Closed {{ closed }} by <a href="{{ user.profile.get_absolute_url }}">{{ user }}</a>
{% endblocktrans %}
{% if object.status == "DECLINED" %}
<p>
<strong>{% trans "Reason" %}:</strong> {{ object.reason }}
</p>
{% endif %}
</div>
{% endif %}
</div><!-- .panel-body -->
</div>
</div>
</div>
{% endblock %}
{% spaceless %}
{% if LANGUAGE_CODE == "en" %}
Why do you need this lease?
{% else %} {# place your translations here #}
Why do you need this lease?
{% endif %}
{% endspaceless %}
{% spaceless %}
{% if LANGUAGE_CODE == "en" %}
Why do you need these resources?
{% else %} {# place your translations here #}
Why do you need these resources?
{% endif %}
{% endspaceless %}
{% spaceless %}
{% if LANGUAGE_CODE == "en" %}
Why do you need this template?
{% else %} {# place your translations here #}
Why do you need this template?
{% endif %}
{% endspaceless %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load render_table from django_tables2 %}
{% block title-page %}
{% if form.instance.pk %}{{ form.instance.name }}{% else %}{% trans "Create" %}{% endif %}
| {% trans "lease type" %}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
{% if object.pk %}
<a class="btn btn-xs btn-danger"
href="{% url "request.views.lease-type-delete" pk=object.pk %}">
<i class="fa fa-times"></i> {% trans "Delete" %}
</a>
{% endif %}
<a class="btn btn-xs btn-default" href="{% url "request.views.type-list" %}">
{% trans "Back" %}
</a>
</div>
<h3 class="no-margin">
<i class="fa fa-clock-o"></i>
{% if form.instance.pk %}
{{ form.instance.name }}
{% else %}
{% trans "New lease type" %}
{% endif %}
</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-8">
{% crispy form %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block title-page %}{% trans "Requests" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<a class="btn btn-xs btn-primary pull-right "href="{% url "request.views.type-list" %}">
{% trans "Request types" %}
</a>
<h3 class="no-margin"><i class="fa fa-phone"></i> {% trans "Requests" %}</h3>
</div>
<div class="panel-body">
<div class="panel-body">
{% trans "Filter by status" %}:
<a href="{{ request.path }}">{% trans "ALL" %}</a>
{% for s in statuses %}
<a href="?status={{ s.0 }}">{{ s.1|upper }}</a>
{% endfor %}
<div class="table-responsive">
{% render_table table %}
</div>
</div>
</div><!-- .panel-body -->
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
<i class="fa fa-puzzle-piece"></i> {% trans "Request new lease" %}
</h3>
</div>
<div class="panel-body">
<div class="form-group">
<label>{% trans "Virtual machine" %}</label>
<div class="controls">
<a href="{{ vm.get_absolute_url }}">{{ vm.name }}</a>
</div>
</div>
{% include "request/_request-lease-form.html" %}
</div>
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
<i class="fa fa-tasks"></i> {% trans "Request new resources" %}
</h3>
</div>
<div class="panel-body">
<form action="{% url "request.views.request-resource" vm_pk=vm.pk %}" method="POST">
{% csrf_token %}
<div class="form-group">
<label>{% trans "Virtual machine" %}</label>
<div class="controls">
<a href="{{ vm.get_absolute_url }}">{{ vm.name }}</a>
</div>
</div>
{% include "display-form-errors.html" %}
{% include "dashboard/_resources-sliders.html" with field_priority=form.priority field_num_cores=form.num_cores field_ram_size=form.ram_size %}
{{ form.message|as_crispy_field }}
<button type="submit" class="btn btn-success">
{% trans "Request new resources" %}
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
<i class="fa fa-puzzle-piece"></i> {% trans "Request template access" %}
</h3>
</div>
<div class="panel-body">
{% include "request/_request-template-form.html" %}
</div>
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load render_table from django_tables2 %}
{% block title-page %}
{% if form.instance.pk %}{{ form.instance.name }}{% else %}{% trans "Create" %}{% endif %}
| {% trans "template access type" %}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
{% if object.pk %}
<a class="btn btn-xs btn-danger"
href="{% url "request.views.template-type-delete" pk=object.pk %}">
<i class="fa fa-times"></i> {% trans "Delete" %}
</a>
{% endif %}
<a class="btn btn-xs btn-default" href="{% url "request.views.type-list" %}">
{% trans "Back" %}
</a>
</div>
<h3 class="no-margin">
<i class="fa fa-puzzle-piece"></i>
{% if form.instance.pk %}
{{ form.instance.name }}
{% else %}
{% trans "New Template Access type" %}
{% endif %}
</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-8">
{% crispy form %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block title-page %}{% trans "Request types" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
<a class="btn btn-xs btn-success" href="{% url "request.views.lease-type-create" %}">
<i class="fa fa-plus-circle"></i>
{% trans "new lease type" %}
</a>
<a class="btn btn-xs btn-success" href="{% url "request.views.template-type-create" %}">
<i class="fa fa-plus-circle"></i>
{% trans "new template access type" %}
</a>
</div>
<h3 class="no-margin"><i class="fa fa-phone"></i> {% trans "Request types" %}</h3>
</div>
<div class="panel-body">
<div class="text-muted little-margin-bottom">
{% blocktrans %}
Lease types are used for sharing leases. User can request longer ones via these.
{% endblocktrans %}
</div>
<div class="table-responsive">
{% render_table lease_table %}
</div>
<div class="text-muted little-margin-bottom">
{% blocktrans %}
Using template access types users can request multiple templates with user with operator or user level access.
{% endblocktrans %}
</div>
<div class="table-responsive">
{% render_table template_table %}
</div>
</div><!-- .panel-body -->
</div>
</div>
</div>
{% endblock %}
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# 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.test import TestCase
from django.test.client import Client
from django.contrib.auth.models import User, Permission
from mock import Mock, patch
from common.tests.celery_mock import MockCeleryMixin
from vm.models import Instance, InstanceTemplate, Lease
from dashboard.models import Profile
from request.models import Request, LeaseType, TemplateAccessType
from dashboard.tests.test_views import LoginMixin
from vm.operations import ResourcesOperation
class RequestTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self):
Instance.get_remote_queue_name = Mock(return_value='test')
self.u1 = User.objects.create(username='user1')
self.u1.set_password('password')
self.u1.save()
self.us = User.objects.create(username='superuser', is_superuser=True)
self.us.set_password('password')
self.us.save()
self.u1.user_permissions.add(Permission.objects.get(
codename='create_vm'))
# superusers are notified uppon
for u in User.objects.filter(is_superuser=True):
p = Profile(user=u)
p.save()
self.lease = Lease(name="new lease", suspend_interval_seconds=1,
delete_interval_seconds=1)
self.lease.save()
LeaseType(name="lease type #1", lease=self.lease).save()
tat = TemplateAccessType(name="a")
tat.save()
tat.templates.add(InstanceTemplate.objects.get(pk=1))
def tearDown(self):
super(RequestTest, self).tearDown()
self.u1.delete()
self.us.delete()
def test_resources_request(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
req_count = Request.objects.count()
resp = c.post("/request/resource/1/", {
'num_cores': 5,
'ram_size': 512,
'priority': 30,
'message': "szia",
})
self.assertEqual(resp.status_code, 302)
self.assertEqual(req_count + 1, Request.objects.count())
new_request = Request.objects.latest("pk")
self.assertEqual(new_request.status, "PENDING")
self.assertEqual(inst.num_cores, 2)
self.assertEqual(inst.ram_size, 200)
self.assertEqual(inst.priority, 10)
# workaround for NOSTATE
inst.emergency_change_state(new_state="STOPPED", system=True)
with patch.object(ResourcesOperation, 'async') as mock_method:
mock_method.side_effect = (
new_request.action.instance.resources_change)
new_request.accept(self.us)
inst = Instance.objects.get(pk=1)
self.assertEqual(inst.num_cores, 5)
self.assertEqual(inst.ram_size, 512)
self.assertEqual(inst.priority, 30)
new_request = Request.objects.latest("pk")
self.assertEqual(new_request.status, "ACCEPTED")
def test_template_access_request(self):
c = Client()
self.login(c, "user1")
template = InstanceTemplate.objects.get(pk=1)
self.assertFalse(template.has_level(self.u1, "user"))
req_count = Request.objects.count()
resp = c.post("/request/template/", {
'template': 1,
'level': "user",
'message': "szia",
})
self.assertEqual(resp.status_code, 302)
self.assertEqual(req_count + 1, Request.objects.count())
new_request = Request.objects.latest("pk")
self.assertEqual(new_request.status, "PENDING")
new_request.accept(self.us)
new_request = Request.objects.latest("pk")
self.assertEqual(new_request.status, "ACCEPTED")
self.assertTrue(template.has_level(self.u1, "user"))
def test_lease_request(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
req_count = Request.objects.count()
resp = c.post("/request/lease/1/", {
'lease': 1,
'message': "szia",
})
self.assertEqual(resp.status_code, 302)
self.assertEqual(req_count + 1, Request.objects.count())
new_request = Request.objects.latest("pk")
self.assertEqual(new_request.status, "PENDING")
new_request.accept(self.us)
inst = Instance.objects.get(pk=1)
new_request = Request.objects.latest("pk")
self.assertEqual(new_request.status, "ACCEPTED")
self.assertEqual(inst.lease, self.lease)
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
from django.conf.urls import patterns, url
from .views import (
RequestList, RequestDetail, RequestTypeList,
LeaseTypeCreate, LeaseTypeDetail,
TemplateAccessTypeCreate, TemplateAccessTypeDetail,
TemplateRequestView, LeaseRequestView, ResourceRequestView,
LeaseTypeDelete, TemplateAccessTypeDelete,
)
urlpatterns = patterns(
'',
url(r'^list/$', RequestList.as_view(),
name="request.views.request-list"),
url(r'^(?P<pk>\d+)/$', RequestDetail.as_view(),
name="request.views.request-detail"),
url(r'^type/list/$', RequestTypeList.as_view(),
name="request.views.type-list"),
# request types
url(r'^type/lease/create/$', LeaseTypeCreate.as_view(),
name="request.views.lease-type-create"),
url(r'^type/lease/(?P<pk>\d+)/$', LeaseTypeDetail.as_view(),
name="request.views.lease-type-detail"),
url(r'^type/lease/delete/(?P<pk>\d+)/$', LeaseTypeDelete.as_view(),
name="request.views.lease-type-delete"),
url(r'^type/template/create/$', TemplateAccessTypeCreate.as_view(),
name="request.views.template-type-create"),
url(r'^type/template/(?P<pk>\d+)/$',
TemplateAccessTypeDetail.as_view(),
name="request.views.template-type-detail"),
url(r'^type/template/delete/(?P<pk>\d+)/$',
TemplateAccessTypeDelete.as_view(),
name="request.views.template-type-delete"),
# request views (visible for users)
url(r'template/$', TemplateRequestView.as_view(),
name="request.views.request-template"),
url(r'lease/(?P<vm_pk>\d+)/$', LeaseRequestView.as_view(),
name="request.views.request-lease"),
url(r'resource/(?P<vm_pk>\d+)/$', ResourceRequestView.as_view(),
name="request.views.request-resource"),
)
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals, absolute_import
from django.views.generic import (
UpdateView, TemplateView, DetailView, CreateView, FormView, DeleteView,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import redirect, get_object_or_404
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from django_tables2 import SingleTableView
from request.models import (
Request, TemplateAccessType, LeaseType, TemplateAccessAction,
ExtendLeaseAction, ResourceChangeAction,
)
from vm.models import Instance
from vm.operations import ResourcesOperation
from request.tables import (
RequestTable, TemplateAccessTypeTable, LeaseTypeTable,
)
from request.forms import (
LeaseTypeForm, TemplateAccessTypeForm, TemplateRequestForm,
LeaseRequestForm, ResourceRequestForm,
)
class RequestList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
model = Request
table_class = RequestTable
template_name = "request/list.html"
def get_context_data(self, **kwargs):
context = super(RequestList, self).get_context_data(**kwargs)
context['statuses'] = Request.STATUSES
return context
def get_table_data(self):
data = Request.objects.all()
status = self.request.GET.get("status")
if status:
data = data.filter(status=status)
return data
class RequestDetail(LoginRequiredMixin, DetailView):
model = Request
template_name = "request/detail.html"
def post(self, *args, **kwargs):
user = self.request.user
request = self.get_object() # not self.request!
if not user.is_superuser:
raise SuspiciousOperation
if self.get_object().status == "PENDING":
accept = self.request.POST.get("accept")
reason = self.request.POST.get("reason")
if accept:
request.accept(user)
else:
request.decline(user, reason)
return redirect(request.get_absolute_url())
def get_context_data(self, **kwargs):
request = self.object
user = self.request.user
if not user.is_superuser and request.user != user:
raise SuspiciousOperation
context = super(RequestDetail, self).get_context_data(**kwargs)
context['action'] = request.action
context['accept_states'] = ResourcesOperation.accept_states
return context
class TemplateAccessTypeDetail(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, UpdateView):
model = TemplateAccessType
template_name = "request/template-type-form.html"
form_class = TemplateAccessTypeForm
success_message = _("Template access type successfully updated.")
class TemplateAccessTypeCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
model = TemplateAccessType
template_name = "request/template-type-form.html"
form_class = TemplateAccessTypeForm
success_message = _("New template access type successfully created.")
class TemplateAccessTypeDelete(LoginRequiredMixin, SuperuserRequiredMixin,
DeleteView):
model = TemplateAccessType
template_name = "dashboard/confirm/base-delete.html"
def get_success_url(self):
return reverse("request.views.type-list")
class LeaseTypeDetail(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, UpdateView):
model = LeaseType
template_name = "request/lease-type-form.html"
form_class = LeaseTypeForm
success_message = _("Lease type successfully updated.")
class LeaseTypeCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
model = LeaseType
template_name = "request/lease-type-form.html"
form_class = LeaseTypeForm
success_message = _("New lease type successfully created.")
class LeaseTypeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
model = LeaseType
template_name = "dashboard/confirm/base-delete.html"
def get_success_url(self):
return reverse("request.views.type-list")
class RequestTypeList(LoginRequiredMixin, SuperuserRequiredMixin,
TemplateView):
template_name = "request/type-list.html"
def get_context_data(self, **kwargs):
context = super(RequestTypeList, self).get_context_data(**kwargs)
context['lease_table'] = LeaseTypeTable(
LeaseType.objects.all(), request=self.request)
context['template_table'] = TemplateAccessTypeTable(
TemplateAccessType.objects.all(), request=self.request)
return context
class TemplateRequestView(LoginRequiredMixin, FormView):
form_class = TemplateRequestForm
template_name = "request/request-template.html"
def get_form_kwargs(self):
kwargs = super(TemplateRequestView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def form_valid(self, form):
data = form.cleaned_data
user = self.request.user
ta = TemplateAccessAction(
template_type=data['template'],
level=data['level'],
user=user,
)
ta.save()
req = Request(
user=user,
message=data['message'],
type=Request.TYPES.template,
action=ta
)
req.save()
return redirect("/")
class VmRequestMixin(LoginRequiredMixin, object):
def get_vm(self):
return get_object_or_404(Instance, pk=self.kwargs['vm_pk'])
def dispatch(self, *args, **kwargs):
vm = self.get_vm()
user = self.request.user
if not vm.has_level(user, self.user_level):
raise PermissionDenied()
return super(VmRequestMixin, self).dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super(VmRequestMixin, self).get_context_data(**kwargs)
context['vm'] = self.get_vm()
return context
def get_form_kwargs(self):
kwargs = super(VmRequestMixin, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def form_valid(self, form):
raise NotImplementedError
class LeaseRequestView(VmRequestMixin, FormView):
form_class = LeaseRequestForm
template_name = "request/request-lease.html"
user_level = "operator"
def form_valid(self, form):
data = form.cleaned_data
user = self.request.user
vm = self.get_vm()
el = ExtendLeaseAction(
lease_type=data['lease'],
instance=vm,
)
el.save()
req = Request(
user=user,
message=data['message'],
type=Request.TYPES.lease,
action=el
)
req.save()
return redirect(vm.get_absolute_url())
class ResourceRequestView(VmRequestMixin, FormView):
form_class = ResourceRequestForm
template_name = "request/request-resource.html"
user_level = "user"
def get_form_kwargs(self):
kwargs = super(ResourceRequestView, self).get_form_kwargs()
kwargs['can_edit'] = True
kwargs['instance'] = self.get_vm()
return kwargs
def get_initial(self):
vm = self.get_vm()
initial = super(ResourceRequestView, self).get_initial()
initial['num_cores'] = vm.num_cores
initial['priority'] = vm.priority
initial['ram_size'] = vm.ram_size
return initial
def form_valid(self, form):
vm = self.get_vm()
data = form.cleaned_data
user = self.request.user
rc = ResourceChangeAction(
instance=vm,
num_cores=data['num_cores'],
priority=data['priority'],
ram_size=data['ram_size'],
)
rc.save()
req = Request(
user=user,
message=data['message'],
type=Request.TYPES.resource,
action=rc
)
req.save()
return redirect(vm.get_absolute_url())
{% extends "base.html" %}
{% load i18n %}
{% block title %}HTTP 403{% endblock %}
{% block page_title %}{% trans ":(" %}{% endblock page_title %}
{% block content %}
<div class="alert alert-danger" style="font-size: 22px; margin-top: 2em;">
<div class="row">
<div class="col-md-2" style="text-align: center;">
HTTP 403
</div>
<div class="col-md-10" style="text-align: center;">
{% if error %}
{{ error }}
{% else %}
{% trans "Forbidden" %}
{% endif %}
</div>
</div>
</div>
{% endblock content %}
......@@ -6,5 +6,14 @@
{% block page_title %}{% trans "Page not found" %}{% endblock page_title %}
{% block content %}
<p>{% trans "This page does not exist." %}</p>
<div class="alert alert-warning" style="font-size: 22px; margin-top: 2em;">
<div class="row">
<div class="col-md-2" style="text-align: center;">
HTTP 404
</div>
<div class="col-md-10" style="text-align: center;">
{% trans "This page does not exist." %}
</div>
</div>
</div>
{% endblock content %}
{% extends "dashboard/base.html" %}
{% extends "base.html" %}
{% load i18n %}
{% block title %}HTTP 500{% endblock %}
......
......@@ -62,7 +62,6 @@ scheduler = import_module(name=django.conf.settings.VM_SCHEDULER)
ACCESS_PROTOCOLS = django.conf.settings.VM_ACCESS_PROTOCOLS
ACCESS_METHODS = [(key, name) for key, (name, port, transport)
in ACCESS_PROTOCOLS.iteritems()]
VNC_PORT_RANGE = (20000, 65536) # inclusive start, exclusive end
def find_unused_port(port_range, used_ports=[]):
......@@ -81,7 +80,7 @@ def find_unused_port(port_range, used_ports=[]):
def find_unused_vnc_port():
port = find_unused_port(
port_range=VNC_PORT_RANGE,
port_range=django.conf.settings.VNC_PORT_RANGE,
used_ports=Instance.objects.values_list('vnc_port', flat=True))
if port is None:
......
......@@ -1334,10 +1334,20 @@ class ResourcesOperation(InstanceOperation):
description = _("Change resources of a stopped virtual machine.")
acl_level = "owner"
required_perms = ('vm.change_resources', )
accept_states = ('STOPPED', 'PENDING', )
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
def _operation(self, user, activity,
num_cores, ram_size, max_ram_size, priority):
num_cores, ram_size, max_ram_size, priority,
with_shutdown=False, task=None):
if self.instance.status == 'RUNNING' and not with_shutdown:
raise Instance.WrongStateError(self.instance)
try:
self.instance.shutdown(parent_activity=activity, task=task)
except Instance.WrongStateError:
pass
self.instance._update_status()
self.instance.num_cores = num_cores
self.instance.ram_size = ram_size
......
......@@ -11,11 +11,12 @@ django-braces==1.4.0
django-celery==3.1.16
django-crispy-forms==1.4.0
django-model-utils==2.2
djangosaml2==0.13.0
django-sizefield==0.6
django-sshkey==2.2.0
django-statici18n==1.1
django-tables2==0.15.0
git+https://git.ik.bme.hu/circle/django-taggit.git
django-taggit==0.13.0
docutils==0.12
Jinja2==2.7.3
jsonfield==1.0.0
......@@ -32,6 +33,7 @@ pyinotify==0.9.4
pytz==2014.7
requests==2.5.3
salt==2014.1.0
shutilwhich==1.0.1
simplejson==3.6.5
six==1.8.0
slimit==0.8.1
......
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