diff --git a/circle/circle/settings/base.py b/circle/circle/settings/base.py index 12c848d..ab0a0fc 100644 --- a/circle/circle/settings/base.py +++ b/circle/circle/settings/base.py @@ -447,5 +447,16 @@ if graphite_host and graphite_port: else: GRAPHITE_URL = None +STORE_BASIC_AUTH = get_env_variable("STORE_BASIC_AUTH", "") == "True" +STORE_VERIFY_SSL = get_env_variable("STORE_VERIFY_SSL", "") == "True" +STORE_SSL_AUTH = get_env_variable("STORE_SSL_AUTH", "") == "True" +STORE_CLIENT_USER = get_env_variable("STORE_CLIENT_USER", "") +STORE_CLIENT_PASSWORD = get_env_variable("STORE_CLIENT_PASSWORD", "") +STORE_CLIENT_KEY = get_env_variable("STORE_CLIENT_KEY", "") +STORE_CLIENT_CERT = get_env_variable("STORE_CLIENT_CERT", "") +STORE_URL = get_env_variable("STORE_URL", "") + SESSION_COOKIE_NAME = "csessid%x" % (((getnode() // 139) ^ (getnode() % 983)) & 0xffff) + +MAX_NODE_RAM = get_env_variable("MAX_NODE_RAM", 1024) diff --git a/circle/circle/settings/production.py b/circle/circle/settings/production.py index a083089..5d6fe6f 100644 --- a/circle/circle/settings/production.py +++ b/circle/circle/settings/production.py @@ -70,20 +70,14 @@ SERVER_EMAIL = EMAIL_HOST_USER ########## CACHE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#caches -try: - CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': get_env_variable('DJANGO_MEMCACHED'), - } - } -except ImproperlyConfigured: - CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': SITE_NAME, - } +from urlparse import urlsplit + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': urlsplit(get_env_variable('CACHE_URI')).netloc, } +} ########## END CACHE CONFIGURATION diff --git a/circle/circle/settings/test.py b/circle/circle/settings/test.py index 76bb815..7130c42 100644 --- a/circle/circle/settings/test.py +++ b/circle/circle/settings/test.py @@ -35,7 +35,11 @@ SOUTH_TESTS_MIGRATE = False INSTALLED_APPS += ( 'acl.tests', + 'django_nose', ) +TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' +NOSE_ARGS = ['--with-doctest'] +PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher'] CACHES = { 'default': { @@ -52,3 +56,5 @@ LOGGING['handlers']['console'] = {'level': level, 'formatter': 'simple'} for i in LOCAL_APPS: LOGGING['loggers'][i] = {'handlers': ['console'], 'level': level} +# Forbid store usage +STORE_URL = "" diff --git a/circle/common/models.py b/circle/common/models.py index 2bda712..d63beb3 100644 --- a/circle/common/models.py +++ b/circle/common/models.py @@ -23,6 +23,7 @@ from logging import getLogger from time import time from warnings import warn +from django.contrib import messages from django.contrib.auth.models import User from django.core.cache import cache from django.core.serializers.json import DjangoJSONEncoder @@ -46,17 +47,24 @@ class WorkerNotFound(Exception): def activitycontextimpl(act, on_abort=None, on_commit=None): try: - yield act - except BaseException as e: - # BaseException is the common parent of Exception and - # system-exiting exceptions, e.g. KeyboardInterrupt + try: + yield act + except HumanReadableException as e: + result = e + raise + except BaseException as e: + # BaseException is the common parent of Exception and + # system-exiting exceptions, e.g. KeyboardInterrupt + result = create_readable( + ugettext_noop("Failure."), + ugettext_noop("Unhandled exception: %(error)s"), + error=unicode(e)) + raise + except: + logger.exception("Failed activity %s" % unicode(act)) handler = None if on_abort is None else lambda a: on_abort(a, e) - result = create_readable(ugettext_noop("Failure."), - ugettext_noop("Unhandled exception: " - "%(error)s"), - error=unicode(e)) act.finish(succeeded=False, result=result, event_handler=handler) - raise e + raise else: act.finish(succeeded=True, event_handler=on_commit) @@ -70,11 +78,11 @@ activity_code_separator = '.' def has_prefix(activity_code, *prefixes): """Determine whether the activity code has the specified prefix. - E.g.: has_prefix('foo.bar.buz', 'foo.bar') == True - has_prefix('foo.bar.buz', 'foo', 'bar') == True - has_prefix('foo.bar.buz', 'foo.bar', 'buz') == True - has_prefix('foo.bar.buz', 'foo', 'bar', 'buz') == True - has_prefix('foo.bar.buz', 'foo', 'buz') == False + >>> assert has_prefix('foo.bar.buz', 'foo.bar') + >>> assert has_prefix('foo.bar.buz', 'foo', 'bar') + >>> assert has_prefix('foo.bar.buz', 'foo.bar', 'buz') + >>> assert has_prefix('foo.bar.buz', 'foo', 'bar', 'buz') + >>> assert not has_prefix('foo.bar.buz', 'foo', 'buz') """ equal = lambda a, b: a == b act_code_parts = split_activity_code(activity_code) @@ -85,11 +93,11 @@ def has_prefix(activity_code, *prefixes): def has_suffix(activity_code, *suffixes): """Determine whether the activity code has the specified suffix. - E.g.: has_suffix('foo.bar.buz', 'bar.buz') == True - has_suffix('foo.bar.buz', 'bar', 'buz') == True - has_suffix('foo.bar.buz', 'foo.bar', 'buz') == True - has_suffix('foo.bar.buz', 'foo', 'bar', 'buz') == True - has_suffix('foo.bar.buz', 'foo', 'buz') == False + >>> assert has_suffix('foo.bar.buz', 'bar.buz') + >>> assert has_suffix('foo.bar.buz', 'bar', 'buz') + >>> assert has_suffix('foo.bar.buz', 'foo.bar', 'buz') + >>> assert has_suffix('foo.bar.buz', 'foo', 'bar', 'buz') + >>> assert not has_suffix('foo.bar.buz', 'foo', 'buz') """ equal = lambda a, b: a == b act_code_parts = split_activity_code(activity_code) @@ -196,6 +204,10 @@ class ActivityModel(TimeStampedModel): DeprecationWarning, stacklevel=2) value = create_readable(user_text_template="", admin_text_template=value) + elif not hasattr(value, "to_dict"): + warn("Use HumanReadableObject.", DeprecationWarning, stacklevel=2) + value = create_readable(user_text_template="", + admin_text_template=unicode(value)) self.result_data = None if value is None else value.to_dict() @@ -361,8 +373,9 @@ class HumanReadableObject(object): @classmethod def create(cls, user_text_template, admin_text_template=None, **params): - return cls(user_text_template, - admin_text_template or user_text_template, params) + return cls(user_text_template=user_text_template, + admin_text_template=(admin_text_template + or user_text_template), params=params) def set(self, user_text_template, admin_text_template=None, **params): self._set_values(user_text_template, @@ -407,10 +420,29 @@ create_readable = HumanReadableObject.create class HumanReadableException(HumanReadableObject, Exception): """HumanReadableObject that is an Exception so can used in except clause. """ - pass + + def __init__(self, level=None, *args, **kwargs): + super(HumanReadableException, self).__init__(*args, **kwargs) + if level is not None: + if hasattr(messages, level): + self.level = level + else: + raise ValueError( + "Level should be the name of an attribute of django." + "contrib.messages (and it should be callable with " + "(request, message)). Like 'error', 'warning'.") + else: + self.level = "error" + + def send_message(self, request, level=None): + if request.user and request.user.is_superuser: + msg = self.get_admin_text() + else: + msg = self.get_user_text() + getattr(messages, level or self.level)(request, msg) -def humanize_exception(message, exception=None, **params): +def humanize_exception(message, exception=None, level=None, **params): """Return new dynamic-class exception which is based on HumanReadableException and the original class with the dict of exception. @@ -423,4 +455,7 @@ def humanize_exception(message, exception=None, **params): Ex = type("HumanReadable" + type(exception).__name__, (HumanReadableException, type(exception)), exception.__dict__) - return Ex.create(message, **params) + ex = Ex.create(message, **params) + if level: + ex.level = level + return ex diff --git a/circle/dashboard/fixtures/test-vm-fixture.json b/circle/dashboard/fixtures/test-vm-fixture.json index c23bd99..2e10104 100644 --- a/circle/dashboard/fixtures/test-vm-fixture.json +++ b/circle/dashboard/fixtures/test-vm-fixture.json @@ -1322,7 +1322,7 @@ "user_permissions": [ 115 ], - "password": "pbkdf2_sha256$10000$KIoeMs78MiOj$PnVXn3YJMehbOciBO32CMzqL0ZnQrzrdb7+b5dE13os=", + "password": "md5$qLN4mQMOrsUJ$f07129fd1a289a0afb4e09f7a6816a4f", "email": "test@example.org", "date_joined": "2013-09-04T15:29:49.914Z" } @@ -1382,7 +1382,7 @@ "pw": "ads", "time_of_suspend": null, "ram_size": 200, - "priority": 4, + "priority": 10, "active_since": null, "template": null, "access_method": "nx", @@ -1412,7 +1412,7 @@ "pw": "ads", "time_of_suspend": null, "ram_size": 200, - "priority": 4, + "priority": 10, "active_since": null, "template": null, "access_method": "nx", @@ -1518,7 +1518,7 @@ "ram_size": 1024, "modified": "2014-01-24T00:58:19.654Z", "system": "bubuntu", - "priority": 20, + "priority": 10, "access_method": "ssh", "raw_data": "", "arch": "x86_64", diff --git a/circle/dashboard/forms.py b/circle/dashboard/forms.py index bbef8d3..56f21a3 100644 --- a/circle/dashboard/forms.py +++ b/circle/dashboard/forms.py @@ -30,7 +30,7 @@ from django.core.exceptions import PermissionDenied, ValidationError import autocomplete_light from crispy_forms.helper import FormHelper from crispy_forms.layout import ( - Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK, + Layout, Div, BaseInput, Field, HTML, Submit, TEMPLATE_PACK, ) from crispy_forms.utils import render_field @@ -51,7 +51,7 @@ from vm.models import ( from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.auth.models import Permission from .models import Profile, GroupProfile -from circle.settings.base import LANGUAGES +from circle.settings.base import LANGUAGES, MAX_NODE_RAM from django.utils.translation import string_concat from .virtvalidator import domain_validator @@ -59,6 +59,13 @@ from .virtvalidator import domain_validator LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")")) for l in LANGUAGES) +priority_choices = ( + (10, _("idle")), + (30, _("normal")), + (80, _("server")), + (100, _("realtime")), +) + class VmSaveForm(forms.Form): name = forms.CharField(max_length=100, label=_('Name'), @@ -72,19 +79,62 @@ class VmSaveForm(forms.Form): class VmCustomizeForm(forms.Form): - name = forms.CharField() - cpu_priority = forms.IntegerField() - cpu_count = forms.IntegerField() - ram_size = forms.IntegerField() - amount = forms.IntegerField(min_value=0, initial=1) + name = forms.CharField(widget=forms.TextInput(attrs={ + 'class': "form-control", + 'style': "max-width: 350px", + 'required': "", + })) + + cpu_count = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags cpu-count-input", + 'min': 1, + 'max': 10, + 'required': "", + }), + min_value=1, max_value=10, + ) + + ram_size = forms.IntegerField(widget=forms.TextInput(attrs={ + 'class': "form-control input-tags ram-input", + 'min': 128, + 'pattern': "\d+", + 'max': MAX_NODE_RAM, + 'step': 128, + 'required': "", + }), + min_value=128, max_value=MAX_NODE_RAM, + ) + + cpu_priority = forms.ChoiceField( + priority_choices, widget=forms.Select(attrs={ + 'class': "form-control input-tags cpu-priority-input", + }) + ) + + amount = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control", + 'min': "1", + 'style': "max-width: 60px", + 'required': "", + }), initial=1, min_value=1) disks = forms.ModelMultipleChoiceField( - queryset=None, required=False) + queryset=None, required=False, + widget=forms.SelectMultiple(attrs={ + 'class': "form-control", + 'id': "vm-create-disk-add-form", + }) + ) networks = forms.ModelMultipleChoiceField( - queryset=None, required=False) + queryset=None, required=False, + widget=forms.SelectMultiple(attrs={ + 'class': "form-control", + 'id': "vm-create-network-add-vlan", + }) + ) - template = forms.CharField() - customized = forms.CharField() # dummy flag field + template = forms.CharField(widget=forms.HiddenInput()) + customized = forms.CharField(widget=forms.HiddenInput()) def __init__(self, *args, **kwargs): self.user = kwargs.pop("user", None) @@ -111,230 +161,6 @@ class VmCustomizeForm(forms.Form): self.initial['template'] = self.template.pk self.initial['customized'] = self.template.pk - # set widget for amount - self.fields['amount'].widget = NumberInput() - - self.helper = FormHelper(self) - - # don't show labels for the sliders - self.helper.form_show_labels = True - self.fields['cpu_count'].label = "" - self.fields['ram_size'].label = "" - self.fields['cpu_priority'].label = "" - - self.helper.layout = Layout( - Field("template", type="hidden"), - Field("customized", type="hidden"), - Div( - Div( - AnyTag( # tip: don't try to use Button class - "button", - AnyTag( - "i", - css_class="fa fa-play" - ), - HTML(" Start"), - css_id="vm-create-customized-start", - css_class="btn btn-success", - style="float: right; margin-top: 24px;", - ), - Field("name", style="max-width: 350px;"), - css_class="col-sm-12", - ), - css_class="row", - ), - Div( - Div( - Field("amount", min="1", style="max-width: 60px;"), - css_class="col-sm-10", - ), - css_class="row", - ), - Div( - Div( - AnyTag( - 'h2', - HTML(_("Resources")), - ), - css_class="col-sm-12", - ), - css_class="row", - ), - Div( # cpu priority - Div( - HTML(''), - css_class="col-sm-3" - ), - Div( - Field('cpu_priority', id="vm-cpu-priority-slider", - css_class="vm-slider", - data_slider_min="0", data_slider_max="100", - data_slider_step="1", - data_slider_value=self.template.priority, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( # cpu count - Div( - HTML(''), - css_class="col-sm-3" - ), - Div( - Field('cpu_count', id="vm-cpu-count-slider", - css_class="vm-slider", - data_slider_min="1", data_slider_max="8", - data_slider_step="1", - data_slider_value=self.template.num_cores, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( # ram size - Div( - HTML(''), - css_class="col-sm-3" - ), - Div( - Field('ram_size', id="vm-ram-size-slider", - css_class="vm-slider", - data_slider_min="128", data_slider_max="4096", - data_slider_step="128", - data_slider_value=self.template.ram_size, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( # disks - Div( - AnyTag( - "h2", - HTML("Disks") - ), - css_class="col-sm-4", - ), - Div( - Div( - Field("disks", css_class="form-control", - id="vm-create-disk-add-form"), - css_class="js-hidden", - style="padding-top: 15px; max-width: 450px;", - ), - Div( - AnyTag( - "h3", - HTML(_("No disks are added!")), - css_id="vm-create-disk-list", - ), - Div( - HTML(""), - style="clear: both;", - ), - # AnyTag( - # "h3", - # Div( - # AnyTag( - # "select", - # css_class="form-control", - # css_id="vm-create-disk-add-select", - # ), - # Div( - # AnyTag( - # "a", - # AnyTag( - # "i", - # css_class="icon-plus-sign", - # ), - # href="#", - # css_id="vm-create-disk-add-button", - # css_class="btn btn-success", - # ), - # css_class="input-group-btn" - # ), - # css_class="input-group", - # style="max-width: 330px;", - # ), - # css_id="vm-create-disk-add", - # ), - css_class="no-js-hidden", - ), - css_class="col-sm-8", - style="padding-top: 3px;", - ), - css_class="row", - ), # end of disks - Div( # network - Div( - AnyTag( - "h2", - HTML(_("Network")), - ), - css_class="col-sm-4", - ), - Div( - Div( # js-hidden - Field( - "networks", - css_class="form-control", - id="vm-create-network-add-vlan", - ), - css_class="js-hidden", - style="padding-top: 15px; max-width: 450px;", - ), - Div( # no-js-hidden - AnyTag( - "h3", - HTML(_("Not added to any network!")), - css_id="vm-create-network-list", - ), - AnyTag( - "h3", - Div( - AnyTag( - "select", - css_class=("form-control " - "font-awesome-font"), - css_id="vm-create-network-add-select", - ), - Div( - AnyTag( - "a", - AnyTag( - "i", - css_class="fa fa-plus-circle", - ), - css_id=("vm-create-network-add" - "-button"), - css_class="btn btn-success", - ), - css_class="input-group-btn", - ), - css_class="input-group", - style="max-width: 330px;", - ), - css_class="vm-create-network-add" - ), - css_class="no-js-hidden", - ), - css_class="col-sm-8", - style="padding-top: 3px;", - ), - css_class="row" - ), # end of network - ) - class GroupCreateForm(forms.ModelForm): @@ -581,6 +407,29 @@ class TemplateForm(forms.ModelForm): networks = forms.ModelMultipleChoiceField( queryset=None, required=False, label=_("Networks")) + num_cores = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags cpu-count-input", + 'min': 1, + 'max': 10, + 'required': "", + }), + min_value=1, max_value=10, + ) + + ram_size = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags ram-input", + 'min': 128, + 'max': MAX_NODE_RAM, + 'step': 128, + 'required': "", + }), + min_value=128, max_value=MAX_NODE_RAM, + ) + + priority = forms.ChoiceField(priority_choices, widget=forms.Select(attrs={ + 'class': "form-control input-tags cpu-priority-input", + })) + def __init__(self, *args, **kwargs): self.user = kwargs.pop("user", None) super(TemplateForm, self).__init__(*args, **kwargs) @@ -612,18 +461,27 @@ class TemplateForm(forms.ModelForm): field.widget.attrs['disabled'] = 'disabled' if not self.instance.pk and len(self.errors) < 1: - self.instance.priority = 20 - self.instance.ram_size = 512 - self.instance.num_cores = 2 + self.initial['num_cores'] = 1 + self.initial['priority'] = 10 + self.initial['ram_size'] = 512 + self.initial['max_ram_size'] = 512 + + lease_queryset = ( + Lease.get_objects_with_level("operator", self.user).distinct() + | Lease.objects.filter(pk=self.instance.lease_id).distinct()) - self.fields["lease"].queryset = Lease.get_objects_with_level( - "operator", self.user) + self.fields["lease"].queryset = lease_queryset + + self.fields['raw_data'].validators.append(domain_validator) def clean_owner(self): if self.instance.pk is not None: return User.objects.get(pk=self.instance.owner.pk) return self.user + def clean_max_ram_size(self): + return self.cleaned_data.get("ram_size", 0) + def _clean_fields(self): try: old = InstanceTemplate.objects.get(pk=self.instance.pk) @@ -685,77 +543,14 @@ class TemplateForm(forms.ModelForm): submit_kwargs['disabled'] = None helper = FormHelper() - helper.layout = Layout( - Field("name"), - Fieldset( - _("Resource configuration"), - Div( # cpu count - Div( - Field('num_cores', id="vm-cpu-count-slider", - css_class="vm-slider", - data_slider_min="1", data_slider_max="8", - data_slider_step="1", - data_slider_value=self.instance.num_cores, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( # cpu priority - Div( - Field('priority', id="vm-cpu-priority-slider", - css_class="vm-slider", - data_slider_min="0", data_slider_max="100", - data_slider_step="1", - data_slider_value=self.instance.priority, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row" - ), - Div( - Div( - Field('ram_size', id="vm-ram-size-slider", - css_class="vm-slider", - data_slider_min="128", data_slider_max="4096", - data_slider_step="128", - data_slider_value=self.instance.ram_size, - data_slider_handle="square", - data_slider_tooltip="hide"), - css_class="col-sm-9" - ), - css_class="row", - ), - Field('max_ram_size', type="hidden", value="0"), - Field('arch'), - ), - Fieldset( - _("Virtual machine settings"), - Field('access_method'), - Field('boot_menu'), - Field('raw_data'), - Field('req_traits'), - Field('description'), - Field("parent", type="hidden"), - Field("system"), - ), - Fieldset( - _("External resources"), - Field("networks"), - Field("lease"), - Field("tags"), - ), - ) - helper.add_input(Submit('submit', 'Save changes', **submit_kwargs)) return helper class Meta: model = InstanceTemplate exclude = ('state', 'disks', ) widgets = { - 'system': forms.TextInput + 'system': forms.TextInput, + 'max_ram_size': forms.HiddenInput } @@ -902,16 +697,18 @@ class LeaseForm(forms.ModelForm): class VmRenewForm(forms.Form): + force = forms.BooleanField(required=False, label=_( + "Set expiration times even if they are shorter than " + "the current value.")) + def __init__(self, *args, **kwargs): choices = kwargs.pop('choices') default = kwargs.pop('default') super(VmRenewForm, self).__init__(*args, **kwargs) - self.fields['lease'] = forms.ModelChoiceField(queryset=choices, - initial=default, - required=False, - empty_label=None, - label=_('Length')) + self.fields.insert(0, 'lease', forms.ModelChoiceField( + queryset=choices, initial=default, required=False, + empty_label=None, label=_('Length'))) if len(choices) < 2: self.fields['lease'].widget = HiddenInput() @@ -1303,3 +1100,32 @@ class GroupPermissionForm(forms.ModelForm): helper.add_input(Submit("submit", _("Save"), css_class="btn btn-success", )) return helper + + +class VmResourcesForm(forms.ModelForm): + num_cores = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags cpu-count-input", + 'min': 1, + 'max': 10, + 'required': "", + }), + min_value=1, max_value=10, + ) + + ram_size = forms.IntegerField(widget=forms.NumberInput(attrs={ + 'class': "form-control input-tags ram-input", + 'min': 128, + 'max': MAX_NODE_RAM, + 'step': 128, + 'required': "", + }), + min_value=128, max_value=MAX_NODE_RAM, + ) + + priority = forms.ChoiceField(priority_choices, widget=forms.Select(attrs={ + 'class': "form-control input-tags cpu-priority-input", + })) + + class Meta: + model = Instance + fields = ('num_cores', 'priority', 'ram_size', ) diff --git a/circle/dashboard/migrations/0010_auto__add_field_profile_smb_password__add_field_profile_disk_quota.py b/circle/dashboard/migrations/0010_auto__add_field_profile_smb_password__add_field_profile_disk_quota.py new file mode 100644 index 0000000..147a287 --- /dev/null +++ b/circle/dashboard/migrations/0010_auto__add_field_profile_smb_password__add_field_profile_disk_quota.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Profile.smb_password' + db.add_column(u'dashboard_profile', 'smb_password', + self.gf('django.db.models.fields.CharField')(default='asdasd', max_length=20), + keep_default=False) + + # Adding field 'Profile.disk_quota' + db.add_column(u'dashboard_profile', 'disk_quota', + self.gf('django.db.models.fields.IntegerField')(default=2048), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Profile.smb_password' + db.delete_column(u'dashboard_profile', 'smb_password') + + # Deleting field 'Profile.disk_quota' + db.delete_column(u'dashboard_profile', 'disk_quota') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'dashboard.favourite': { + 'Meta': {'object_name': 'Favourite'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Instance']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'dashboard.groupprofile': { + 'Meta': {'object_name': 'GroupProfile'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'group': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.Group']", 'unique': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'org_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + u'dashboard.notification': { + 'Meta': {'ordering': "['-created']", 'object_name': 'Notification'}, + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'status': ('model_utils.fields.StatusField', [], {'default': "'new'", 'max_length': '100', u'no_check_for_status': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'to': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'valid_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) + }, + u'dashboard.profile': { + 'Meta': {'object_name': 'Profile'}, + 'disk_quota': ('django.db.models.fields.IntegerField', [], {'default': '2048'}), + 'email_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instance_limit': ('django.db.models.fields.IntegerField', [], {'default': '5'}), + 'org_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'preferred_language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '32'}), + 'smb_password': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'use_gravatar': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'firewall.domain': { + 'Meta': {'object_name': 'Domain'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'ttl': ('django.db.models.fields.IntegerField', [], {'default': '600'}) + }, + u'firewall.group': { + 'Meta': {'object_name': 'Group'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + u'firewall.host': { + 'Meta': {'ordering': "('normalized_hostname', 'vlan')", 'unique_together': "(('hostname', 'vlan'),)", 'object_name': 'Host'}, + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'external_ipv4': ('firewall.fields.IPAddressField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['firewall.Group']", 'null': 'True', 'blank': 'True'}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ipv4': ('firewall.fields.IPAddressField', [], {'unique': 'True', 'max_length': '100'}), + 'ipv6': ('firewall.fields.IPAddressField', [], {'max_length': '100', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'mac': ('firewall.fields.MACAddressField', [], {'unique': 'True', 'max_length': '17'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'normalized_hostname': ('common.models.HumanSortField', [], {'default': "''", 'maximum_number_length': '4', 'max_length': '80', 'monitor': "'hostname'", 'blank': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'reverse': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'shared_ip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Vlan']"}) + }, + u'firewall.vlan': { + 'Meta': {'object_name': 'Vlan'}, + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'dhcp_pool': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Domain']"}), + 'host_ipv6_prefixlen': ('django.db.models.fields.IntegerField', [], {'default': '112'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ipv6_template': ('django.db.models.fields.TextField', [], {'default': "'2001:738:2001:4031:%(b)d:%(c)d:%(d)d:0'"}), + 'managed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}), + 'network4': ('firewall.fields.IPNetworkField', [], {'max_length': '100'}), + 'network6': ('firewall.fields.IPNetworkField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'network_type': ('django.db.models.fields.CharField', [], {'default': "'portforward'", 'max_length': '20'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'reverse_domain': ('django.db.models.fields.TextField', [], {'default': "'%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa'"}), + 'snat_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}), + 'snat_to': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['firewall.Vlan']", 'null': 'True', 'blank': 'True'}), + 'vid': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}) + }, + u'storage.datastore': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'DataStore'}, + 'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}) + }, + u'storage.disk': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'Disk'}, + 'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'derivatives'", 'null': 'True', 'to': u"orm['storage.Disk']"}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['storage.DataStore']"}), + 'destroyed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'dev_num': ('django.db.models.fields.CharField', [], {'default': "u'a'", 'max_length': '1'}), + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_ready': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'size': ('sizefield.models.FileSizeField', [], {'default': 'None', 'null': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '10'}) + }, + u'vm.instance': { + 'Meta': {'ordering': "(u'pk',)", 'object_name': 'Instance'}, + 'access_method': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'active_since': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'boot_menu': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'destroyed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'disks': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'instance_set'", 'symmetrical': 'False', 'to': u"orm['storage.Disk']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_base': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'lease': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Lease']"}), + 'max_ram_size': ('django.db.models.fields.IntegerField', [], {}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'instance_set'", 'null': 'True', 'to': u"orm['vm.Node']"}), + 'num_cores': ('django.db.models.fields.IntegerField', [], {}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {}), + 'pw': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'ram_size': ('django.db.models.fields.IntegerField', [], {}), + 'raw_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'req_traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}), + 'status': ('model_utils.fields.StatusField', [], {'default': "u'NOSTATE'", 'max_length': '100', u'no_check_for_status': 'True'}), + 'status_changed': ('model_utils.fields.MonitorField', [], {'default': 'datetime.datetime.now', u'monitor': "u'status'"}), + 'system': ('django.db.models.fields.TextField', [], {}), + 'template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'instance_set'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['vm.InstanceTemplate']"}), + 'time_of_delete': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'time_of_suspend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'vnc_port': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + u'vm.instancetemplate': { + 'Meta': {'ordering': "(u'name',)", 'object_name': 'InstanceTemplate'}, + 'access_method': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'boot_menu': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'disks': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'template_set'", 'symmetrical': 'False', 'to': u"orm['storage.Disk']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lease': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Lease']"}), + 'max_ram_size': ('django.db.models.fields.IntegerField', [], {}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'num_cores': ('django.db.models.fields.IntegerField', [], {}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.InstanceTemplate']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'priority': ('django.db.models.fields.IntegerField', [], {}), + 'ram_size': ('django.db.models.fields.IntegerField', [], {}), + 'raw_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'req_traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}), + 'system': ('django.db.models.fields.TextField', [], {}) + }, + u'vm.lease': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'Lease'}, + 'delete_interval_seconds': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'suspend_interval_seconds': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + u'vm.node': { + 'Meta': {'ordering': "(u'-enabled', u'normalized_name')", 'object_name': 'Node'}, + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'host': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Host']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}), + 'normalized_name': ('common.models.HumanSortField', [], {'default': "''", 'maximum_number_length': '4', 'max_length': '100', 'monitor': "u'name'", 'blank': 'True'}), + 'overcommit': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + 'priority': ('django.db.models.fields.IntegerField', [], {}), + 'traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'vm.trait': { + 'Meta': {'object_name': 'Trait'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + } + } + + complete_apps = ['dashboard'] \ No newline at end of file diff --git a/circle/dashboard/migrations/0010_auto__add_futuremember__add_unique_futuremember_org_id_group.py b/circle/dashboard/migrations/0010_auto__add_futuremember__add_unique_futuremember_org_id_group.py index c913946..e370e84 100644 --- a/circle/dashboard/migrations/0010_auto__add_futuremember__add_unique_futuremember_org_id_group.py +++ b/circle/dashboard/migrations/0010_auto__add_futuremember__add_unique_futuremember_org_id_group.py @@ -97,11 +97,13 @@ class Migration(SchemaMigration): }, u'dashboard.profile': { 'Meta': {'object_name': 'Profile'}, + 'disk_quota': ('django.db.models.fields.IntegerField', [], {'default': '2048'}), 'email_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'instance_limit': ('django.db.models.fields.IntegerField', [], {'default': '5'}), 'org_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}), 'preferred_language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '32'}), + 'smb_password': ('django.db.models.fields.CharField', [], {'max_length': '20'}), 'use_gravatar': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) }, @@ -269,4 +271,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['dashboard'] \ No newline at end of file + complete_apps = ['dashboard'] diff --git a/circle/dashboard/migrations/0011_auto__add_field_notification_subject_data__add_field_notification_mess.py b/circle/dashboard/migrations/0011_auto__add_field_notification_subject_data__add_field_notification_mess.py index 51c31a9..7c6b311 100644 --- a/circle/dashboard/migrations/0011_auto__add_field_notification_subject_data__add_field_notification_mess.py +++ b/circle/dashboard/migrations/0011_auto__add_field_notification_subject_data__add_field_notification_mess.py @@ -98,11 +98,13 @@ class Migration(SchemaMigration): }, u'dashboard.profile': { 'Meta': {'object_name': 'Profile'}, + 'disk_quota': ('django.db.models.fields.IntegerField', [], {'default': '2048'}), 'email_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'instance_limit': ('django.db.models.fields.IntegerField', [], {'default': '5'}), 'org_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}), 'preferred_language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '32'}), + 'smb_password': ('django.db.models.fields.CharField', [], {'max_length': '20'}), 'use_gravatar': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) }, @@ -270,4 +272,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['dashboard'] \ No newline at end of file + complete_apps = ['dashboard'] diff --git a/circle/dashboard/migrations/0012_migrate_messages.py b/circle/dashboard/migrations/0012_migrate_messages.py index f06b10f..f8faf6d 100644 --- a/circle/dashboard/migrations/0012_migrate_messages.py +++ b/circle/dashboard/migrations/0012_migrate_messages.py @@ -93,11 +93,13 @@ class Migration(DataMigration): }, u'dashboard.profile': { 'Meta': {'object_name': 'Profile'}, + 'disk_quota': ('django.db.models.fields.IntegerField', [], {'default': '2048'}), 'email_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'instance_limit': ('django.db.models.fields.IntegerField', [], {'default': '5'}), 'org_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}), 'preferred_language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '32'}), + 'smb_password': ('django.db.models.fields.CharField', [], {'max_length': '20'}), 'use_gravatar': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) }, diff --git a/circle/dashboard/migrations/0013_auto__del_field_notification_message__del_field_notification_subject.py b/circle/dashboard/migrations/0013_auto__del_field_notification_message__del_field_notification_subject.py index 5a89b92..a0b2f99 100644 --- a/circle/dashboard/migrations/0013_auto__del_field_notification_message__del_field_notification_subject.py +++ b/circle/dashboard/migrations/0013_auto__del_field_notification_message__del_field_notification_subject.py @@ -96,11 +96,13 @@ class Migration(SchemaMigration): }, u'dashboard.profile': { 'Meta': {'object_name': 'Profile'}, + 'disk_quota': ('django.db.models.fields.IntegerField', [], {'default': '2048'}), 'email_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'instance_limit': ('django.db.models.fields.IntegerField', [], {'default': '5'}), 'org_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}), 'preferred_language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '32'}), + 'smb_password': ('django.db.models.fields.CharField', [], {'max_length': '20'}), 'use_gravatar': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) }, @@ -268,4 +270,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['dashboard'] \ No newline at end of file + complete_apps = ['dashboard'] diff --git a/circle/dashboard/migrations/0014_auto__chg_field_profile_disk_quota.py b/circle/dashboard/migrations/0014_auto__chg_field_profile_disk_quota.py new file mode 100644 index 0000000..80d43b4 --- /dev/null +++ b/circle/dashboard/migrations/0014_auto__chg_field_profile_disk_quota.py @@ -0,0 +1,263 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'Profile.disk_quota' + db.alter_column(u'dashboard_profile', 'disk_quota', self.gf('sizefield.models.FileSizeField')()) + + def backwards(self, orm): + + # Changing field 'Profile.disk_quota' + db.alter_column(u'dashboard_profile', 'disk_quota', self.gf('django.db.models.fields.IntegerField')()) + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'dashboard.favourite': { + 'Meta': {'object_name': 'Favourite'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Instance']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'dashboard.futuremember': { + 'Meta': {'unique_together': "(('org_id', 'group'),)", 'object_name': 'FutureMember'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'org_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}) + }, + u'dashboard.groupprofile': { + 'Meta': {'object_name': 'GroupProfile'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'group': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.Group']", 'unique': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'org_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + u'dashboard.notification': { + 'Meta': {'ordering': "['-created']", 'object_name': 'Notification'}, + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message_data': ('jsonfield.fields.JSONField', [], {'null': 'True'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'status': ('model_utils.fields.StatusField', [], {'default': "'new'", 'max_length': '100', u'no_check_for_status': 'True'}), + 'subject_data': ('jsonfield.fields.JSONField', [], {'null': 'True'}), + 'to': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'valid_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) + }, + u'dashboard.profile': { + 'Meta': {'object_name': 'Profile'}, + 'disk_quota': ('sizefield.models.FileSizeField', [], {'default': '2147483648'}), + 'email_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instance_limit': ('django.db.models.fields.IntegerField', [], {'default': '5'}), + 'org_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'preferred_language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '32'}), + 'smb_password': ('django.db.models.fields.CharField', [], {'default': "u'ztX9mwgeNM'", 'max_length': '20'}), + 'use_gravatar': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'firewall.domain': { + 'Meta': {'object_name': 'Domain'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'ttl': ('django.db.models.fields.IntegerField', [], {'default': '600'}) + }, + u'firewall.group': { + 'Meta': {'object_name': 'Group'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + u'firewall.host': { + 'Meta': {'ordering': "('normalized_hostname', 'vlan')", 'unique_together': "(('hostname', 'vlan'),)", 'object_name': 'Host'}, + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'external_ipv4': ('firewall.fields.IPAddressField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['firewall.Group']", 'null': 'True', 'blank': 'True'}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ipv4': ('firewall.fields.IPAddressField', [], {'unique': 'True', 'max_length': '100'}), + 'ipv6': ('firewall.fields.IPAddressField', [], {'max_length': '100', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'mac': ('firewall.fields.MACAddressField', [], {'unique': 'True', 'max_length': '17'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'normalized_hostname': ('common.models.HumanSortField', [], {'default': "''", 'maximum_number_length': '4', 'max_length': '80', 'monitor': "'hostname'", 'blank': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'reverse': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'shared_ip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Vlan']"}) + }, + u'firewall.vlan': { + 'Meta': {'object_name': 'Vlan'}, + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'dhcp_pool': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Domain']"}), + 'host_ipv6_prefixlen': ('django.db.models.fields.IntegerField', [], {'default': '112'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ipv6_template': ('django.db.models.fields.TextField', [], {'default': "'2001:738:2001:4031:%(b)d:%(c)d:%(d)d:0'"}), + 'managed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}), + 'network4': ('firewall.fields.IPNetworkField', [], {'max_length': '100'}), + 'network6': ('firewall.fields.IPNetworkField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'network_type': ('django.db.models.fields.CharField', [], {'default': "'portforward'", 'max_length': '20'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'reverse_domain': ('django.db.models.fields.TextField', [], {'default': "'%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa'"}), + 'snat_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}), + 'snat_to': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['firewall.Vlan']", 'null': 'True', 'blank': 'True'}), + 'vid': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}) + }, + u'storage.datastore': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'DataStore'}, + 'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}) + }, + u'storage.disk': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'Disk'}, + 'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'derivatives'", 'null': 'True', 'to': u"orm['storage.Disk']"}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['storage.DataStore']"}), + 'destroyed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'dev_num': ('django.db.models.fields.CharField', [], {'default': "u'a'", 'max_length': '1'}), + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_ready': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'size': ('sizefield.models.FileSizeField', [], {'default': 'None', 'null': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '10'}) + }, + u'vm.instance': { + 'Meta': {'ordering': "(u'pk',)", 'object_name': 'Instance'}, + 'access_method': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'active_since': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'boot_menu': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'destroyed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'disks': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'instance_set'", 'symmetrical': 'False', 'to': u"orm['storage.Disk']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_base': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'lease': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Lease']"}), + 'max_ram_size': ('django.db.models.fields.IntegerField', [], {}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'instance_set'", 'null': 'True', 'to': u"orm['vm.Node']"}), + 'num_cores': ('django.db.models.fields.IntegerField', [], {}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {}), + 'pw': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'ram_size': ('django.db.models.fields.IntegerField', [], {}), + 'raw_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'req_traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}), + 'status': ('model_utils.fields.StatusField', [], {'default': "u'NOSTATE'", 'max_length': '100', u'no_check_for_status': 'True'}), + 'status_changed': ('model_utils.fields.MonitorField', [], {'default': 'datetime.datetime.now', u'monitor': "u'status'"}), + 'system': ('django.db.models.fields.TextField', [], {}), + 'template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'instance_set'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['vm.InstanceTemplate']"}), + 'time_of_delete': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'time_of_suspend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'vnc_port': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + u'vm.instancetemplate': { + 'Meta': {'ordering': "(u'name',)", 'object_name': 'InstanceTemplate'}, + 'access_method': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'arch': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'boot_menu': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'disks': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'template_set'", 'symmetrical': 'False', 'to': u"orm['storage.Disk']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lease': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.Lease']"}), + 'max_ram_size': ('django.db.models.fields.IntegerField', [], {}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'num_cores': ('django.db.models.fields.IntegerField', [], {}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['vm.InstanceTemplate']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'priority': ('django.db.models.fields.IntegerField', [], {}), + 'ram_size': ('django.db.models.fields.IntegerField', [], {}), + 'raw_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'req_traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}), + 'system': ('django.db.models.fields.TextField', [], {}) + }, + u'vm.lease': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'Lease'}, + 'delete_interval_seconds': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'suspend_interval_seconds': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + u'vm.node': { + 'Meta': {'ordering': "(u'-enabled', u'normalized_name')", 'object_name': 'Node'}, + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'host': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['firewall.Host']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}), + 'normalized_name': ('common.models.HumanSortField', [], {'default': "''", 'maximum_number_length': '4', 'max_length': '100', 'monitor': "u'name'", 'blank': 'True'}), + 'overcommit': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + 'priority': ('django.db.models.fields.IntegerField', [], {}), + 'traits': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['vm.Trait']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'vm.trait': { + 'Meta': {'object_name': 'Trait'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + } + } + + complete_apps = ['dashboard'] \ No newline at end of file diff --git a/circle/dashboard/models.py b/circle/dashboard/models.py index 7106b86..044f0cd 100644 --- a/circle/dashboard/models.py +++ b/circle/dashboard/models.py @@ -29,10 +29,13 @@ from django.db.models import ( Model, ForeignKey, OneToOneField, CharField, IntegerField, TextField, DateTimeField, permalink, BooleanField ) -from django.db.models.signals import post_save, pre_delete +from django.db.models.signals import post_save, pre_delete, post_delete from django.templatetags.static import static from django.utils.translation import ugettext_lazy as _ from django_sshkey.models import UserKey +from django.core.exceptions import ObjectDoesNotExist + +from sizefield.models import FileSizeField from jsonfield import JSONField from model_utils.models import TimeStampedModel @@ -44,8 +47,12 @@ from common.models import HumanReadableObject, create_readable, Encoder from vm.tasks.agent_tasks import add_keys, del_keys +from .store_api import Store, NoStoreException, NotOkException + logger = getLogger(__name__) +pwgen = User.objects.make_random_password + class Favourite(Model): instance = ForeignKey("vm.Instance") @@ -109,6 +116,18 @@ class Profile(Model): email_notifications = BooleanField( verbose_name=_("Email notifications"), default=True, help_text=_('Whether user wants to get digested email notifications.')) + smb_password = CharField( + max_length=20, + verbose_name=_('Samba password'), + help_text=_( + 'Generated password for accessing store from ' + 'virtual machines.'), + default=pwgen, + ) + disk_quota = FileSizeField( + verbose_name=_('disk quota'), + default=2048 * 1024 * 1024, + help_text=_('Disk quota in mebibytes.')) def notify(self, subject, template, context=None, valid_until=None, **kwargs): @@ -201,6 +220,11 @@ def create_profile(sender, user, request, **kwargs): if not user.pk: return False profile, created = Profile.objects.get_or_create(user=user) + + try: + Store(user).create_user(profile.smb_password, None, profile.disk_quota) + except: + logger.exception("Can't create user %s", unicode(user)) return created user_logged_in.connect(create_profile) @@ -268,6 +292,44 @@ else: logger.debug("Do not register save_org_id to djangosaml2 pre_user_save") +def update_store_profile(sender, **kwargs): + profile = kwargs.get('instance') + keys = [i.key for i in profile.user.userkey_set.all()] + try: + s = Store(profile.user) + s.create_user(profile.smb_password, keys, + profile.disk_quota) + except NoStoreException: + logger.debug("Store is not available.") + except NotOkException: + logger.critical("Store is not accepting connections.") + + +post_save.connect(update_store_profile, sender=Profile) + + +def update_store_keys(sender, **kwargs): + userkey = kwargs.get('instance') + try: + profile = userkey.user.profile + except ObjectDoesNotExist: + pass # If there is no profile the user is deleted + else: + keys = [i.key for i in profile.user.userkey_set.all()] + try: + s = Store(userkey.user) + s.create_user(profile.smb_password, keys, + profile.disk_quota) + except NoStoreException: + logger.debug("Store is not available.") + except NotOkException: + logger.critical("Store is not accepting connections.") + + +post_save.connect(update_store_keys, sender=UserKey) +post_delete.connect(update_store_keys, sender=UserKey) + + def add_ssh_keys(sender, **kwargs): from vm.models import Instance diff --git a/circle/dashboard/static/dashboard/dashboard.css b/circle/dashboard/static/dashboard/dashboard.css index 1825604..65fb297 100644 --- a/circle/dashboard/static/dashboard/dashboard.css +++ b/circle/dashboard/static/dashboard/dashboard.css @@ -186,42 +186,6 @@ html { text-decoration: none !important; } -.slider { - display: inline-block; -} -.slider .track { - height: 20px; - top: 50%; -} -.slider > .dragger, .slider > .dragger:hover { - border-radius: 0px; - -moz-border-radius: 0px; - -webkit-border-radius: 0px; - width: 8px; - height: 24px; - margin-top: -12px!important; - text-shadow: 0 1px 0 #fff; - background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); - background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); - background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); - background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); - background-repeat: repeat-x; - border-color: #2d6ca2; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); -} -.slider > .dragger:hover { - background-color: #3071a9; - background-image: none; - border-color: #2d6ca2; -} - -.slider > .highlight-track { - height: 20px; - top: 50%; -} -.slider + .output { - -} .rule-table tr >:nth-child(1) { text-align: right; } @@ -723,6 +687,82 @@ textarea[name="list-new-namelist"] { } +#store-list-list { + list-style: none; +} + +.store-list-item { + cursor: pointer; +} + +.store-list-item:hover { + background: rgba(0, 0, 0, 0.6); +} + +.store-list-item-icon { + width: 20px; + text-align: center; + display: inline-block; + margin-right: 15px; + float: left; +} + +.store-list-item-size { + width: 70px; + text-align: right; + float: right; +} + +.store-list-file-infos { + padding: 15px; + display: none; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + position: relative; +} + +.store-list-item-new { + display: inline-block; +} + +.store-list-item-new .badge { + margin-left: 5px; + background: #5bc0dc; +} + +.store-list-item-icon-directory { + color: #ff8c00; +} + +.store-remove-button { + margin-top: 8px; +} + +#dashboard-files-toplist div.list-group-item { + color: #555; +} + +#dashboard-files-toplist div.list-group-item:hover { + background: #eee; +} + +.store-list-item-name { + max-width: 70%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + float: left; +} + +.dashboard-toplist-icon { + float: left; + padding: 2px 5px 0 0; +} + +.no-hover:hover { + background: none !important; +} + #group-detail-permissions .filtered { margin: 2px 0; padding: 2px 3px; @@ -752,6 +792,74 @@ textarea[name="list-new-namelist"] { margin-top: -6px; } +.store-action-button { + margin-left: 5px; +} + +#progress-marker-hard { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + right: 0; + background: red; +} + +.progress-marker { + width: 6px; + height: 20px; + position: absolute; +} + #show-all-activities-container { margin: 20px 0 0 10px; } + +#vm-details-resources-form { + margin-top: 15px; +} + +#vm-details-resources-form .row, .resources-sliders .row { + margin-bottom: 15px; +} + +#vm-create-disk-add-form { + max-width: 450px; + margin-top: 15px; +} + +.vm-create-template { + max-width: 800px; + border: 1px solid black; + border-bottom: none; +} + +.vm-create-template-list .vm-create-template:last-child { + border-bottom: 1px solid black; +} + +.vm-create-template-summary { + padding: 15px; + cursor: pointer; +} + +.vm-create-template:nth-child(odd) .vm-create-template-summary { + background: #F5F5F5; +} + +.vm-create-template-list .vm-create-template-summary:hover { + background: #D2D2D2; +} + +.vm-create-template-details { + border-top: 1px dashed #D3D3D3; + padding: 15px; +} + +.vm-create-template-details ul { + list-style: none; + padding: 0 15px; +} + +.vm-create-template-details li { + border-bottom: 1px dotted #aaa; + padding: 5px 0px; +} diff --git a/circle/dashboard/static/dashboard/dashboard.js b/circle/dashboard/static/dashboard/dashboard.js index b88eb2b..2269083 100644 --- a/circle/dashboard/static/dashboard/dashboard.js +++ b/circle/dashboard/static/dashboard/dashboard.js @@ -112,6 +112,7 @@ $(function () { /* no js compatibility */ + noJS(); $('.no-js-hidden').show(); $('.js-hidden').hide(); @@ -349,9 +350,9 @@ $(function () { } } for(var i=0; i<5 && i'; } -function generateGroupHTML(url, name) { - return ''+ +function generateGroupHTML(url, name, is_last) { + return ''+ ' '+ name + ''; } @@ -431,28 +435,63 @@ function compareVmByFav(a, b) { return a.pk < b.pk ? -1 : 1; } +$(document).on('shown.bs.tab', 'a[href="#resources"]', function (e) { + $(".cpu-priority-input").trigger("change"); + $(".cpu-count-input, .ram-input").trigger("input"); +}) + function addSliderMiscs() { - $('.vm-slider').each(function() { - $("").addClass("output").html($(this).val()).insertAfter($(this)); - }); - - $('.vm-slider').slider() - .on('slide', function(e) { - $(this).val(e.value); - $(this).parent('div').nextAll("span").html(e.value) + // set max values based on inputs + var cpu_count_range = "0, " + $(".cpu-count-input").prop("max"); + var ram_range = "0, " + $(".ram-input").prop("max"); + $(".cpu-count-slider").data("slider-range", cpu_count_range); + $(".ram-slider").data("slider-range", ram_range); + + $(".vm-slider").simpleSlider(); + $(".cpu-priority-slider").bind("slider:changed", function (event, data) { + var value = data.value + 0; + + $('.cpu-priority-input option[value="' + value + '"]').attr("selected", "selected"); }); - refreshSliders(); -} + $(".cpu-priority-input").change(function() { + var val = $(":selected", $(this)).val(); + $(".cpu-priority-slider").simpleSlider("setValue", val); + }); + + $(".cpu-count-slider").bind("slider:changed", function (event, data) { + var value = data.value + 0; + $(".cpu-count-input").val(parseInt(value)); + }); + + $(".cpu-count-input").bind("input", function() { + var val = parseInt($(this).val()); + if(!val) return; + $(".cpu-count-slider").simpleSlider("setValue", val); + }); + + + var ram_fire = false; + $(".ram-slider").bind("slider:changed", function (event, data) { + if(ram_fire) { + ram_fire = false; + return; + } -// ehhh -function refreshSliders() { - $('.vm-slider').each(function() { - $(this).val($(this).slider().data('slider').getValue()); - $(this).parent('div').nextAll("span").html($(this).val()); + var value = data.value + 0; + $(".ram-input").val(value); }); + + $(".ram-input").bind("input", function() { + var val = $(this).val(); + ram_fire = true; + $(".ram-slider").simpleSlider("setValue", parseInt(val)); + }); + $(".cpu-priority-input").trigger("change"); + $(".cpu-count-input, .ram-input").trigger("input"); } + /* deletes the VM with the pk * if dir is true, then redirect to the dashboard landing page * else it adds a success message */ @@ -561,3 +600,10 @@ function getCookie(name) { } return cookieValue; } + + +/* no js compatibility */ +function noJS() { + $('.no-js-hidden').show(); + $('.js-hidden').hide(); +} diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/.gitignore b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/.gitignore deleted file mode 100644 index 4381f42..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -*.zip -gh-pages \ No newline at end of file diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/README.md b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/README.md deleted file mode 100644 index 1ab4e1c..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/README.md +++ /dev/null @@ -1,27 +0,0 @@ -jQuery Simple Slider: Unobtrusive Numerical Slider -================================================== - -SimpleSlider is a jQuery plugin for turning your text inputs into draggable -numerical sliders. - -It has no external dependencies other than jQuery, and you don't need to write -a single line of JavaScript to get it to work. - - -How to Use ------------ - -Include the javascript file in your page: - - - -Turn your text input into a slider: - - - - -Documentation, Features and Demos ---------------------------------- -Full details and documentation can be found on the project page here: - - \ No newline at end of file diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/demo.html b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/demo.html deleted file mode 100644 index 1bf42ec..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/demo.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - -

jQuery Simple Slider Examples

-

- Here are a few examples of the functionality available in Simple Slider. -

- - -

Basic Example

- - -

Basic Example (Themed)

- - -

Predefined Value

- - -

Steps

- - -

Range

- - -

Range & Steps

- - -

Range, Steps & Snap

- - -

Predefined List of Values

- - -

Predefined List & Snap

- - -

Predefined List, Equal Steps & Snap

- - -

Highlighted

- - -

Highlighted (Themed)

- - - - - diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/grunt.js b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/grunt.js deleted file mode 100644 index eea6217..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/grunt.js +++ /dev/null @@ -1,68 +0,0 @@ -module.exports = function(grunt) { - grunt.initConfig({ - pkg: '', - - meta: { - banner: - '/*\n' + - ' * <%= pkg.title || pkg.name %>: <%= pkg.description %>\n' + - ' * Version <%= pkg.version %>\n' + - ' *\n' + - ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> (<%= pkg.author.url %>)\n' + - ' *\n' + - ' * Licensed under the <%= pkg.licenses[0].type %> license (<%= pkg.licenses[0].url %>)\n' + - ' *\n' + - ' */\n' - }, - - coffee: { - compile: { - files: { - 'js/<%= pkg.name %>.js': 'js/*.coffee' - }, - - options: { - bare: true - } - } - }, - - watch: { - coffee: { - files: ['js/*.coffee'], - tasks: 'coffee growl:coffee' - } - }, - - growl: { - coffee: { - title: 'CoffeeScript', - message: 'Compiled successfully' - } - }, - - min: { - dist: { - src: ['', 'js/<%= pkg.name %>.js'], - dest: 'js/<%= pkg.name %>.min.js' - } - }, - - compress: { - zip: { - files: { - "<%= pkg.name %>-<%= pkg.version %>.zip": ["js/**", "demo.html", "README.md"] - } - } - } - }); - - // Lib tasks. - grunt.loadNpmTasks('grunt-contrib'); - grunt.loadNpmTasks('grunt-growl'); - - // Default task. - grunt.registerTask('build', 'coffee min'); - grunt.registerTask('serve', 'server watch:coffee'); - grunt.registerTask('default', 'build'); -}; \ No newline at end of file diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.coffee b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.coffee deleted file mode 100644 index 9c3d40c..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.coffee +++ /dev/null @@ -1,361 +0,0 @@ -### - jQuery Simple Slider - - Copyright (c) 2012 James Smith (http://loopj.com) - - Licensed under the MIT license (http://mit-license.org/) -### - -(($, window) -> - - # - # Main slider class - # - - class SimpleSlider - # Build a slider object. - # Exposed via el.numericalSlider(options) - constructor: (@input, options) -> - # Load in the settings - @defaultOptions = - animate: true - snapMid: false - classPrefix: null - classSuffix: null - theme: null - highlight: false - - @settings = $.extend({}, @defaultOptions, options) - @settings.classSuffix = "-#{@settings.theme}" if @settings.theme - - # Hide the original input - @input.hide() - - # Create the slider canvas - @slider = $("
") - .addClass("slider"+(@settings.classSuffix || "")) - .css - position: "relative" - userSelect: "none" - boxSizing: "border-box" - .insertBefore @input - @slider.attr("id", @input.attr("id") + "-slider") if @input.attr("id") - - @track = @createDivElement("track") - .css - width: "100%" - - if @settings.highlight - # Create the highlighting track on top of the track - @highlightTrack = @createDivElement("highlight-track") - .css - width: "0" - - # Create the slider drag target - @dragger = @createDivElement("dragger") - - # Adjust dimensions now elements are in the DOM - @slider.css - minHeight: @dragger.outerHeight() - marginLeft: @dragger.outerWidth()/2 - marginRight: @dragger.outerWidth()/2 - - @track.css - marginTop: @track.outerHeight()/-2 - - if @settings.highlight - @highlightTrack.css - marginTop: @track.outerHeight()/-2 - - @dragger.css - marginTop: @dragger.outerWidth()/-2 - marginLeft: @dragger.outerWidth()/-2 - - # Hook up drag/drop mouse events - @track - .mousedown (e) => - @trackEvent(e) - - if @settings.highlight - @highlightTrack - .mousedown (e) => - @trackEvent(e) - - @dragger - .mousedown (e) => - return unless e.which == 1 - - # We've started moving - @dragging = true - @dragger.addClass "dragging" - - # Update the slider position - @domDrag(e.pageX, e.pageY) - - false - - $("body") - .mousemove (e) => - if @dragging - # Update the slider position - @domDrag(e.pageX, e.pageY) - - # Always show a pointer when dragging - $("body").css cursor: "pointer" - - - .mouseup (e) => - if @dragging - # Finished dragging - @dragging = false - @dragger.removeClass "dragging" - - # Revert the cursor - $("body").css cursor: "auto" - - # Set slider initial position - @pagePos = 0 - - # Fill in initial slider value - if @input.val() == "" - @value = @getRange().min - @input.val(@value) - else - @value = @nearestValidValue(@input.val()) - - @setSliderPositionFromValue(@value) - - # We are ready to go - ratio = @valueToRatio(@value) - @input.trigger "slider:ready", - value: @value - ratio: ratio - position: ratio * @slider.outerWidth() - el: @slider - - # Create the basis of the track-div(s) - createDivElement: (classname) -> - item = $("
") - .addClass(classname) - .css - position: "absolute" - top: "50%" - userSelect: "none" - cursor: "pointer" - .appendTo @slider - return item - - - # Set the ratio (value between 0 and 1) of the slider. - # Exposed via el.slider("setRatio", ratio) - setRatio: (ratio) -> - # Range-check the ratio - ratio = Math.min(1, ratio) - ratio = Math.max(0, ratio) - - # Work out the value - value = @ratioToValue(ratio) - - # Update the position of the slider on the screen - @setSliderPositionFromValue(value) - - # Trigger value changed events - @valueChanged(value, ratio, "setRatio") - - # Set the value of the slider - # Exposed via el.slider("setValue", value) - setValue: (value) -> - # Snap value to nearest step or allowedValue - value = @nearestValidValue(value) - - # Work out the ratio - ratio = @valueToRatio(value) - - # Update the position of the slider on the screen - @setSliderPositionFromValue(value) - - # Trigger value changed events - @valueChanged(value, ratio, "setValue") - - # Respond to an event on a track - trackEvent: (e) -> - return unless e.which == 1 - - @domDrag(e.pageX, e.pageY, true) - @dragging = true - false - - # Respond to a dom drag event - domDrag: (pageX, pageY, animate=false) -> - # Normalize position within allowed range - pagePos = pageX - @slider.offset().left - pagePos = Math.min(@slider.outerWidth(), pagePos) - pagePos = Math.max(0, pagePos) - - # If the element position has changed, do stuff - if @pagePos != pagePos - @pagePos = pagePos - - # Set the percentage value of the slider - ratio = pagePos / @slider.outerWidth() - - # Trigger value changed events - value = @ratioToValue(ratio) - @valueChanged(value, ratio, "domDrag") - - # Update the position of the slider on the screen - if @settings.snap - @setSliderPositionFromValue(value, animate) - else - @setSliderPosition(pagePos, animate) - - # Set the slider position given a slider canvas position - setSliderPosition: (position, animate=false) -> - if animate and @settings.animate - @dragger.animate left: position, 200 - @highlightTrack.animate width: position, 200 if @settings.highlight - else - @dragger.css left: position - @highlightTrack.css width: position if @settings.highlight - - # Set the slider position given a value - setSliderPositionFromValue: (value, animate=false) -> - # Get the slide ratio from the value - ratio = @valueToRatio(value) - - # Set the slider position - @setSliderPosition(ratio * @slider.outerWidth(), animate) - - # Get the valid range of values - getRange: -> - if @settings.allowedValues - min: Math.min(@settings.allowedValues...) - max: Math.max(@settings.allowedValues...) - else if @settings.range - min: parseFloat(@settings.range[0]) - max: parseFloat(@settings.range[1]) - else - min: 0 - max: 1 - - # Find the nearest valid value, checking allowedValues and step settings - nearestValidValue: (rawValue) -> - range = @getRange() - - # Range-check the value - rawValue = Math.min(range.max, rawValue) - rawValue = Math.max(range.min, rawValue) - - # Apply allowedValues or step settings - if @settings.allowedValues - closest = null - $.each @settings.allowedValues, -> - if closest == null || Math.abs(this - rawValue) < Math.abs(closest - rawValue) - closest = this - - return closest - else if @settings.step - maxSteps = (range.max - range.min) / @settings.step - steps = Math.floor((rawValue - range.min) / @settings.step) - steps += 1 if (rawValue - range.min) % @settings.step > @settings.step / 2 and steps < maxSteps - - return steps * @settings.step + range.min - else - return rawValue - - # Convert a value to a ratio - valueToRatio: (value) -> - if @settings.equalSteps - # Get slider ratio for equal-step - for allowedVal, idx in @settings.allowedValues - if !closest? || Math.abs(allowedVal - value) < Math.abs(closest - value) - closest = allowedVal - closestIdx = idx - - if @settings.snapMid - (closestIdx+0.5)/@settings.allowedValues.length - else - (closestIdx)/(@settings.allowedValues.length - 1) - - else - # Get slider ratio for continuous values - range = @getRange() - (value - range.min) / (range.max - range.min) - - # Convert a ratio to a valid value - ratioToValue: (ratio) -> - if @settings.equalSteps - steps = @settings.allowedValues.length - step = Math.round(ratio * steps - 0.5) - idx = Math.min(step, @settings.allowedValues.length - 1) - - @settings.allowedValues[idx] - else - range = @getRange() - rawValue = ratio * (range.max - range.min) + range.min - - @nearestValidValue(rawValue) - - # Trigger value changed events - valueChanged: (value, ratio, trigger) -> - return if value.toString() == @value.toString() - - # Save the new value - @value = value - - # Construct event data and fire event - eventData = - value: value - ratio: ratio - position: ratio * @slider.outerWidth() - trigger: trigger - el: @slider - - @input - .val(value) - .trigger($.Event("change", eventData)) - .trigger("slider:changed", eventData) - - - # - # Expose as jQuery Plugin - # - - $.extend $.fn, simpleSlider: (settingsOrMethod, params...) -> - publicMethods = ["setRatio", "setValue"] - - $(this).each -> - if settingsOrMethod and settingsOrMethod in publicMethods - obj = $(this).data("slider-object") - - obj[settingsOrMethod].apply(obj, params) - else - settings = settingsOrMethod - $(this).data "slider-object", new SimpleSlider($(this), settings) - - - # - # Attach unobtrusive JS hooks - # - - $ -> - $("[data-slider]").each -> - $el = $(this) - - # Build options object from data attributes - settings = {} - - allowedValues = $el.data "slider-values" - settings.allowedValues = (parseFloat(x) for x in allowedValues.split(",")) if allowedValues - settings.range = $el.data("slider-range").split(",") if $el.data("slider-range") - settings.step = $el.data("slider-step") if $el.data("slider-step") - settings.snap = $el.data("slider-snap") - settings.equalSteps = $el.data("slider-equal-steps") - settings.theme = $el.data("slider-theme") if $el.data("slider-theme") - settings.highlight = $el.data("slider-highlight") if $el.attr("data-slider-highlight") - settings.animate = $el.data("slider-animate") if $el.data("slider-animate")? - - # Activate the plugin - $el.simpleSlider settings - -) @jQuery or @Zepto, this diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.min.js b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.min.js deleted file mode 100644 index b6a7341..0000000 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.min.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * jQuery Simple Slider: Unobtrusive Numerical Slider - * Version 1.0.0 - * - * Copyright (c) 2013 James Smith (http://loopj.com) - * - * Licensed under the MIT license (http://mit-license.org/) - * - */ - -var __slice=[].slice,__indexOf=[].indexOf||function(e){for(var t=0,n=this.length;t").addClass("slider"+(this.settings.classSuffix||"")).css({position:"relative",userSelect:"none",boxSizing:"border-box"}).insertBefore(this.input),this.input.attr("id")&&this.slider.attr("id",this.input.attr("id")+"-slider"),this.track=this.createDivElement("track").css({width:"100%"}),this.settings.highlight&&(this.highlightTrack=this.createDivElement("highlight-track").css({width:"0"})),this.dragger=this.createDivElement("dragger"),this.slider.css({minHeight:this.dragger.outerHeight(),marginLeft:this.dragger.outerWidth()/2,marginRight:this.dragger.outerWidth()/2}),this.track.css({marginTop:this.track.outerHeight()/-2}),this.settings.highlight&&this.highlightTrack.css({marginTop:this.track.outerHeight()/-2}),this.dragger.css({marginTop:this.dragger.outerWidth()/-2,marginLeft:this.dragger.outerWidth()/-2}),this.track.mousedown(function(e){return i.trackEvent(e)}),this.settings.highlight&&this.highlightTrack.mousedown(function(e){return i.trackEvent(e)}),this.dragger.mousedown(function(e){if(e.which!==1)return;return i.dragging=!0,i.dragger.addClass("dragging"),i.domDrag(e.pageX,e.pageY),!1}),e("body").mousemove(function(t){if(i.dragging)return i.domDrag(t.pageX,t.pageY),e("body").css({cursor:"pointer"})}).mouseup(function(t){if(i.dragging)return i.dragging=!1,i.dragger.removeClass("dragging"),e("body").css({cursor:"auto"})}),this.pagePos=0,this.input.val()===""?(this.value=this.getRange().min,this.input.val(this.value)):this.value=this.nearestValidValue(this.input.val()),this.setSliderPositionFromValue(this.value),r=this.valueToRatio(this.value),this.input.trigger("slider:ready",{value:this.value,ratio:r,position:r*this.slider.outerWidth(),el:this.slider})}return t.prototype.createDivElement=function(t){var n;return n=e("
").addClass(t).css({position:"absolute",top:"50%",userSelect:"none",cursor:"pointer"}).appendTo(this.slider),n},t.prototype.setRatio=function(e){var t;return e=Math.min(1,e),e=Math.max(0,e),t=this.ratioToValue(e),this.setSliderPositionFromValue(t),this.valueChanged(t,e,"setRatio")},t.prototype.setValue=function(e){var t;return e=this.nearestValidValue(e),t=this.valueToRatio(e),this.setSliderPositionFromValue(e),this.valueChanged(e,t,"setValue")},t.prototype.trackEvent=function(e){if(e.which!==1)return;return this.domDrag(e.pageX,e.pageY,!0),this.dragging=!0,!1},t.prototype.domDrag=function(e,t,n){var r,i,s;n==null&&(n=!1),r=e-this.slider.offset().left,r=Math.min(this.slider.outerWidth(),r),r=Math.max(0,r);if(this.pagePos!==r)return this.pagePos=r,i=r/this.slider.outerWidth(),s=this.ratioToValue(i),this.valueChanged(s,i,"domDrag"),this.settings.snap?this.setSliderPositionFromValue(s,n):this.setSliderPosition(r,n)},t.prototype.setSliderPosition=function(e,t){t==null&&(t=!1);if(t&&this.settings.animate){this.dragger.animate({left:e},200);if(this.settings.highlight)return this.highlightTrack.animate({width:e},200)}else{this.dragger.css({left:e});if(this.settings.highlight)return this.highlightTrack.css({width:e})}},t.prototype.setSliderPositionFromValue=function(e,t){var n;return t==null&&(t=!1),n=this.valueToRatio(e),this.setSliderPosition(n*this.slider.outerWidth(),t)},t.prototype.getRange=function(){return this.settings.allowedValues?{min:Math.min.apply(Math,this.settings.allowedValues),max:Math.max.apply(Math,this.settings.allowedValues)}:this.settings.range?{min:parseFloat(this.settings.range[0]),max:parseFloat(this.settings.range[1])}:{min:0,max:1}},t.prototype.nearestValidValue=function(t){var n,r,i,s;return i=this.getRange(),t=Math.min(i.max,t),t=Math.max(i.min,t),this.settings.allowedValues?(n=null,e.each(this.settings.allowedValues,function(){if(n===null||Math.abs(this-t)this.settings.step/2&&s=0?(s=e(this).data("slider-object"),s[i].apply(s,t)):(o=i,e(this).data("slider-object",new n(e(this),o)))})}}),e(function(){return e("[data-slider]").each(function(){var t,n,r,i;return t=e(this),r={},n=t.data("slider-values"),n&&(r.allowedValues=function(){var e,t,r,s;r=n.split(","),s=[];for(e=0,t=r.length;e .dragger, .slider > .dragger:hover { + border-radius: 0px; + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + width: 8px; + height: 24px; + margin-top: -12px!important; + text-shadow: 0 1px 0 #fff; + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); + background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); + background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); + background-repeat: repeat-x; + border-color: #2d6ca2; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); +} +.slider > .dragger:hover { + background-color: #3071a9; + background-image: none; + border-color: #2d6ca2; +} + +.slider > .highlight-track { + height: 20px; + top: 50%; +} +.slider + .output { + +} + +.slider > .track, .slider > .highlight-track { +border-radius: 5px; +} + +/* review this later */ +.slider { + width: 100%; +} diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.js b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.js similarity index 80% rename from circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.js rename to circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.js index 1b917f6..5426cbd 100644 --- a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider-fa64f59/js/simple-slider.js +++ b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.js @@ -1,7 +1,10 @@ /* jQuery Simple Slider - Copyright (c) 2012 James Smith (http://loopj.com) + Copyright (c) 2012, 2013 James Smith (http://loopj.com) + Copyright (c) 2013 Maarten van Grootel (http://maatenvangrootel.nl) + Copyright (c) 2013 Nathan Hunzaker (http://natehunzaker.com) + Copyright (c) 2013 Erik J. Nedwidek (http://github.com/nedwidek) Licensed under the MIT license (http://mit-license.org/) */ @@ -23,8 +26,12 @@ var __slice = [].slice, classPrefix: null, classSuffix: null, theme: null, - highlight: false + highlight: false, + showScale: false }; + if(typeof options == 'undefined') { + options = this.loadDataOptions(); + } this.settings = $.extend({}, this.defaultOptions, options); if (this.settings.theme) { this.settings.classSuffix = "-" + this.settings.theme; @@ -106,6 +113,20 @@ var __slice = [].slice, } this.setSliderPositionFromValue(this.value); ratio = this.valueToRatio(this.value); + if (this.settings.showScale) { + this.scale = this.createDivElement("scale"); + this.minScale = this.createSpanElement("min-scale", this.scale); + this.maxScale = this.createSpanElement("max-scale", this.scale); + + range = this.getRange(); + + this.minScale.html(range.min); + this.maxScale.html(range.max); + + this.scale.css('marginTop', function(index, currentValue) { + return (parseInt(currentValue, 10) + this.previousSibling.offsetHeight / 2) + 'px'; + }); + } this.input.trigger("slider:ready", { value: this.value, ratio: ratio, @@ -114,6 +135,44 @@ var __slice = [].slice, }); } + SimpleSlider.prototype.loadDataOptions = function() { + var options = {}; + allowedValues = this.input.data("slider-values"); + if (allowedValues) { + options.allowedValues = (function() { + var _i, _len, _ref, _results; + _ref = allowedValues.split(","); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + x = _ref[_i]; + _results.push(parseFloat(x)); + } + return _results; + })(); + } + if (this.input.data("slider-range")) { + options.range = this.input.data("slider-range").split(","); + } + if (this.input.data("slider-step")) { + options.step = this.input.data("slider-step"); + } + options.snap = this.input.data("slider-snap"); + options.equalSteps = this.input.data("slider-equal-steps"); + if (this.input.data("slider-theme")) { + options.theme = this.input.data("slider-theme"); + } + if (this.input.attr("data-slider-highlight")) { + options.highlight = this.input.data("slider-highlight"); + } + if (this.input.data("slider-animate") != null) { + options.animate = this.input.data("slider-animate"); + } + if (this.input.data("slider-showscale") != null) { + options.showScale = this.input.data("slider-showscale"); + } + return options; + } + SimpleSlider.prototype.createDivElement = function(classname) { var item; item = $("
").addClass(classname).css({ @@ -125,6 +184,12 @@ var __slice = [].slice, return item; }; + SimpleSlider.prototype.createSpanElement = function(classname, parent) { + var item; + item = $("").addClass(classname).appendTo(parent); + return item; + }; + SimpleSlider.prototype.setRatio = function(ratio) { var value; ratio = Math.min(1, ratio); @@ -322,42 +387,14 @@ var __slice = [].slice, }); } }); + + /* return $(function() { return $("[data-slider]").each(function() { var $el, allowedValues, settings, x; $el = $(this); - settings = {}; - allowedValues = $el.data("slider-values"); - if (allowedValues) { - settings.allowedValues = (function() { - var _i, _len, _ref, _results; - _ref = allowedValues.split(","); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - x = _ref[_i]; - _results.push(parseFloat(x)); - } - return _results; - })(); - } - if ($el.data("slider-range")) { - settings.range = $el.data("slider-range").split(","); - } - if ($el.data("slider-step")) { - settings.step = $el.data("slider-step"); - } - settings.snap = $el.data("slider-snap"); - settings.equalSteps = $el.data("slider-equal-steps"); - if ($el.data("slider-theme")) { - settings.theme = $el.data("slider-theme"); - } - if ($el.attr("data-slider-highlight")) { - settings.highlight = $el.data("slider-highlight"); - } - if ($el.data("slider-animate") != null) { - settings.animate = $el.data("slider-animate"); - } - return $el.simpleSlider(settings); + return $el.simpleSlider(); }); }); + */ })(this.jQuery || this.Zepto, this); diff --git a/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.min.js b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.min.js new file mode 100644 index 0000000..fbdb6e3 --- /dev/null +++ b/circle/dashboard/static/dashboard/loopj-jquery-simple-slider/js/simple-slider.min.js @@ -0,0 +1,11 @@ +/* + * jQuery Simple Slider: Unobtrusive Numerical Slider + * Version 1.0.0 + * + * Copyright (c) 2013 James Smith (http://loopj.com) + * + * Licensed under the MIT license (http://mit-license.org/) + * + */ + +var __slice=[].slice,__indexOf=[].indexOf||function(c){for(var b=0,a=this.length;b").addClass("slider"+(this.settings.classSuffix||"")).css({position:"relative",userSelect:"none",boxSizing:"border-box"}).insertBefore(this.input);if(this.input.attr("id")){this.slider.attr("id",this.input.attr("id")+"-slider")}this.track=this.createDivElement("track").css({width:"100%"});if(this.settings.highlight){this.highlightTrack=this.createDivElement("highlight-track").css({width:"0"})}this.dragger=this.createDivElement("dragger");this.slider.css({minHeight:this.dragger.outerHeight(),marginLeft:this.dragger.outerWidth()/2,marginRight:this.dragger.outerWidth()/2});this.track.css({marginTop:this.track.outerHeight()/-2});if(this.settings.highlight){this.highlightTrack.css({marginTop:this.track.outerHeight()/-2})}this.dragger.css({marginTop:this.dragger.outerWidth()/-2,marginLeft:this.dragger.outerWidth()/-2});this.track.mousedown(function(i){return h.trackEvent(i)});if(this.settings.highlight){this.highlightTrack.mousedown(function(i){return h.trackEvent(i)})}this.dragger.mousedown(function(i){if(i.which!==1){return}h.dragging=true;h.dragger.addClass("dragging");h.domDrag(i.pageX,i.pageY);return false});c("body").mousemove(function(i){if(h.dragging){h.domDrag(i.pageX,i.pageY);return c("body").css({cursor:"pointer"})}}).mouseup(function(i){if(h.dragging){h.dragging=false;h.dragger.removeClass("dragging");return c("body").css({cursor:"auto"})}});this.pagePos=0;if(this.input.val()===""){this.value=this.getRange().min;this.input.val(this.value)}else{this.value=this.nearestValidValue(this.input.val())}this.setSliderPositionFromValue(this.value);g=this.valueToRatio(this.value);if(this.settings.showScale){this.scale=this.createDivElement("scale");this.minScale=this.createSpanElement("min-scale",this.scale);this.maxScale=this.createSpanElement("max-scale",this.scale);range=this.getRange();this.minScale.html(range.min);this.maxScale.html(range.max);this.scale.css("marginTop",function(i,j){return(parseInt(j,10)+this.previousSibling.offsetHeight/2)+"px"})}this.input.trigger("slider:ready",{value:this.value,ratio:g,position:g*this.slider.outerWidth(),el:this.slider})}d.prototype.loadDataOptions=function(){var e={};allowedValues=this.input.data("slider-values");if(allowedValues){e.allowedValues=(function(){var i,g,h,f;h=allowedValues.split(",");f=[];for(i=0,g=h.length;i").addClass(f).css({position:"absolute",top:"50%",userSelect:"none",cursor:"pointer"}).appendTo(this.slider);return e};d.prototype.createSpanElement=function(g,e){var f;f=c("").addClass(g).appendTo(e);return f};d.prototype.setRatio=function(e){var f;e=Math.min(1,e);e=Math.max(0,e);f=this.ratioToValue(e);this.setSliderPositionFromValue(f);return this.valueChanged(f,e,"setRatio")};d.prototype.setValue=function(f){var e;f=this.nearestValidValue(f);e=this.valueToRatio(f);this.setSliderPositionFromValue(f);return this.valueChanged(f,e,"setValue")};d.prototype.trackEvent=function(f){if(f.which!==1){return}this.domDrag(f.pageX,f.pageY,true);this.dragging=true;return false};d.prototype.domDrag=function(i,g,e){var f,h,j;if(e==null){e=false}f=i-this.slider.offset().left;f=Math.min(this.slider.outerWidth(),f);f=Math.max(0,f);if(this.pagePos!==f){this.pagePos=f;h=f/this.slider.outerWidth();j=this.ratioToValue(h);this.valueChanged(j,h,"domDrag");if(this.settings.snap){return this.setSliderPositionFromValue(j,e)}else{return this.setSliderPosition(f,e)}}};d.prototype.setSliderPosition=function(e,f){if(f==null){f=false}if(f&&this.settings.animate){this.dragger.animate({left:e},200);if(this.settings.highlight){return this.highlightTrack.animate({width:e},200)}}else{this.dragger.css({left:e});if(this.settings.highlight){return this.highlightTrack.css({width:e})}}};d.prototype.setSliderPositionFromValue=function(g,e){var f;if(e==null){e=false}f=this.valueToRatio(g);return this.setSliderPosition(f*this.slider.outerWidth(),e)};d.prototype.getRange=function(){if(this.settings.allowedValues){return{min:Math.min.apply(Math,this.settings.allowedValues),max:Math.max.apply(Math,this.settings.allowedValues)}}else{if(this.settings.range){return{min:parseFloat(this.settings.range[0]),max:parseFloat(this.settings.range[1])}}else{return{min:0,max:1}}}};d.prototype.nearestValidValue=function(i){var h,g,f,e;f=this.getRange();i=Math.min(f.max,i);i=Math.max(f.min,i);if(this.settings.allowedValues){h=null;c.each(this.settings.allowedValues,function(){if(h===null||Math.abs(this-i)this.settings.step/2&&e=0){h=c(this).data("slider-object");return h[f].apply(h,e)}else{g=f;return c(this).data("slider-object",new b(c(this),g))}})}});return c(function(){return c("[data-slider]").each(function(){var e,g,f,d;e=c(this);return e.simpleSlider()})})})(this.jQuery||this.Zepto,this); diff --git a/circle/dashboard/static/dashboard/store.js b/circle/dashboard/static/dashboard/store.js new file mode 100644 index 0000000..602f601 --- /dev/null +++ b/circle/dashboard/static/dashboard/store.js @@ -0,0 +1,60 @@ +$(function() { + $("#store-list-container").on("click", ".store-list-item", function() { + if($(this).data("item-type") == "D") { + $("#store-list-up-icon").removeClass("fa-reply").addClass("fa-refresh fa-spin"); + var url = $(this).prop("href"); + $.get(url, function(result) { + $("#store-list-container").html(result); + noJS(); + $("[title]").tooltip(); + history.pushState({}, "", url); + }); + } else { + $(this).next(".store-list-file-infos").stop().slideToggle(); + } + return false; + }); + + /* how upload works + * - user clicks on a "fake" browse button, this triggers a click event on the file upload + * - if the file input changes it adds the name of the file to form (or number of files if multiple is enabled) + * - and finally when we click on the upload button (this event handler) it firsts ask the store api where to upload + * then changes the form's action attr before sending the form itself + */ + $("#store-list-container").on("click", '#store-upload-form button[type="submit"]', function() { + var current_dir = $("#store-upload-form").find('[name="current_dir"]').val(); + $.get($("#store-upload-form").data("action") + "?current_dir=" + current_dir, function(result) { + $('#store-upload-form button[type="submit"] i').addClass("fa-spinner fa-spin"); + $("#store-upload-form").get(0).setAttribute("action", result['url']); + $("#store-upload-form").submit(); + }); + + return false; + }); + + /* "fake" browse button */ + $("#store-list-container").on("click", "#store-upload-browse", function() { + $('#store-upload-form input[type="file"]').click(); + }); + + $("#store-list-container").on("change", "#store-upload-file", function() { + var input = $(this); + var numFiles = input.get(0).files ? input.get(0).files.length : 1; + var label = input.val().replace(/\\/g, '/').replace(/.*\//, ''); + input.trigger('fileselect', [numFiles, label]); + }); + + $("#store-list-container").on("fileselect", "#store-upload-file", function(event, numFiles, label) { + var input = $("#store-upload-filename"); + var log = numFiles > 1 ? numFiles + ' files selected' : label; + if(input.length) { + input.val(log); + } + if(log) { + $('#store-upload-form button[type="submit"]').prop("disabled", false); + } else { + $('#store-upload-form button[type="submit"]').prop("disabled", true); + } + + }); +}); diff --git a/circle/dashboard/static/dashboard/vm-common.js b/circle/dashboard/static/dashboard/vm-common.js index 548777a..bc3f0ef 100644 --- a/circle/dashboard/static/dashboard/vm-common.js +++ b/circle/dashboard/static/dashboard/vm-common.js @@ -3,7 +3,7 @@ $(function() { /* vm operations */ - $('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface').on('click', '.operation.btn', function(e) { + $('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface').on('click', '.operation', function(e) { var icon = $(this).children("i").addClass('fa-spinner fa-spin'); $.ajax({ diff --git a/circle/dashboard/static/dashboard/vm-create.js b/circle/dashboard/static/dashboard/vm-create.js index b0926f1..8806993 100644 --- a/circle/dashboard/static/dashboard/vm-create.js +++ b/circle/dashboard/static/dashboard/vm-create.js @@ -2,13 +2,17 @@ var vlans = []; var disks = []; $(function() { - vmCustomizeLoaded(); + if($(".vm-create-template-list").length) { + vmCreateLoaded(); + } else { + vmCustomizeLoaded(); + } }); function vmCreateLoaded() { $(".vm-create-template-details").hide(); - $(".vm-create-template-summary").click(function() { + $(".vm-create-template-summary").unbind("click").click(function() { $(this).next(".vm-create-template-details").slideToggle(); }); @@ -64,9 +68,18 @@ function vmCreateLoaded() { return false; }); + $('.progress-bar').each(function() { + var min = $(this).attr('aria-valuemin'); + var max = $(this).attr('aria-valuemax'); + var now = $(this).attr('aria-valuenow'); + var siz = (now-min)*100/(max-min); + $(this).css('width', siz+'%'); + }); + } function vmCustomizeLoaded() { + $("[title]").tooltip(); /* network thingies */ /* add network */ @@ -92,7 +105,7 @@ function vmCustomizeLoaded() { /* add dummy text if no more networks are available */ if($('#vm-create-network-add-select option').length < 1) { $('#vm-create-network-add-button').attr('disabled', true); - $('#vm-create-network-add-select').html(''); + $('#vm-create-network-add-select').html(''); } return false; @@ -124,7 +137,7 @@ function vmCustomizeLoaded() { /* remove the selection from the multiple select */ $('#vm-create-network-add-vlan option[value="' + vlan_pk + '"]').prop('selected', false); if ($('#vm-create-network-list').children('span').length < 1) { - $('#vm-create-network-list').append('Not added to any network!'); + $('#vm-create-network-list').append(gettext("Not added to any network")); } }); return false; @@ -155,7 +168,7 @@ function vmCustomizeLoaded() { // if all networks are added add a dummy and disable the add button if($("#vm-create-network-add-select option").length < 1) { - $("#vm-create-network-add-select").html(''); + $("#vm-create-network-add-select").html(''); $('#vm-create-network-add-button').attr('disabled', true); } @@ -170,54 +183,6 @@ function vmCustomizeLoaded() { /* ----- end of networks thingies ----- */ - - /* add disk */ - $('#vm-create-disk-add-button').click(function() { - var disk_pk = $('#vm-create-disk-add-select :selected').val(); - var name = $('#vm-create-disk-add-select :selected').text(); - - if ($('#vm-create-disk-list').children('span').length < 1) { - $('#vm-create-disk-list').html(''); - } - $('#vm-create-disk-list').append( - vmCreateDiskLabel(disk_pk, name) - ); - - /* select the disk from the multiple select */ - $('#vm-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', true); - - $('option:selected', $('#vm-create-disk-add-select')).remove(); - - /* add dummy text if no more disks are available */ - if($('#vm-create-disk-add-select option').length < 1) { - $('#vm-create-disk-add-button').attr('disabled', true); - $('#vm-create-disk-add-select').html(''); - } - - return false; - }); - - - /* remove disk */ - // event for disk remove button (icon, X) - $('body').on('click', '.vm-create-remove-disk', function() { - var disk_pk = ($(this).parent('span').prop('id')).replace('disk-', '') - - $(this).parent('span').fadeOut(500, function() { - /* remove the disk label */ - $(this).remove(); - - var disk_name = $(this).text(); - - /* remove the selection from the multiple select */ - $('#vm-create-disk-add-form option[value="' + disk_pk + '"]').prop('selected', false); - if ($('#vm-create-disk-list').children('span').length < 1) { - $('#vm-create-disk-list').append('No disks are added!'); - } - }); - return false; - }); - /* copy disks from hidden select */ $('#vm-create-disk-add-form option').each(function() { var text = $(this).text(); @@ -244,6 +209,14 @@ function vmCustomizeLoaded() { /* start vm button clicks */ $('#vm-create-customized-start').click(function() { + var error = false; + $(".cpu-count-input, .ram-input, #id_name, #id_amount ").each(function() { + if(!$(this)[0].checkValidity()) { + error = true; + } + }); + if(error) return true; + $.ajax({ url: '/dashboard/vm/create/', headers: {"X-CSRFToken": getCookie('csrftoken')}, @@ -284,6 +257,7 @@ function vmCustomizeLoaded() { /* for no js stuff */ $('.no-js-hidden').show(); $('.js-hidden').hide(); + } @@ -294,5 +268,5 @@ function vmCreateNetworkLabel(pk, name, managed) { function vmCreateDiskLabel(pk, name) { var style = "float: left; margin: 5px 5px 5px 0;"; - return ' ' + name + ' '; + return ' ' + name + ' '; } diff --git a/circle/dashboard/static/dashboard/vm-details.js b/circle/dashboard/static/dashboard/vm-details.js index 2a32a2c..3129f9d 100644 --- a/circle/dashboard/static/dashboard/vm-details.js +++ b/circle/dashboard/static/dashboard/vm-details.js @@ -1,5 +1,6 @@ var show_all = false; var in_progress = false; +var activity_hash = 5; $(function() { /* do we need to check for new activities */ @@ -27,6 +28,15 @@ $(function() { /* save resources */ $('#vm-details-resources-save').click(function() { + var error = false; + $(".cpu-count-input, .ram-input").each(function() { + if(!$(this)[0].checkValidity()) { + error = true; + } + }); + if(error) return true; + + $('i.fa-floppy-o', this).removeClass("fa-floppy-o").addClass("fa-refresh fa-spin"); var vm = $(this).data("vm"); $.ajax({ @@ -34,8 +44,12 @@ $(function() { url: "/dashboard/vm/" + vm + "/op/resources_change/", data: $('#vm-details-resources-form').serialize(), success: function(data, textStatus, xhr) { + if(data.success) { + $('a[href="#activity"]').trigger("click"); + } else { + addMessage(data.messages.join("
"), "danger"); + } $("#vm-details-resources-save i").removeClass('fa-refresh fa-spin').addClass("fa-floppy-o"); - $('a[href="#activity"]').trigger("click"); }, error: function(xhr, textStatus, error) { $("#vm-details-resources-save i").removeClass('fa-refresh fa-spin').addClass("fa-floppy-o"); @@ -307,6 +321,10 @@ $(function() { $("#vm-details-connection-string").focus(); }); + $("a.operation-password_reset").click(function() { + if(Boolean($(this).data("disabled"))) return false; + }); + }); @@ -327,6 +345,7 @@ function removePort(data) { } }); + } function decideActivityRefresh() { @@ -341,17 +360,6 @@ function decideActivityRefresh() { return check; } -/* unescapes html got via the request, also removes whitespaces and replaces all ' with " */ -function unescapeHTML(html) { - return html.replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&').replace(/–/g, "–").replace(/\//g, "").replace(/'/g, '"').replace(/'/g, "'").replace(/ /g, ''); -} - -/* the html page contains some tags that were modified via js (titles for example), we delete these - also some html tags are closed with / */ -function changeHTML(html) { - return html.replace(/data-original-title/g, "title").replace(/title=""/g, "").replace(/\//g, '').replace(/ /g, ''); -} - function checkNewActivity(runs) { var instance = location.href.split('/'); instance = instance[instance.length - 2]; @@ -360,19 +368,24 @@ function checkNewActivity(runs) { url: '/dashboard/vm/' + instance + '/activity/', data: {'show_all': show_all}, success: function(data) { - if(show_all) { /* replace on longer string freezes the spinning stuff */ + var new_activity_hash = (data['activities'] + "").hashCode(); + if(new_activity_hash != activity_hash) { $("#activity-refresh").html(data['activities']); - } else { - a = unescapeHTML(data['activities']); - b = changeHTML($("#activity-refresh").html()); - if(a != b) - $("#activity-refresh").html(data['activities']); } + activity_hash = new_activity_hash; + $("#ops").html(data['ops']); $("#disk-ops").html(data['disk_ops']); $("[title]").tooltip(); - $("#vm-details-state i").prop("class", "fa " + data['icon']); + /* changing the status text */ + var icon = $("#vm-details-state i"); + if(data['is_new_state']) { + if(!icon.hasClass("fa-spin")) + icon.prop("class", "fa fa-spinner fa-spin"); + } else { + icon.prop("class", "fa " + data['icon']); + } $("#vm-details-state span").html(data['human_readable_status'].toUpperCase()); if(data['status'] == "RUNNING") { $("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled"); @@ -380,12 +393,12 @@ function checkNewActivity(runs) { $("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled"); } - if(data['status'] == "STOPPED") { - $(".enabled-when-stopped").prop("disabled", false); - $(".hide-when-stopped").hide(); + if(data['status'] == "STOPPED" || data['status'] == "PENDING") { + $(".change-resources-button").prop("disabled", false); + $(".change-resources-help").hide(); } else { - $(".enabled-when-stopped").prop("disabled", true); - $(".hide-when-stopped").show(); + $(".change-resources-button").prop("disabled", true); + $(".change-resources-help").show(); } if(runs > 0 && decideActivityRefresh()) { @@ -403,3 +416,14 @@ function checkNewActivity(runs) { } }); } + +String.prototype.hashCode = function() { + var hash = 0, i, chr, len; + if (this.length == 0) return hash; + for (i = 0, len = this.length; i < len; i++) { + chr = this.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; +}; diff --git a/circle/dashboard/static/grafikon.png b/circle/dashboard/static/grafikon.png deleted file mode 100644 index 744a092..0000000 Binary files a/circle/dashboard/static/grafikon.png and /dev/null differ diff --git a/circle/dashboard/store_api.py b/circle/dashboard/store_api.py new file mode 100644 index 0000000..07f9441 --- /dev/null +++ b/circle/dashboard/store_api.py @@ -0,0 +1,195 @@ +from os.path import splitext +import json +import logging +from urlparse import urljoin +from datetime import datetime + +from django.http import Http404 +from django.conf import settings +from requests import get, post, codes +from sizefield.utils import filesizeformat + +logger = logging.getLogger(__name__) + + +class StoreApiException(Exception): + pass + + +class NotOkException(StoreApiException): + def __init__(self, status, *args, **kwargs): + self.status = status + super(NotOkException, self).__init__(*args, **kwargs) + + +class NoStoreException(StoreApiException): + pass + + +class Store(object): + + def __init__(self, user, default_timeout=0.5): + self.request_args = {'verify': settings.STORE_VERIFY_SSL} + if settings.STORE_SSL_AUTH: + self.request_args['cert'] = (settings.STORE_CLIENT_CERT, + settings.STORE_CLIENT_KEY) + if settings.STORE_BASIC_AUTH: + self.request_args['auth'] = (settings.STORE_CLIENT_USER, + settings.STORE_CLIENT_PASSWORD) + self.username = "u-%d" % user.pk + self.default_timeout = default_timeout + self.store_url = settings.STORE_URL + if not self.store_url: + raise NoStoreException + + def _request(self, url, method=get, timeout=None, + raise_status_code=True, **kwargs): + url = urljoin(self.store_url, url) + if timeout is None: + timeout = self.default_timeout + payload = json.dumps(kwargs) if kwargs else None + try: + headers = {'content-type': 'application/json'} + response = method(url, data=payload, headers=headers, + timeout=timeout, **self.request_args) + except Exception: + logger.exception("Error in store %s loading %s", + unicode(method), url) + raise + else: + if raise_status_code and response.status_code != codes.ok: + if response.status_code == 404: + raise Http404() + else: + raise NotOkException(response.status_code) + return response + + def _request_cmd(self, cmd, **kwargs): + return self._request(self.username, post, CMD=cmd, **kwargs) + + def list(self, path, process=True): + r = self._request_cmd("LIST", PATH=path) + result = r.json() + if process: + return self._process_list(result) + else: + return result + + def toplist(self, process=True): + r = self._request_cmd("TOPLIST") + result = r.json() + if process: + return self._process_list(result) + else: + return result + + def request_download(self, path): + r = self._request_cmd("DOWNLOAD", PATH=path, timeout=10) + return r.json()['LINK'] + + def request_upload(self, path): + r = self._request_cmd("UPLOAD", PATH=path) + return r.json()['LINK'] + + def remove(self, path): + self._request_cmd("REMOVE", PATH=path) + + def new_folder(self, path): + self._request_cmd("NEW_FOLDER", PATH=path) + + def rename(self, old_path, new_name): + self._request_cmd("RENAME", PATH=old_path, NEW_NAME=new_name) + + def get_quota(self): # no CMD? :o + r = self._request(self.username) + quota = r.json() + quota.update({ + 'readable_used': filesizeformat(float(quota['used'])), + 'readable_soft': filesizeformat(float(quota['soft'])), + 'readable_hard': filesizeformat(float(quota['hard'])), + }) + return quota + + def set_quota(self, quota): + self._request("/quota/" + self.username, post, QUOTA=quota) + + def user_exist(self): + try: + self._request(self.username) + return True + except NotOkException: + return False + + def create_user(self, password, keys, quota): + self._request("/new/" + self.username, method=post, + SMBPASSWD=password, KEYS=keys, QUOTA=quota) + + @staticmethod + def _process_list(content): + for d in content: + d['human_readable_date'] = datetime.utcfromtimestamp(float( + d['MTIME'])) + delta = (datetime.utcnow() - + d['human_readable_date']).total_seconds() + d['is_new'] = 0 < delta < 5 + d['human_readable_size'] = ( + "directory" if d['TYPE'] == "D" else + filesizeformat(float(d['SIZE']))) + + if d['DIR'] == ".": + d['directory'] = "/" + else: + d['directory'] = "/" + d['DIR'] + "/" + + d['path'] = d['directory'] + d['path'] += d['NAME'] + if d['TYPE'] == "D": + d['path'] += "/" + + d['ext'] = splitext(d['path'])[1] + d['icon'] = ("folder-open" if not d['TYPE'] == "F" + else file_icons.get(d['ext'], "file-o")) + + return sorted(content, key=lambda k: k['TYPE']) + + +file_icons = { + '.txt': "file-text-o", + '.pdf': "file-pdf-o", + + '.jpg': "file-image-o", + '.jpeg': "file-image-o", + '.png': "file-image-o", + '.gif': "file-image-o", + + '.avi': "file-video-o", + '.mkv': "file-video-o", + '.mp4': "file-video-o", + '.mov': "file-video-o", + + '.mp3': "file-sound-o", + '.flac': "file-sound-o", + '.wma': "file-sound-o", + + '.pptx': "file-powerpoint-o", + '.ppt': "file-powerpoint-o", + '.doc': "file-word-o", + '.docx': "file-word-o", + '.xlsx': "file-excel-o", + '.xls': "file-excel-o", + + '.rar': "file-archive-o", + '.zip': "file-archive-o", + '.7z': "file-archive-o", + '.tar': "file-archive-o", + '.gz': "file-archive-o", + + '.py': "file-code-o", + '.html': "file-code-o", + '.js': "file-code-o", + '.css': "file-code-o", + '.c': "file-code-o", + '.cpp': "file-code-o", + '.h': "file-code-o", + '.sh': "file-code-o", +} diff --git a/circle/dashboard/tables.py b/circle/dashboard/tables.py index 66f16de..e8188c9 100644 --- a/circle/dashboard/tables.py +++ b/circle/dashboard/tables.py @@ -27,43 +27,6 @@ from django.utils.translation import ugettext_lazy as _ from django_sshkey.models import UserKey -class VmListTable(Table): - pk = TemplateColumn( - template_name='dashboard/vm-list/column-id.html', - verbose_name="ID", - attrs={'th': {'class': 'vm-list-table-thin'}}, - ) - - name = TemplateColumn( - template_name="dashboard/vm-list/column-name.html" - ) - - admin = TemplateColumn( - template_name='dashboard/vm-list/column-admin.html', - attrs={'th': {'class': 'vm-list-table-admin'}}, - ) - details = TemplateColumn( - template_name='dashboard/vm-list/column-details.html', - attrs={'th': {'class': 'vm-list-table-thin'}}, - ) - actions = TemplateColumn( - template_name='dashboard/vm-list/column-actions.html', - attrs={'th': {'class': 'vm-list-table-thin'}}, - ) - time_of_suspend = TemplateColumn( - '{{ record.time_of_suspend|timeuntil }}', - verbose_name=_("Suspend in")) - time_of_delete = TemplateColumn( - '{{ record.time_of_delete|timeuntil }}', - verbose_name=_("Delete in")) - - class Meta: - model = Instance - attrs = {'class': ('table table-bordered table-striped table-hover ' - 'vm-list-table')} - fields = ('pk', 'name', 'state', 'time_of_suspend', 'time_of_delete', ) - - class NodeListTable(Table): pk = Column( diff --git a/circle/dashboard/templates/base.html b/circle/dashboard/templates/base.html index 4498b18..aefa330 100644 --- a/circle/dashboard/templates/base.html +++ b/circle/dashboard/templates/base.html @@ -68,6 +68,7 @@ + {% include 'autocomplete_light/static.html' %} diff --git a/circle/dashboard/templates/dashboard/_resources-sliders.html b/circle/dashboard/templates/dashboard/_resources-sliders.html new file mode 100644 index 0000000..27dd1a5 --- /dev/null +++ b/circle/dashboard/templates/dashboard/_resources-sliders.html @@ -0,0 +1,65 @@ +{% load i18n %} +{% load sizefieldtags %} +{% load crispy_forms_tags %} + +
+
+ {% trans "CPU priority" %} +
+
+ +
+
+
+ {{ field_priority }} + + + +
+
+
+
+
+ {% trans "CPU count" %} +
+
+ +
+
+
+ {{ field_num_cores }} + + + +
+
+
+ +
+
+ {% trans "RAM amount" %} +
+
+ +
+
+
+ {{ field_ram_size }} + + MiB + + + + +
+
+
diff --git a/circle/dashboard/templates/dashboard/_template-create.html b/circle/dashboard/templates/dashboard/_template-create.html index 2737e3f..7b65f67 100644 --- a/circle/dashboard/templates/dashboard/_template-create.html +++ b/circle/dashboard/templates/dashboard/_template-create.html @@ -1,17 +1,49 @@ {% load i18n %} {% load crispy_forms_tags %} -{% if leases < 1 %} + +
+{% with form=form %} + {% include "display-form-errors.html" %} +{% endwith %} + +{% csrf_token %} + +{{ form.name|as_crispy_field }} + +
+ {% trans "Resource configuration" %} + {% include "dashboard/_resources-sliders.html" with field_priority=form.priority field_num_cores=form.num_cores field_ram_size=form.ram_size %} + {{ form.max_ram_size|as_crispy_field }} +
+ +
+ {% trans "Virtual machine settings" %} +{{ form.arch|as_crispy_field }} +{{ form.access_method|as_crispy_field }} +{{ form.boot_menu|as_crispy_field }} +{{ form.raw_data|as_crispy_field }} +{{ form.req_traits|as_crispy_field }} +{{ form.description|as_crispy_field }} +{{ form.system|as_crispy_field }} +
+
+ {% trans "External resources" %} +{{ form.networks|as_crispy_field }} +{{ form.lease|as_crispy_field }} + +{% if show_lease_create %}
- {% trans "You haven't created any leases yet, but you need one to create a template!" %} + {% trans "You haven't created any leases yet, but you need one to create a template." %} {% trans "Create a new lease now." %}
{% endif %} +{{ form.tags|as_crispy_field }} +
+ + +
-{% with form=form %} - {% include "display-form-errors.html" %} -{% endwith %} -{% crispy form %} - diff --git a/circle/dashboard/templates/dashboard/_vm-create-1.html b/circle/dashboard/templates/dashboard/_vm-create-1.html index 4b19b71..9d275a2 100644 --- a/circle/dashboard/templates/dashboard/_vm-create-1.html +++ b/circle/dashboard/templates/dashboard/_vm-create-1.html @@ -58,7 +58,7 @@
- {% if perms.vm_set_resources %} + {% if perms.vm.set_resources %} {% trans "Customize" %} {% endif %}
@@ -76,48 +76,6 @@
- -{% block "extra-js" %} - -{% endblock %} diff --git a/circle/dashboard/templates/dashboard/_vm-create-2.html b/circle/dashboard/templates/dashboard/_vm-create-2.html index c793d52..3d3a62c 100644 --- a/circle/dashboard/templates/dashboard/_vm-create-2.html +++ b/circle/dashboard/templates/dashboard/_vm-create-2.html @@ -1,6 +1,94 @@ {% load crispy_forms_tags %} +{% load i18n %} {% load sizefieldtags %} -{% crispy vm_create_form %} +{% include "display-form-errors.html" with form=vm_create_form %} + +{% csrf_token %} - +{{ vm_create_form.template }} +{{ vm_create_form.customized }} + +
+
+
+ + + {{ vm_create_form.name }} +
+
+
+ +
+
+
+ + {{ vm_create_form.amount }} +
+
+
+ +
+
+

{% trans "Resources" %}

+
+
+ + +
+{% include "dashboard/_resources-sliders.html" with field_priority=vm_create_form.cpu_priority field_num_cores=vm_create_form.cpu_count field_ram_size=vm_create_form.ram_size %} +
+ +
+
+

{% trans "Disks" %}

+
+
+
+ {{ vm_create_form.disks }} +
+
+

{% trans "No disks are added." %}

+
+
+
+
+ + +
+
+

{% trans "Network" %}

+
+
+
+ {{ vm_create_form.networks }} +
+
+

+ {% trans "Not added to any network." %} +

+

+
+ +
+ + + +
+
+

+
+
+
+ + + diff --git a/circle/dashboard/templates/dashboard/base.html b/circle/dashboard/templates/dashboard/base.html index 3deaaff..a4b32e6 100644 --- a/circle/dashboard/templates/dashboard/base.html +++ b/circle/dashboard/templates/dashboard/base.html @@ -5,7 +5,7 @@ {% block extra_link %} - + {% endblock %} @@ -18,23 +18,22 @@ {% endblock %} {% block navbar %} +{% if user.is_authenticated and user.pk and not request.token_user %} + - - -{% if user.is_authenticated and user.pk %} {% trans "Log out" %} @@ -48,7 +47,7 @@ {% trans "Admin" %} {% endif %} {% else %} - {% trans "Log in " %} + {% trans "Log in " %} {% endif %} {% endblock %} @@ -56,6 +55,5 @@ {% block extra_script %} - {% endblock %} diff --git a/circle/dashboard/templates/dashboard/group-create.html b/circle/dashboard/templates/dashboard/group-create.html index 29a270c..35e47a9 100644 --- a/circle/dashboard/templates/dashboard/group-create.html +++ b/circle/dashboard/templates/dashboard/group-create.html @@ -1,9 +1,4 @@ {% load crispy_forms_tags %} -
{% csrf_token %} diff --git a/circle/dashboard/templates/dashboard/group-detail.html b/circle/dashboard/templates/dashboard/group-detail.html index e7738f8..794691c 100644 --- a/circle/dashboard/templates/dashboard/group-detail.html +++ b/circle/dashboard/templates/dashboard/group-detail.html @@ -7,7 +7,7 @@ {% endif %} - {% comment %} + {% if not no_store %}
- {% include "dashboard/index-files.html" %} + {% include "dashboard/store/index-files.html" %}
- {% endcomment %} + {% endif %} {% if perms.vm.create_template %}
diff --git a/circle/dashboard/templates/dashboard/lease-edit.html b/circle/dashboard/templates/dashboard/lease-edit.html index 0186ec1..519f0bd 100644 --- a/circle/dashboard/templates/dashboard/lease-edit.html +++ b/circle/dashboard/templates/dashboard/lease-edit.html @@ -82,9 +82,9 @@ {% endfor %} - - {% for id, name in acl.levels %} {% endfor %} diff --git a/circle/dashboard/templates/dashboard/node-create.html b/circle/dashboard/templates/dashboard/node-create.html index 640e951..68c63f3 100644 --- a/circle/dashboard/templates/dashboard/node-create.html +++ b/circle/dashboard/templates/dashboard/node-create.html @@ -1,11 +1,12 @@ {% load crispy_forms_tags %} + +{% csrf_token %} +{% crispy formset formset.form.helper %} + + -
-{% csrf_token %} -{% crispy formset formset.form.helper %} -
diff --git a/circle/dashboard/templates/dashboard/nojs-wrapper.html b/circle/dashboard/templates/dashboard/nojs-wrapper.html index cbba2a1..e5f87c2 100644 --- a/circle/dashboard/templates/dashboard/nojs-wrapper.html +++ b/circle/dashboard/templates/dashboard/nojs-wrapper.html @@ -16,7 +16,7 @@ {% endblock %} {% block extra_js %} - {% if template == "dashboard/_vm-create-1.html" %} + {% if template == "dashboard/_vm-create-1.html" or template == "dashboard/_vm-create-2.html" %} {% endif %} {% endblock %} diff --git a/circle/dashboard/templates/dashboard/profile.html b/circle/dashboard/templates/dashboard/profile.html index 70eca0a..8d3b08f 100644 --- a/circle/dashboard/templates/dashboard/profile.html +++ b/circle/dashboard/templates/dashboard/profile.html @@ -64,7 +64,7 @@ {% trans "Virtual machines owned by the user" %} ({{ instances_owned|length }}) -
+
{% with form=form %} {% include "display-form-errors.html" %} {% endwith %} - {% crispy form %} + + {% csrf_token %} + + {{ form.name|as_crispy_field }} + +
+ {% trans "Resource configuration" %} + {% include "dashboard/_resources-sliders.html" with field_priority=form.priority field_num_cores=form.num_cores field_ram_size=form.ram_size %} + {{ form.max_ram_size|as_crispy_field }} +
+ +
+ {% trans "Virtual machine settings" %} + {{ form.arch|as_crispy_field }} + {{ form.access_method|as_crispy_field }} + {{ form.boot_menu|as_crispy_field }} + {{ form.raw_data|as_crispy_field }} + {{ form.req_traits|as_crispy_field }} + {{ form.description|as_crispy_field }} + {{ form.system|as_crispy_field }} +
+
+ {% trans "External resources" %} + {{ form.networks|as_crispy_field }} + {{ form.lease|as_crispy_field }} + + {{ form.tags|as_crispy_field }} +
+ + +
diff --git a/circle/dashboard/templates/dashboard/vm-detail.html b/circle/dashboard/templates/dashboard/vm-detail.html index e6fd893..1996a37 100644 --- a/circle/dashboard/templates/dashboard/vm-detail.html +++ b/circle/dashboard/templates/dashboard/vm-detail.html @@ -63,7 +63,11 @@
- + {{ instance.get_status_display|upper }}
@@ -75,6 +79,8 @@
{% if instance.get_connect_port %} {{ instance.get_connect_host }}:{{ instance.get_connect_port }} + {% elif instance.interface_set.count < 1%} + {% trans "The VM doesn't have any network interface." %} {% else %} {% trans "The required port for this protocol is not forwarded." %} {% endif %} @@ -100,7 +106,7 @@
{% with op=op.password_reset %}{% if op %} - {% trans "Generate new password!" %} + {% trans "Generate new password!" %} {% endif %}{% endwith %}
@@ -109,8 +115,7 @@
{% trans "Command" %} diff --git a/circle/dashboard/templates/dashboard/vm-detail/resources.html b/circle/dashboard/templates/dashboard/vm-detail/resources.html index 77f0d49..209cc72 100644 --- a/circle/dashboard/templates/dashboard/vm-detail/resources.html +++ b/circle/dashboard/templates/dashboard/vm-detail/resources.html @@ -2,55 +2,26 @@ {% load sizefieldtags %} {% load crispy_forms_tags %} -
+ {% csrf_token %} -

-

- -
-
- -
-

- - -

-

- -
-
- -
-

- - -

-

- -
-
- MiB -
-

+ {% 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 %} {% if can_change_resources %} -

-

- - {% trans "Stop your VM to change resources." %} -
-

{% endif %}
+
+

diff --git a/circle/dashboard/templates/dashboard/vm-list/column-actions.html b/circle/dashboard/templates/dashboard/vm-list/column-actions.html index bf72135..689685b 100644 --- a/circle/dashboard/templates/dashboard/vm-list/column-actions.html +++ b/circle/dashboard/templates/dashboard/vm-list/column-actions.html @@ -2,8 +2,6 @@ diff --git a/circle/dashboard/tests/test_views.py b/circle/dashboard/tests/test_views.py index 41b19e8..8e416a7 100644 --- a/circle/dashboard/tests/test_views.py +++ b/circle/dashboard/tests/test_views.py @@ -107,28 +107,6 @@ class VmDetailTest(LoginMixin, TestCase): response = c.get('/dashboard/vm/1/') self.assertEqual(response.status_code, 200) - def test_permitted_vm_delete(self): - c = Client() - self.login(c, 'user2') - inst = Instance.objects.get(pk=1) - inst.set_level(self.u2, 'owner') - response = c.post('/dashboard/vm/delete/1/') - self.assertEqual(response.status_code, 302) - - def test_not_permitted_vm_delete(self): - c = Client() - self.login(c, 'user2') - inst = Instance.objects.get(pk=1) - inst.set_level(self.u2, 'operator') - response = c.post('/dashboard/vm/delete/1/') - self.assertEqual(response.status_code, 403) - - def test_unpermitted_vm_delete(self): - c = Client() - self.login(c, 'user1') - response = c.post('/dashboard/vm/delete/1/') - self.assertEqual(response.status_code, 403) - def test_unpermitted_vm_mass_delete(self): c = Client() self.login(c, 'user1') @@ -304,10 +282,12 @@ class VmDetailTest(LoginMixin, TestCase): c = Client() self.login(c, 'superuser') kwargs = InstanceTemplate.objects.get(id=1).__dict__.copy() - kwargs.update(name='t2', lease=1, disks=1, raw_data='tst2') + kwargs.update(name='t2', lease=1, disks=1, + raw_data='') response = c.post('/dashboard/template/1/', kwargs) self.assertEqual(response.status_code, 302) - self.assertEqual(InstanceTemplate.objects.get(id=1).raw_data, 'tst2') + self.assertEqual(InstanceTemplate.objects.get(id=1).raw_data, + "") def test_permitted_lease_delete_w_template_using_it(self): c = Client() @@ -529,36 +509,38 @@ class VmDetailTest(LoginMixin, TestCase): def test_permitted_wake_up_wrong_state(self): c = Client() self.login(c, "user2") - with patch.object(WakeUpOperation, 'async') as mock_method: + with patch.object(WakeUpOperation, 'async') as mock_method, \ + patch.object(Instance.WrongStateError, 'send_message') as wro: inst = Instance.objects.get(pk=1) mock_method.side_effect = inst.wake_up inst.status = 'RUNNING' inst.set_level(self.u2, 'owner') - with patch('dashboard.views.messages') as msg: - c.post("/dashboard/vm/1/op/wake_up/") - assert msg.error.called + c.post("/dashboard/vm/1/op/wake_up/") inst = Instance.objects.get(pk=1) self.assertEqual(inst.status, 'RUNNING') # mocked anyway assert mock_method.called + assert wro.called def test_permitted_wake_up(self): c = Client() self.login(c, "user2") - with patch.object(Instance, 'select_node', return_value=None): - with patch.object(WakeUpOperation, 'async') as new_wake_up: - with patch('vm.tasks.vm_tasks.wake_up.apply_async') as wuaa: - inst = Instance.objects.get(pk=1) - new_wake_up.side_effect = inst.wake_up - inst.get_remote_queue_name = Mock(return_value='test') - inst.status = 'SUSPENDED' - inst.set_level(self.u2, 'owner') - with patch('dashboard.views.messages') as msg: - response = c.post("/dashboard/vm/1/op/wake_up/") - assert not msg.error.called - self.assertEqual(response.status_code, 302) - self.assertEqual(inst.status, 'RUNNING') - assert new_wake_up.called - assert wuaa.called + with patch.object(Instance, 'select_node', return_value=None), \ + patch.object(WakeUpOperation, 'async') as new_wake_up, \ + patch('vm.tasks.vm_tasks.wake_up.apply_async') as wuaa, \ + patch.object(Instance.WrongStateError, 'send_message') as wro: + inst = Instance.objects.get(pk=1) + new_wake_up.side_effect = inst.wake_up + inst.get_remote_queue_name = Mock(return_value='test') + inst.status = 'SUSPENDED' + inst.set_level(self.u2, 'owner') + with patch('dashboard.views.messages') as msg: + response = c.post("/dashboard/vm/1/op/wake_up/") + assert not msg.error.called + self.assertEqual(response.status_code, 302) + self.assertEqual(inst.status, 'RUNNING') + assert new_wake_up.called + assert wuaa.called + assert not wro.called def test_unpermitted_wake_up(self): c = Client() @@ -585,7 +567,7 @@ class VmDetailTest(LoginMixin, TestCase): 'amount': 2, 'customized': 1, 'template': 1, - 'cpu_priority': 1, 'cpu_count': 1, 'ram_size': 1, + 'cpu_priority': 10, 'cpu_count': 1, 'ram_size': 128, 'network': [], }) @@ -1796,9 +1778,11 @@ class SshKeyTest(LoginMixin, TestCase): def setUp(self): self.u1 = User.objects.create(username='user1') self.u1.set_password('password') + self.u1.profile = Profile() self.u1.save() self.u2 = User.objects.create(username='user2') self.u2.set_password('password') + self.u2.profile = Profile() self.u2.save() self.k1 = UserKey(key='ssh-rsa AAAAB3NzaC1yc2EC asd', user=self.u1) self.k1.save() diff --git a/circle/dashboard/urls.py b/circle/dashboard/urls.py index 6d6bbb5..dc147b7 100644 --- a/circle/dashboard/urls.py +++ b/circle/dashboard/urls.py @@ -28,7 +28,7 @@ from .views import ( NodeDetailView, NodeFlushView, NodeGraphView, NodeList, NodeStatus, NotificationView, PortDelete, TemplateAclUpdateView, TemplateCreate, TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView, - TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView, + TransferOwnershipView, vm_activity, VmCreate, VmDetailView, VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, DiskRemoveView, get_disk_download_status, InterfaceDeleteView, GroupRemoveUserView, @@ -39,6 +39,8 @@ from .views import ( get_vm_screenshot, ProfileView, toggle_use_gravatar, UnsubscribeFormView, UserKeyDelete, UserKeyDetail, UserKeyCreate, + StoreList, store_download, store_upload, store_get_upload_url, StoreRemove, + store_new_directory, store_refresh_toplist, VmTraitsUpdate, VmRawDataUpdate, GroupPermissionsView, LeaseAclUpdateView, @@ -86,8 +88,6 @@ urlpatterns = patterns( url(r'^vm/list/$', VmList.as_view(), name='dashboard.views.vm-list'), url(r'^vm/create/$', VmCreate.as_view(), name='dashboard.views.vm-create'), - url(r'^vm/delete/(?P\d+)/$', VmDelete.as_view(), - name="dashboard.views.delete-vm"), url(r'^vm/mass-delete/', VmMassDelete.as_view(), name='dashboard.view.mass-delete-vm'), url(r'^vm/(?P\d+)/activity/$', vm_activity), @@ -179,5 +179,21 @@ urlpatterns = patterns( url(r'^sshkey/create/$', UserKeyCreate.as_view(), name="dashboard.views.userkey-create"), + url(r'^autocomplete/', include('autocomplete_light.urls')), + + url(r"^store/list/$", StoreList.as_view(), + name="dashboard.views.store-list"), + url(r"^store/download/$", store_download, + name="dashboard.views.store-download"), + url(r"^store/upload/url$", store_get_upload_url, + name="dashboard.views.store-upload-url"), + url(r"^store/upload/$", store_upload, + name="dashboard.views.store-upload"), + url(r"^store/remove/$", StoreRemove.as_view(), + name="dashboard.views.store-remove"), + url(r"^store/new_directory/$", store_new_directory, + name="dashboard.views.store-new-directory"), + url(r"^store/refresh_toplist$", store_refresh_toplist, + name="dashboard.views.store-refresh-toplist"), ) diff --git a/circle/dashboard/views.py b/circle/dashboard/views.py index ef16edf..7f0425c 100644 --- a/circle/dashboard/views.py +++ b/circle/dashboard/views.py @@ -20,6 +20,7 @@ from __future__ import unicode_literals, absolute_import from collections import OrderedDict from itertools import chain from os import getenv +from os.path import join, normpath, dirname, basename from urlparse import urljoin import json import logging @@ -29,22 +30,27 @@ import requests from django.conf import settings from django.contrib.auth.models import User, Group from django.contrib.auth.views import login, redirect_to_login +from django.contrib.auth.decorators import login_required from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import ( PermissionDenied, SuspiciousOperation, ) +from django.core.cache import get_cache from django.core import signing from django.core.urlresolvers import reverse, reverse_lazy from django.db.models import Count, Q from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.shortcuts import redirect, render, get_object_or_404 +from django.shortcuts import ( + redirect, render, get_object_or_404, render_to_response, +) from django.views.decorators.http import require_GET, require_POST from django.views.generic.detail import SingleObjectMixin from django.views.generic import (TemplateView, DetailView, View, DeleteView, UpdateView, CreateView, ListView) from django.contrib import messages -from django.utils.translation import ugettext as _, ugettext_noop -from django.utils.translation import ungettext as __ +from django.utils.translation import ( + ugettext as _, ugettext_noop, ungettext_lazy +) from django.template.loader import render_to_string from django.template import RequestContext @@ -64,14 +70,14 @@ from .forms import ( VmSaveForm, UserKeyForm, VmRenewForm, CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm, TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm, - VmAddInterfaceForm, + VmResourcesForm, VmAddInterfaceForm, ) from .tables import ( NodeListTable, NodeVmListTable, TemplateListTable, LeaseListTable, GroupListTable, UserKeyListTable ) -from common.models import HumanReadableObject +from common.models import HumanReadableObject, HumanReadableException from vm.models import ( Instance, instance_activity, InstanceActivity, InstanceTemplate, Interface, InterfaceTemplate, Lease, Node, NodeActivity, Trait, @@ -80,6 +86,8 @@ from storage.models import Disk from firewall.models import Vlan, Host, Rule from .models import Favourite, Profile, GroupProfile, FutureMember +from .store_api import Store, NoStoreException, NotOkException + logger = logging.getLogger(__name__) saml_available = hasattr(settings, "SAML_CONFIG") @@ -219,6 +227,27 @@ class IndexView(LoginRequiredMixin, TemplateView): context['templates'] = InstanceTemplate.get_objects_with_level( 'operator', user).all()[:5] + # toplist + if settings.STORE_URL: + cache_key = "files-%d" % self.request.user.pk + cache = get_cache("default") + files = cache.get(cache_key) + if not files: + try: + store = Store(self.request.user) + toplist = store.toplist() + quota = store.get_quota() + files = {'toplist': toplist, 'quota': quota} + except Exception: + logger.exception("Unable to get tolist for %s", + unicode(self.request.user)) + files = {'toplist': []} + cache.set(cache_key, files, 300) + + context['files'] = files + else: + context['no_store'] = True + return context @@ -281,6 +310,10 @@ class VmDetailView(CheckedDetailView): activities = activities[:10] context['activities'] = activities context['show_show_all'] = show_show_all + latest = instance.get_latest_activity_in_progress() + context['is_new_state'] = (latest and + latest.resultant_state is not None and + instance.status != latest.resultant_state) context['vlans'] = Vlan.get_objects_with_level( 'user', self.request.user @@ -298,6 +331,8 @@ class VmDetailView(CheckedDetailView): context['ipv6_port'] = instance.get_connect_port(use_ipv6=True) # resources forms + context['resources_form'] = VmResourcesForm(instance=instance) + if self.request.user.is_superuser: context['traits_form'] = TraitsForm(instance=instance) context['raw_data_form'] = RawDataForm(instance=instance) @@ -322,8 +357,6 @@ class VmDetailView(CheckedDetailView): return v(request) raise Http404() - raise Http404() - def __set_name(self, request): self.object = self.get_object() if not self.object.has_level(request.user, 'owner'): @@ -562,9 +595,13 @@ class OperationView(RedirectToLoginMixin, DetailView): done = False try: task = self.get_op().async(user=request.user, **extra) + except HumanReadableException as e: + e.send_message(request) + logger.exception("Could not start operation") + result = e except Exception as e: messages.error(request, _('Could not start operation.')) - logger.exception(e) + logger.exception("Could not start operation") result = e else: wait = self.wait_for_result @@ -575,6 +612,10 @@ class OperationView(RedirectToLoginMixin, DetailView): except TimeoutError: logger.debug("Result didn't arrive in %ss", self.wait_for_result, exc_info=True) + except HumanReadableException as e: + e.send_message(request) + logger.exception(e) + result = e except Exception as e: messages.error(request, _('Operation failed.')) logger.debug("Operation failed.", exc_info=True) @@ -702,6 +743,7 @@ class VmCreateDiskView(FormOperationMixin, VmOperationView): form_class = VmCreateDiskForm show_in_toolbar = False icon = 'hdd-o' + effect = "success" is_disk_operation = True @@ -711,6 +753,7 @@ class VmDownloadDiskView(FormOperationMixin, VmOperationView): form_class = VmDownloadDiskForm show_in_toolbar = False icon = 'download' + effect = "success" is_disk_operation = True @@ -749,22 +792,35 @@ class VmResourcesChangeView(VmOperationView): op = 'resources_change' icon = "save" show_in_toolbar = False + wait_for_result = 0.5 def post(self, request, extra=None, *args, **kwargs): if extra is None: extra = {} - resources = { - 'num_cores': "cpu-count", - 'priority': "cpu-priority", - 'ram_size': "ram-size", - "max_ram_size": "ram-size", # TODO - } - for k, v in resources.iteritems(): - extra[k] = request.POST.get(v) + instance = get_object_or_404(Instance, pk=kwargs['pk']) - return super(VmResourcesChangeView, self).post(request, extra, - *args, **kwargs) + form = VmResourcesForm(request.POST, instance=instance) + if not form.is_valid(): + for f in form.errors: + messages.error(request, "%s: %s" % ( + f, form.errors[f].as_text() + )) + if request.is_ajax(): # this is not too nice + store = messages.get_messages(request) + store.used = True + return HttpResponse( + json.dumps({'success': False, + 'messages': [unicode(m) for m in store]}), + content_type="application=json" + ) + else: + return redirect(instance.get_absolute_url() + "#resources") + else: + extra = form.cleaned_data + extra['max_ram_size'] = extra['ram_size'] + return super(VmResourcesChangeView, self).post(request, extra, + *args, **kwargs) class TokenOperationView(OperationView): @@ -808,6 +864,7 @@ class TokenOperationView(OperationView): logger.info("Request user changed to %s at %s", user, self.request.get_full_path()) self.request.user = user + self.request.token_user = True else: logger.debug("no token supplied to %s", self.request.get_full_path()) @@ -1320,10 +1377,13 @@ class TemplateCreate(SuccessMessageMixin, CreateView): def get_context_data(self, *args, **kwargs): context = super(TemplateCreate, self).get_context_data(*args, **kwargs) + num_leases = Lease.get_objects_with_level("user", + self.request.user).count() + can_create_leases = self.request.user.has_perm("create_leases") context.update({ 'box_title': _("Create a new base VM"), 'template': "dashboard/_template-create.html", - 'leases': Lease.objects.count() + 'show_lease_create': num_leases < 1 and can_create_leases }) return context @@ -1858,7 +1918,7 @@ class VmCreate(LoginRequiredMixin, TemplateView): i.deploy.async(user=request.user) if len(instances) > 1: - messages.success(request, __( + messages.success(request, ungettext_lazy( "Successfully created %(count)d VM.", # this should not happen "Successfully created %(count)d VMs.", len(instances)) % { 'count': len(instances)}) @@ -1885,9 +1945,13 @@ class VmCreate(LoginRequiredMixin, TemplateView): except Exception as e: logger.debug('No profile or instance limit: %s', e) else: + try: + amount = int(request.POST.get("amount", 1)) + except: + amount = limit # TODO this should definitely use a Form current = Instance.active.filter(owner=user).count() logger.debug('current use: %d, limit: %d', current, limit) - if limit < current: + if current + amount > limit: messages.error(request, _('Instance limit (%d) exceeded.') % limit) if request.is_ajax(): @@ -2051,53 +2115,6 @@ class GroupProfileUpdate(SuccessMessageMixin, GroupCodeMixin, return self.form_valid(form) -class VmDelete(LoginRequiredMixin, DeleteView): - model = Instance - template_name = "dashboard/confirm/base-delete.html" - - def get_template_names(self): - if self.request.is_ajax(): - return ['dashboard/confirm/ajax-delete.html'] - else: - return ['dashboard/confirm/base-delete.html'] - - def get_success_url(self): - next = self.request.POST.get('next') - if next: - return next - else: - return reverse_lazy('dashboard.index') - - def get_context_data(self, **kwargs): - object = self.get_object() - if not object.has_level(self.request.user, 'owner'): - raise PermissionDenied() - - context = super(VmDelete, self).get_context_data(**kwargs) - return context - - # github.com/django/django/blob/master/django/views/generic/edit.py#L245 - def delete(self, request, *args, **kwargs): - object = self.get_object() - if not object.has_level(request.user, 'owner'): - raise PermissionDenied() - - object.destroy.async(user=request.user) - success_url = self.get_success_url() - success_message = _("VM successfully deleted.") - - if request.is_ajax(): - if request.POST.get('redirect').lower() == "true": - messages.success(request, success_message) - return HttpResponse( - json.dumps({'message': success_message}), - content_type="application/json", - ) - else: - messages.success(request, success_message) - return HttpResponseRedirect(success_url) - - class NodeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): """This stuff deletes the node. @@ -2332,7 +2349,7 @@ class VmMassDelete(LoginRequiredMixin, View): except Exception as e: logger.error(e) - success_message = __( + success_message = ungettext_lazy( "Mass delete complete, the following VM was deleted: %s.", "Mass delete complete, the following VMs were deleted: %s.", len(names)) % u', '.join(names) @@ -2445,6 +2462,9 @@ def vm_activity(request, pk): response['human_readable_status'] = instance.get_status_display() response['status'] = instance.status response['icon'] = instance.get_status_icon() + latest = instance.get_latest_activity_in_progress() + response['is_new_state'] = (latest and latest.resultant_state is not None + and instance.status != latest.resultant_state) context = { 'instance': instance, @@ -3126,5 +3146,169 @@ class UserKeyCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView): return kwargs +class StoreList(LoginRequiredMixin, TemplateView): + template_name = "dashboard/store/list.html" + + def get_context_data(self, **kwargs): + context = super(StoreList, self).get_context_data(**kwargs) + directory = self.request.GET.get("directory", "/") + directory = "/" if not len(directory) else directory + + store = Store(self.request.user) + context['root'] = store.list(directory) + context['quota'] = store.get_quota() + context['up_url'] = self.create_up_directory(directory) + context['current'] = directory + context['next_url'] = "%s%s?directory=%s" % ( + settings.DJANGO_URL.rstrip("/"), + reverse("dashboard.views.store-list"), directory) + return context + + def get(self, *args, **kwargs): + try: + if self.request.is_ajax(): + context = self.get_context_data(**kwargs) + return render_to_response( + "dashboard/store/_list-box.html", + RequestContext(self.request, context), + ) + else: + return super(StoreList, self).get(*args, **kwargs) + except NoStoreException: + messages.warning(self.request, _("No store.")) + return redirect("/") + except NotOkException: + messages.warning(self.request, _("Store has some problems now." + " Try again later.")) + return redirect("/") + + def create_up_directory(self, directory): + path = normpath(join('/', directory, '..')) + if not path.endswith("/"): + path += "/" + return path + + +@require_GET +@login_required +def store_download(request): + path = request.GET.get("path") + try: + url = Store(request.user).request_download(path) + except Exception: + messages.error(request, _("Something went wrong during download.")) + logger.exception("Unable to download, " + "maybe it is already deleted") + return redirect(reverse("dashboard.views.store-list")) + return redirect(url) + + +@require_GET +@login_required +def store_upload(request): + directory = request.GET.get("directory", "/") + try: + action = Store(request.user).request_upload(directory) + except Exception: + logger.exception("Unable to upload") + messages.error(request, _("Unable to upload file.")) + return redirect("/") + + next_url = "%s%s?directory=%s" % ( + settings.DJANGO_URL.rstrip("/"), + reverse("dashboard.views.store-list"), directory) + + return render(request, "dashboard/store/upload.html", + {'directory': directory, 'action': action, + 'next_url': next_url}) + + +@require_GET +@login_required +def store_get_upload_url(request): + current_dir = request.GET.get("current_dir") + try: + url = Store(request.user).request_upload(current_dir) + except Exception: + logger.exception("Unable to upload") + messages.error(request, _("Unable to upload file.")) + return redirect("/") + return HttpResponse( + json.dumps({'url': url}), content_type="application/json") + + +class StoreRemove(LoginRequiredMixin, TemplateView): + template_name = "dashboard/store/remove.html" + + def get_context_data(self, *args, **kwargs): + context = super(StoreRemove, self).get_context_data(*args, **kwargs) + path = self.request.GET.get("path", "/") + if path == "/": + SuspiciousOperation() + + context['path'] = path + context['is_dir'] = path.endswith("/") + if context['is_dir']: + context['directory'] = path + else: + context['directory'] = dirname(path) + context['name'] = basename(path) + + return context + + def get(self, *args, **kwargs): + try: + return super(StoreRemove, self).get(*args, **kwargs) + except NoStoreException: + return redirect("/") + + def post(self, *args, **kwargs): + path = self.request.POST.get("path") + try: + Store(self.request.user).remove(path) + except Exception: + logger.exception("Unable to remove %s", path) + messages.error(self.request, _("Unable to remove %s.") % path) + + return redirect("%s?directory=%s" % ( + reverse("dashboard.views.store-list"), + dirname(dirname(path)), + )) + + +@require_POST +@login_required +def store_new_directory(request): + path = request.POST.get("path") + name = request.POST.get("name") + + try: + Store(request.user).new_folder(join(path, name)) + except Exception: + logger.exception("Unable to create folder %s in %s for %s", + name, path, unicode(request.user)) + messages.error(request, _("Unable to create folder.")) + return redirect("%s?directory=%s" % ( + reverse("dashboard.views.store-list"), path)) + + +@require_POST +@login_required +def store_refresh_toplist(request): + cache_key = "files-%d" % request.user.pk + cache = get_cache("default") + try: + store = Store(request.user) + toplist = store.toplist() + quota = store.get_quota() + files = {'toplist': toplist, 'quota': quota} + except Exception: + logger.exception("Can't get toplist of %s", unicode(request.user)) + files = {'toplist': []} + cache.set(cache_key, files, 300) + + return redirect(reverse("dashboard.index")) + + def absolute_url(url): return urljoin(settings.DJANGO_URL, url) diff --git a/circle/fabfile.py b/circle/fabfile.py old mode 100644 new mode 100755 index bd75db0..b41813a --- a/circle/fabfile.py +++ b/circle/fabfile.py @@ -1,3 +1,4 @@ +#!/bin/echo Usage: fab --list -f import contextlib import datetime @@ -9,14 +10,14 @@ from fabric.decorators import roles, parallel env.roledefs['portal'] = ['localhost'] try: - from vm.models import Node - from storage.models import DataStore + from vm.models import Node as _Node + from storage.models import DataStore as _DataStore except Exception as e: print e else: env.roledefs['node'] = [unicode(n.host.ipv4) - for n in Node.objects.filter(enabled=True)] - env.roledefs['storage'] = [DataStore.objects.get().hostname] + for n in _Node.objects.filter(enabled=True)] + env.roledefs['storage'] = [_DataStore.objects.get().hostname] def update_all(): @@ -131,7 +132,7 @@ def update_storage(): "Update and restart storagedriver" with _stopped("storage"): pull("~/storagedriver") - pip("storage", "~/storagedriver/requirements/production.txt") + pip("storagedriver", "~/storagedriver/requirements/production.txt") @parallel diff --git a/circle/firewall/tasks/local_tasks.py b/circle/firewall/tasks/local_tasks.py index 2a06967..3085aca 100644 --- a/circle/firewall/tasks/local_tasks.py +++ b/circle/firewall/tasks/local_tasks.py @@ -98,5 +98,5 @@ def reloadtask(type='Host', timeout=15): }[type] logger.info("Reload %s on next periodic iteration applying change to %s.", ", ".join(reload), type) - if all(cache.add("%s_lock" % i, True, 30) for i in reload): + if all([cache.add("%s_lock" % i, 'true', 30) for i in reload]): reloadtask_worker.apply_async(queue='localhost.man', countdown=5) diff --git a/circle/locale/hu/LC_MESSAGES/circle-hu.lokalize b/circle/locale/hu/LC_MESSAGES/circle-hu.lokalize new file mode 100644 index 0000000..c990d3f --- /dev/null +++ b/circle/locale/hu/LC_MESSAGES/circle-hu.lokalize @@ -0,0 +1,6 @@ +[General] +LangCode=hu +MailingList=cloud@ik.bme.hu +PotBaseDir=./ +ProjectID=circle-hu +TargetLangCode=hu diff --git a/circle/locale/hu/LC_MESSAGES/django.po b/circle/locale/hu/LC_MESSAGES/django.po index 88c1d8a..9cd88d4 100644 --- a/circle/locale/hu/LC_MESSAGES/django.po +++ b/circle/locale/hu/LC_MESSAGES/django.po @@ -6,9 +6,9 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-05-07 14:22+0200\n" -"PO-Revision-Date: 2014-05-07 15:32+0200\n" -"Last-Translator: Mate Ory \n" +"POT-Creation-Date: 2014-07-31 13:20+0200\n" +"PO-Revision-Date: 2014-07-31 13:41+0200\n" +"Last-Translator: Mate Ory \n" "Language-Team: Hungarian \n" "Language: hu\n" "MIME-Version: 1.0\n" @@ -17,483 +17,648 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 1.5\n" -#: circle/settings/base.py:118 +#: circle/settings/base.py:123 msgid "English" msgstr "Angol" -#: circle/settings/base.py:119 +#: circle/settings/base.py:124 msgid "Hungarian" msgstr "Magyar" -#: common/models.py:107 -#: dashboard/templates/dashboard/instanceactivity_detail.html:27 +#: common/models.py:59 +msgid "Failure." +msgstr "Hiba." + +#: common/models.py:60 +#, python-format +msgid "Unhandled exception: %(error)s" +msgstr "Kezeletlen kivétel: %(error)s" + +#: common/models.py:135 +#: dashboard/templates/dashboard/instanceactivity_detail.html:32 msgid "activity code" msgstr "tevékenységkód" -#: common/models.py:110 +#: common/models.py:138 +msgid "human readable name" +msgstr "olvasható név" + +#: common/models.py:139 +msgid "Human readable name of activity." +msgstr "A tevékenység neve olvasható formában." + +#: common/models.py:143 msgid "Celery task unique identifier." msgstr "Celery feladat egyedi azonosítója." -#: common/models.py:111 +#: common/models.py:144 msgid "task_uuid" msgstr "feladat uuid" -#: common/models.py:112 -#: dashboard/templates/dashboard/instanceactivity_detail.html:36 -#: firewall/models.py:273 storage/models.py:82 vm/models/instance.py:130 -#: vm/models/instance.py:198 +#: common/models.py:145 +#: dashboard/templates/dashboard/instanceactivity_detail.html:41 +#: firewall/models.py:282 vm/models/common.py:79 vm/models/instance.py:133 +#: vm/models/instance.py:211 msgid "user" msgstr "felhasználó" -#: common/models.py:113 +#: common/models.py:146 msgid "The person who started this activity." msgstr "A tevékenységet indító felhasználó." -#: common/models.py:114 +#: common/models.py:147 msgid "started at" msgstr "indítás ideje" -#: common/models.py:116 +#: common/models.py:149 msgid "Time of activity initiation." msgstr "A tevékenység megkezdésének időpontja." -#: common/models.py:117 +#: common/models.py:150 msgid "finished at" msgstr "befejezés ideje" -#: common/models.py:119 +#: common/models.py:152 msgid "Time of activity finalization." msgstr "A tevékenység befejeztének ideje." -#: common/models.py:121 +#: common/models.py:154 msgid "True, if the activity has finished successfully." msgstr "Igaz, ha a tevékenység sikeresen befejeződött." -#: common/models.py:123 -#: dashboard/templates/dashboard/instanceactivity_detail.html:55 +#: common/models.py:156 +#: dashboard/templates/dashboard/instanceactivity_detail.html:60 msgid "result" msgstr "eredmény" -#: common/models.py:124 +#: common/models.py:158 msgid "Human readable result of activity." msgstr "A tevékenység eredménye olvasható formában." -#: dashboard/forms.py:133 dashboard/templates/dashboard/node-detail.html:74 -#: dashboard/templates/dashboard/vm-detail.html:108 +#: dashboard/autocomplete_light_registry.py:12 +#: dashboard/templates/dashboard/lease-edit.html:86 +msgid "Name of group or user" +msgstr "Csoport vagy felhasználó neve" + +#: dashboard/autocomplete_light_registry.py:19 +msgid "group" +msgstr "csoport" + +#: dashboard/forms.py:64 dashboard/forms.py:926 dashboard/forms.py:947 +#: dashboard/forms.py:1216 dashboard/tables.py:264 +#: dashboard/templates/dashboard/vm-list.html:44 +#: dashboard/templates/dashboard/vm-detail/home.html:8 firewall/models.py:294 +#: network/templates/network/index.html:22 +#: network/templates/network/switch-port-edit.html:43 +msgid "Name" +msgstr "Név" + +#: dashboard/forms.py:65 vm/models/instance.py:138 +msgid "Human readable name of template." +msgstr "A sablon olvasható neve." + +#: dashboard/forms.py:157 dashboard/templates/dashboard/node-detail.html:74 +#: dashboard/templates/dashboard/vm-detail.html:131 msgid "Resources" msgstr "Erőforrások" -#: dashboard/forms.py:214 dashboard/templates/dashboard/template-edit.html:98 -#: dashboard/templates/dashboard/vm-detail/resources.html:59 +#: dashboard/forms.py:238 dashboard/templates/dashboard/template-edit.html:44 +#: dashboard/templates/dashboard/vm-detail/resources.html:68 msgid "No disks are added!" msgstr "Egy lemez sincs hozzáadva!" -#: dashboard/forms.py:258 dashboard/templates/base.html:53 -#: dashboard/templates/dashboard/_vm-create-1.html:40 -#: dashboard/templates/dashboard/vm-detail.html:122 +#: dashboard/forms.py:282 dashboard/templates/dashboard/_vm-create-1.html:40 +#: dashboard/templates/dashboard/base.html:46 +#: dashboard/templates/dashboard/vm-detail.html:145 msgid "Network" msgstr "Hálózat" -#: dashboard/forms.py:275 +#: dashboard/forms.py:299 msgid "Not added to any network!" msgstr "Egy hálózathoz sincs hozzáadva!" -#: dashboard/forms.py:331 +#: dashboard/forms.py:341 dashboard/templates/dashboard/_vm-create-1.html:53 +#: dashboard/templates/dashboard/vm-detail/home.html:30 +msgid "Description" +msgstr "Leírás" + +#: dashboard/forms.py:352 dashboard/forms.py:393 +msgid "Directory identifier" +msgstr "Címtári azonosító" + +#: dashboard/forms.py:372 +#: dashboard/templates/dashboard/store/_list-box.html:57 +#: network/templates/network/dashboard.html:25 +#: network/templates/network/dashboard.html:41 +#: network/templates/network/dashboard.html:57 +#: network/templates/network/dashboard.html:73 +#: network/templates/network/dashboard.html:89 +#: network/templates/network/dashboard.html:104 +#: network/templates/network/dashboard.html:120 +#: network/templates/network/dashboard.html:136 +msgid "Create" +msgstr "Létrehozás" + +#: dashboard/forms.py:401 dashboard/forms.py:1156 dashboard/forms.py:1173 +#: dashboard/forms.py:1199 dashboard/forms.py:1229 dashboard/forms.py:1254 +#: dashboard/forms.py:1274 dashboard/forms.py:1303 +#: dashboard/templates/dashboard/_manage_access.html:73 +#: dashboard/templates/dashboard/group-detail.html:101 +#: dashboard/templates/dashboard/lease-edit.html:96 +msgid "Save" +msgstr "Mentés" + +#: dashboard/forms.py:433 dashboard/templates/dashboard/vm-detail.html:74 msgid "Host" msgstr "Gép" -#: dashboard/forms.py:401 dashboard/templates/dashboard/node-detail.html:4 +#: dashboard/forms.py:503 dashboard/templates/dashboard/node-detail.html:4 #: dashboard/templates/dashboard/vm-list.html:56 msgid "Node" msgstr "Csomópont" -#: dashboard/forms.py:480 +#: dashboard/forms.py:582 msgid "Networks" msgstr "Hálózatok" -#: dashboard/forms.py:547 +#: dashboard/forms.py:691 msgid "Resource configuration" msgstr "Erőforrásbeállítások" -#: dashboard/forms.py:591 +#: dashboard/forms.py:735 msgid "Virtual machine settings" msgstr "Virtuális gépek beállításai" -#: dashboard/forms.py:601 +#: dashboard/forms.py:745 msgid "External resources" msgstr "Külső erőforrások" -#: dashboard/forms.py:697 dashboard/tables.py:54 dashboard/tables.py:202 +#: dashboard/forms.py:841 dashboard/tables.py:169 msgid "Suspend in" msgstr "Felfüggesztés ideje" -#: dashboard/forms.py:703 dashboard/forms.py:731 +#: dashboard/forms.py:847 dashboard/forms.py:875 msgid "hours" msgstr "óra" -#: dashboard/forms.py:708 dashboard/forms.py:736 +#: dashboard/forms.py:852 dashboard/forms.py:880 msgid "days" msgstr "nap" -#: dashboard/forms.py:713 dashboard/forms.py:741 +#: dashboard/forms.py:857 dashboard/forms.py:885 msgid "weeks" msgstr "hét" -#: dashboard/forms.py:718 dashboard/forms.py:746 +#: dashboard/forms.py:862 dashboard/forms.py:890 msgid "months" msgstr "hónap" -#: dashboard/forms.py:725 dashboard/tables.py:57 dashboard/tables.py:205 +#: dashboard/forms.py:869 dashboard/tables.py:172 msgid "Delete in" msgstr "Törlés ideje" -#: dashboard/forms.py:777 -msgid "Invalid format, you can use GB or MB!" -msgstr "Érvénytelen formátum. „GB” és „MB” is használható." +#: dashboard/forms.py:914 +msgid "Length" +msgstr "Hossz" -#: dashboard/forms.py:787 -msgid "You have to either specify size or URL" -msgstr "A méret vagy az URL megadása kötelező." +#: dashboard/forms.py:928 +#: dashboard/templates/dashboard/store/_list-box.html:117 +msgid "Size" +msgstr "Méret" -#: dashboard/forms.py:788 -msgid "Global" -msgstr "Általános" - -#: dashboard/forms.py:825 dashboard/templates/dashboard/vm-list.html:44 -#: dashboard/templates/dashboard/vm-detail/home.html:8 firewall/models.py:285 -#: network/templates/network/index.html:22 -#: network/templates/network/switch-port-edit.html:43 -msgid "Name" -msgstr "Név" +#: dashboard/forms.py:929 +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:826 -msgid "Disk size (for example: 20GB, 1500MB)" -msgstr "Lemezméret (például 20GB vagy 1500MB)" +#: dashboard/forms.py:935 +msgid "Invalid format, you can use GB or MB!" +msgstr "Érvénytelen formátum. „GB” és „MB” is használható." -#: dashboard/forms.py:828 -msgid "URL to an ISO image" -msgstr "ISO lemezkép URL-je" +#: dashboard/forms.py:948 +msgid "URL" +msgstr "URL" -#: dashboard/forms.py:832 -msgid "Either specify the size for an empty disk or a URL to an ISO image!" -msgstr "" -"Az üres lemez méretének vagy egy ISO lemezkép URL-jének megadása kötelező." +#: dashboard/forms.py:963 +#: dashboard/templates/dashboard/node-detail/resources.html:14 +msgid "Vlan" +msgstr "Vlan" -#: dashboard/forms.py:839 -#: dashboard/templates/dashboard/vm-detail/_network-port-add.html:14 -msgid "Add" -msgstr "Hozzáadás" +#: dashboard/forms.py:966 +msgid "No more networks." +msgstr "Nincs több hálózat." -#: dashboard/forms.py:862 +#: dashboard/forms.py:994 dashboard/templates/dashboard/profile.html:25 +#: dashboard/templates/dashboard/vm-detail.html:88 msgid "Username" msgstr "Felhasználónév" -#: dashboard/forms.py:876 dashboard/templates/dashboard/vm-detail.html:73 +#: dashboard/forms.py:1008 dashboard/templates/dashboard/vm-detail.html:90 msgid "Password" msgstr "Jelszó" -#: dashboard/forms.py:881 +#: dashboard/forms.py:1013 msgid "Sign in" msgstr "Bejelentkezés" -#: dashboard/forms.py:904 +#: dashboard/forms.py:1036 dashboard/templates/dashboard/profile.html:31 msgid "Email address" msgstr "E-mail cím" -#: dashboard/forms.py:909 +#: dashboard/forms.py:1041 msgid "Reset password" msgstr "Új jelszó" -#: dashboard/forms.py:925 dashboard/forms.py:1036 +#: dashboard/forms.py:1057 dashboard/forms.py:1182 msgid "Change password" msgstr "Jelszóváltoztatás" -#: dashboard/forms.py:1023 -msgid "Change language" -msgstr "Válasszon nyelvet" +#: dashboard/forms.py:1218 +msgid "Key" +msgstr "Kulcs" + +#: dashboard/forms.py:1219 +msgid "For example: ssh-rsa AAAAB3NzaC1yc2ED..." +msgstr "Például: ssh-rsa AAAAB3NzaC1yc2ED…" -#: dashboard/models.py:49 dashboard/templates/dashboard/index-groups.html:29 -#: dashboard/templates/dashboard/index-nodes.html:36 -#: dashboard/templates/dashboard/index-nodes.html:70 -#: dashboard/templates/dashboard/index-templates.html:33 -#: dashboard/templates/dashboard/index-vm.html:49 +#: dashboard/forms.py:1289 +msgid "permissions" +msgstr "jogosultságok" + +#: dashboard/models.py:63 dashboard/templates/dashboard/index-groups.html:39 +#: dashboard/templates/dashboard/index-nodes.html:46 +#: dashboard/templates/dashboard/index-nodes.html:71 +#: dashboard/templates/dashboard/index-templates.html:38 +#: dashboard/templates/dashboard/index-vm.html:66 +#: dashboard/templates/dashboard/store/_list-box.html:101 msgid "new" msgstr "új" -#: dashboard/models.py:50 +#: dashboard/models.py:64 msgid "delivered" msgstr "kézbesített" -#: dashboard/models.py:51 +#: dashboard/models.py:65 msgid "read" msgstr "olvasott" -#: dashboard/models.py:78 +#: dashboard/models.py:105 msgid "preferred language" msgstr "választott nyelv" -#: dashboard/models.py:84 +#: dashboard/models.py:111 dashboard/models.py:172 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:97 firewall/models.py:274 storage/models.py:83 -#: vm/models/instance.py:131 vm/models/instance.py:199 +#: dashboard/models.py:114 +msgid "Use Gravatar" +msgstr "Gravatar használata" + +#: dashboard/models.py:115 +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:117 +msgid "Email notifications" +msgstr "E-mail értesítések" + +#: dashboard/models.py:118 +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:121 +msgid "Samba password" +msgstr "Samba jelszó" + +#: dashboard/models.py:123 +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:128 +msgid "disk quota" +msgstr "lemezkvóta" + +#: dashboard/models.py:130 +msgid "Disk quota in mebibytes." +msgstr "Lemezkvóta mebibyte-okban." + +#: dashboard/models.py:166 +msgid "Can use autocomplete." +msgstr "Használhat automatikus kiegészítést." + +#: dashboard/models.py:184 firewall/models.py:283 vm/models/common.py:80 +#: vm/models/instance.py:134 vm/models/instance.py:212 msgid "operator" msgstr "operátor" -#: dashboard/models.py:98 firewall/models.py:97 firewall/models.py:369 -#: firewall/models.py:422 firewall/models.py:445 firewall/models.py:510 -#: firewall/models.py:805 firewall/models.py:834 storage/models.py:84 -#: vm/models/instance.py:132 vm/models/instance.py:200 +#: dashboard/models.py:185 firewall/models.py:100 firewall/models.py:378 +#: firewall/models.py:431 firewall/models.py:454 firewall/models.py:519 +#: firewall/models.py:825 firewall/models.py:854 vm/models/common.py:81 +#: vm/models/instance.py:135 vm/models/instance.py:213 msgid "owner" msgstr "tulajdonos" -#: dashboard/models.py:104 +#: dashboard/models.py:191 msgid "Unique identifier of the group at the organization." msgstr "A csoport egyedi szervezeti azonosítója." -#: dashboard/tables.py:93 +#: dashboard/tables.py:57 #: dashboard/templates/dashboard/node-detail/resources.html:12 msgid "Priority" msgstr "Prioritás" -#: dashboard/tables.py:226 +#: dashboard/tables.py:193 msgid "Cores" msgstr "Magok száma" -#: dashboard/tables.py:235 vm/models/instance.py:107 +#: dashboard/tables.py:202 vm/models/instance.py:110 msgid "Lease" msgstr "Bérlet" -#: dashboard/tables.py:248 dashboard/tables.py:279 +#: dashboard/tables.py:215 dashboard/tables.py:246 dashboard/tables.py:279 msgid "Actions" msgstr "Műveletek" -#: dashboard/views.py:271 -msgid "Password changed!" -msgstr "A jelszó megváltoztatva." +#: dashboard/tables.py:269 +msgid "Fingerprint" +msgstr "Ujjlenyomat" -#: dashboard/views.py:293 -msgid "Resources successfully updated!" -msgstr "Az erőforrások megváltoztatásra kerültek." +#: dashboard/tables.py:274 +msgid "Created at" +msgstr "Létrehozva" -#: dashboard/views.py:313 -msgid "VM successfully renamed!" +#: dashboard/views.py:282 +msgid "console access" +msgstr "konzolhozzáférés" + +#: dashboard/views.py:365 +msgid "VM successfully renamed." msgstr "A virtuális gép átnevezésre került." -#: dashboard/views.py:337 -msgid "VM description successfully updated!" -msgstr "A VM leíáramegváltoztatásra került." +#: dashboard/views.py:389 +msgid "VM description successfully updated." +msgstr "A VM leírása megváltoztatásra került." -#: dashboard/views.py:414 -msgid "There is a problem with your input!" +#: dashboard/views.py:466 +msgid "There is a problem with your input." msgstr "A megadott érték nem megfelelő." -#: dashboard/views.py:416 +#: dashboard/views.py:468 msgid "Unknown error." msgstr "Ismeretlen hiba." -#: dashboard/views.py:437 -msgid "Successfully added new interface!" -msgstr "Az interfész hozzáadása sikeres." - -#: dashboard/views.py:511 +#: dashboard/views.py:600 msgid "Could not start operation." msgstr "A művelet megkezdése meghiúsult." -#: dashboard/views.py:638 -msgid "Node successfully renamed!" +#: dashboard/views.py:617 +msgid "Operation failed." +msgstr "A művelet meghiúsult." + +#: dashboard/views.py:622 +msgid "Operation succeeded." +msgstr "A művelet sikeresen végrehajtásra került." + +#: dashboard/views.py:624 +msgid "Operation is started." +msgstr "A művelet megkezdődött." + +#: dashboard/views.py:846 +msgid "The token has expired." +msgstr "A token lejárt." + +#: dashboard/views.py:1003 +msgid "Node successfully renamed." msgstr "A csomópont átnevezésre került." -#: dashboard/views.py:697 -msgid "Group successfully renamed!" +#: dashboard/views.py:1097 +#, python-format +msgid "User \"%s\" not found." +msgstr "Nem található „%s” felhasználó." + +#: dashboard/views.py:1113 +msgid "Group successfully renamed." msgstr "A csoport átnevezésre került." -#: dashboard/views.py:752 +#: dashboard/views.py:1144 +#, python-format +msgid "Acl user/group %(w)s successfully modified." +msgstr "A(z) %(w)s ACL felhasználó/csoport módosításra került." + +#: dashboard/views.py:1146 +#, python-format +msgid "Acl user/group %(w)s successfully added." +msgstr "A(z) %(w)s ACL felhasználó/csoport hozzáadásra került." + +#: dashboard/views.py:1148 +#, python-format +msgid "Acl user/group %(w)s successfully removed." +msgstr "A(z) %(w)s ACL felhasználó/csoport törlésre került." + +#: dashboard/views.py:1232 msgid "" -"The original owner cannot be removed, however you can transfer ownership!" +"The original owner cannot be removed, however you can transfer ownership." msgstr "Az eredeti tulajdonos nem törölhető, azonban a tulajdon átruházható." -#: dashboard/views.py:773 dashboard/views.py:797 dashboard/views.py:826 -#: dashboard/views.py:846 +#: dashboard/views.py:1268 +#, python-format +msgid "User \"%s\" has already access to this object." +msgstr "„%s” felhasználó már hozzáfér az objektumhoz." + +#: dashboard/views.py:1277 +#, python-format +msgid "Group \"%s\" has already access to this object." +msgstr "„%s” csoport már hozzáfér az objektumhoz." + +#: dashboard/views.py:1282 #, python-format msgid "User or group \"%s\" not found." msgstr "Nem található „%s” felhasználó vagy csoport." -#: dashboard/views.py:868 +#: dashboard/views.py:1320 msgid "Choose template" msgstr "Válasszon sablont" -#: dashboard/views.py:883 -msgid "Select an option to proceed!" +#: dashboard/views.py:1335 +msgid "Select an option to proceed." msgstr "Válasszon a folytatáshoz." -#: dashboard/views.py:908 +#: dashboard/views.py:1363 msgid "Create a new base VM" msgstr "Alap VM létrehozása" -#: dashboard/views.py:966 -msgid "Successfully modified template!" +#: dashboard/views.py:1419 +msgid "Successfully modified template." msgstr "A sablon módosításra került." -#: dashboard/views.py:1068 -msgid "Template successfully deleted!" +#: dashboard/views.py:1513 +msgid "Template successfully deleted." msgstr "A sablon törlésre került." -#: dashboard/views.py:1165 dashboard/views.py:1211 -msgid "Group successfully deleted!" +#: dashboard/views.py:1710 +msgid "Member successfully removed from group." +msgstr "A csoporttag eltávolításra került." + +#: dashboard/views.py:1751 +msgid "Future user successfully removed from group." +msgstr "A leendő csoporttag eltávolításra került." + +#: dashboard/views.py:1778 +msgid "Group successfully deleted." msgstr "A csoport törlésre került." -#: dashboard/views.py:1257 +#: dashboard/views.py:1827 msgid "Customize VM" msgstr "VM testreszabása" -#: dashboard/views.py:1265 +#: dashboard/views.py:1835 msgid "Create a VM" msgstr "VM létrehozása" -#: dashboard/views.py:1331 +#: dashboard/views.py:1872 #, python-format -msgid "Successfully created %d VMs!" -msgstr "%d VM létrehozásra került." +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.py:1335 -msgid "VM successfully created!" +#: dashboard/views.py:1906 +msgid "VM successfully created." msgstr "VM létrehozásra került." -#: dashboard/views.py:1357 +#: dashboard/views.py:1935 #, python-format msgid "Instance limit (%d) exceeded." msgstr "A példányok létrehozási korlátját (%d) túllépte." -#: dashboard/views.py:1425 -msgid "Node successfully created!" +#: dashboard/views.py:1995 +msgid "Node successfully created." msgstr "A csomópont létrehozásra került." -#: dashboard/views.py:1468 -msgid "VM successfully deleted!" -msgstr "A VM törlésre került." +#: dashboard/views.py:2039 +msgid "Group successfully created." +msgstr "A csoport létrehozásra került." -#: dashboard/views.py:1507 -msgid "Node successfully deleted!" +#: dashboard/views.py:2053 +msgid "Group is successfully updated." +msgstr "A csoport frissítésre került." + +#: dashboard/views.py:2116 +msgid "Node successfully deleted." msgstr "A csomópont törlésre került." -#: dashboard/views.py:1554 +#: dashboard/views.py:2163 msgid "Trait successfully added to node." msgstr "A csomópontjellemző hozzáadásra került." -#: dashboard/views.py:1599 -msgid "Node successfully changed status!" +#: dashboard/views.py:2208 +msgid "Node successfully changed status." msgstr "A csomópont állapota megváltoztatásra került." -#: dashboard/views.py:1646 -msgid "Node successfully flushed!" +#: dashboard/views.py:2255 +msgid "Node successfully flushed." msgstr "A csomópont ürítésre kerül." -#: dashboard/views.py:1665 +#: dashboard/views.py:2274 msgid "Port delete confirmation" msgstr "Porteltávolítás megerősítése" -#: dashboard/views.py:1666 +#: dashboard/views.py:2275 #, python-format msgid "Are you sure you want to close %(port)d/%(proto)s on %(vm)s?" msgstr "Biztosan bezárja a(z) %(port)d/%(proto)s portot a következőn: %(vm)s?" -#: dashboard/views.py:1681 -msgid "Port successfully removed!" +#: dashboard/views.py:2290 +msgid "Port successfully removed." msgstr "A port eltávolításra került." -#: dashboard/views.py:1722 +#: dashboard/views.py:2332 #, python-format -msgid "Mass delete complete, the following VMs were deleted: %s!" -msgstr "A következő VM-ek törlésre kerültek: %s." +msgid "Mass delete complete, the following VM was deleted: %s." +msgid_plural "Mass delete complete, the following VMs were deleted: %s." +msgstr[0] "Sikeres tömeges törlés. A következő VM törlésre került: %s." +msgstr[1] "Sikeres tömeges törlés. A következő VM-ek törlésre kerültek: %s." -#: dashboard/views.py:1742 -msgid "Successfully created a new lease!" +#: dashboard/views.py:2354 +msgid "Successfully created a new lease." msgstr "Új bérlési mód létrehozásra került." -#: dashboard/views.py:1753 -msgid "Successfully modified lease!" +#: dashboard/views.py:2369 +msgid "Successfully modified lease." msgstr "A bérlési mód megváltoztatásra került." -#: dashboard/views.py:1776 +#: dashboard/views.py:2399 msgid "" "You can't delete this lease because some templates are still using it, " "modify these to proceed: " msgstr "" -"Nem törölhető a bérleti mód, mivel az alábbi sablonok " -"még használják. A folytatáshoz módosítsa őket: " +"Nem törölhető a bérleti mód, mivel az alábbi sablonok még használják. A " +"folytatáshoz módosítsa őket: " -#: dashboard/views.py:1793 -msgid "Lease successfully deleted!" +#: dashboard/views.py:2416 +msgid "Lease successfully deleted." msgstr "A bérlési mód törlésre került." -#: dashboard/views.py:1860 +#: dashboard/views.py:2494 msgid "Can not find specified user." msgstr "Nem található a megadott felhasználó." -#: dashboard/views.py:1876 +#: dashboard/views.py:2510 msgid "Ownership offer" msgstr "Átruházási ajánlat" -#: dashboard/views.py:1880 +#: dashboard/views.py:2511 +#, python-format +msgid "" +"%(user)s offered you to take the ownership of his/her virtual machine called " +"%(instance)s. Accept" +msgstr "" +"%(user)s át kívánja ruházni %(instance)s nevű virtuális gépét Önre. Elfogadás" + +#: dashboard/views.py:2517 msgid "Can not notify selected user." msgstr "A kiválaszott felhasználó értesítése sikertelen." -#: dashboard/views.py:1883 +#: dashboard/views.py:2520 #, python-format msgid "User %s is notified about the offer." msgstr "%s felhasználó értesítésre került az ajánlatról." -#: dashboard/views.py:1901 -msgid "Failed to perform requested action." -msgstr "A kért művelet végrehajtása meghiúsult." - -#: dashboard/views.py:1939 dashboard/views.py:1969 -msgid "The token has expired, please log in." -msgstr "A token lejárt, jelentkezzen be." - -#: dashboard/views.py:1951 dashboard/views.py:1980 -msgid "This token is invalid." -msgstr "A token érvénytelen." - -#: dashboard/views.py:2031 -msgid "Virtual machine is successfully renewed." -msgstr "A virtuális gép megújításra került." - -#: dashboard/views.py:2051 +#: dashboard/views.py:2531 msgid "Ownership successfully transferred to you." msgstr "A tulajdon átruházásra került." -#: dashboard/views.py:2064 +#: dashboard/views.py:2544 msgid "This token is for an other user." msgstr "A token más felhasználó nevére szól." -#: dashboard/views.py:2067 +#: dashboard/views.py:2547 msgid "This token is invalid or has expired." msgstr "A token érvénytelen vagy lejárt." -#: dashboard/views.py:2089 +#: dashboard/views.py:2569 msgid "Ownership accepted" msgstr "Átruházás elfogadva" -#: dashboard/views.py:2275 -msgid "Disk successfully added!" -msgstr "A lemez hozzáadásra került." - -#: dashboard/views.py:2277 -msgid "Disk download started!" -msgstr "A lemez letöltése megkezdődött." +#: dashboard/views.py:2570 +#, 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." -#: dashboard/views.py:2312 +#: dashboard/views.py:2744 msgid "You don't have a profile." msgstr "Nincs profilja." -#: dashboard/views.py:2370 +#: dashboard/views.py:2782 +msgid "Successfully modified subscription." +msgstr "A feliratkozás módosításra került." + +#: dashboard/views.py:2843 msgid "Disk remove confirmation" msgstr "Lemez törlésének megerősítése" -#: dashboard/views.py:2371 +#: dashboard/views.py:2844 #, python-format msgid "" "Are you sure you want to remove %(disk)s from " @@ -502,64 +667,76 @@ msgstr "" "Biztosan eltávolítja a(z) %(disk)s lemezt a következőből: " "%(app)s?" -#: dashboard/views.py:2391 -msgid "Disk successfully removed!" +#: dashboard/views.py:2863 +msgid "Disk successfully removed." msgstr "A lemez eltávolításra került." -#: dashboard/views.py:2442 +#: dashboard/views.py:2945 #, python-format msgid "" "Are you sure you want to remove this interface from %(vm)s?" msgstr "" "Biztosan eltávolítja az interfészt a(z) %(vm)s gépből?" -#: dashboard/views.py:2456 -msgid "Interface successfully deleted!" +#: dashboard/views.py:2959 +msgid "Interface successfully deleted." msgstr "Az interfész törlésre került." -#: dashboard/templates/base.html:42 -#: dashboard/templates/dashboard/notifications.html:9 -msgid "Notifications" -msgstr "Értesítések" +#: dashboard/views.py:3060 +msgid "Successfully modified SSH key." +msgstr "Az SSH kulcs módosításra került." -#: dashboard/templates/base.html:45 -msgid "Loading..." -msgstr "Betöltés..." +#: dashboard/views.py:3098 +msgid "SSH key successfully deleted." +msgstr "Az SSH kulcs törlésre került." -#: dashboard/templates/base.html:50 -msgid "Log out" -msgstr "Kijelentkezés" +#: dashboard/views.py:3114 +msgid "Successfully created a new SSH key." +msgstr "Az új SSH kulcs hozzáadásra került." -#: dashboard/templates/base.html:51 -msgid "User profile" -msgstr "Felhasználói profil" +#: dashboard/views.py:3154 +msgid "No store." +msgstr "Nincs tárhely." -#: dashboard/templates/base.html:54 -msgid "Admin" -msgstr "Adminisztráció" +#: dashboard/views.py:3171 +msgid "Something went wrong during download." +msgstr "Hiba a letöltésben." -#: dashboard/templates/base.html:57 -msgid "Log in " -msgstr "Bejelentkezés" +#: dashboard/views.py:3186 dashboard/views.py:3206 +msgid "Unable to upload file." +msgstr "Fájl feltöltése sikertelen." + +#: dashboard/views.py:3243 +#, python-format +msgid "Unable to remove %s." +msgstr "%s törlése sikertelen." + +#: dashboard/views.py:3262 +msgid "Unable to create folder." +msgstr "Mappa létrehozása sikertelen." + +#: dashboard/tasks/local_periodic_tasks.py:59 +#, python-format +msgid "%d new notification" +msgid_plural "%d new notifications" +msgstr[0] "%d új értesítés" +msgstr[1] "%d új értesítés" -#: dashboard/templates/base.html:80 +#: dashboard/templates/base.html:62 msgid "Legal notice" msgstr "Impresszum" -#: dashboard/templates/base.html:80 +#: dashboard/templates/base.html:63 msgid "Policy" msgstr "Szabályzat" -#: dashboard/templates/base.html:80 -#: dashboard/templates/dashboard/group-detail.html:10 -#: dashboard/templates/dashboard/index-groups.html:5 -#: dashboard/templates/dashboard/index-templates.html:4 -#: dashboard/templates/dashboard/index-vm.html:9 +#: dashboard/templates/base.html:64 +#: dashboard/templates/dashboard/group-detail.html:11 #: dashboard/templates/dashboard/node-detail.html:15 msgid "Help" msgstr "Súgó" -#: dashboard/templates/base.html:81 +#: dashboard/templates/base.html:65 msgid "Support" msgstr "Támogatás" @@ -568,34 +745,58 @@ msgid "Confirmation" msgstr "Megerősítés" #: dashboard/templates/dashboard/_disk-list-element.html:10 -#: dashboard/templates/dashboard/node-detail/_activity-timeline.html:21 -#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:33 +#: dashboard/templates/dashboard/node-detail/_activity-timeline.html:28 +#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:45 msgid "failed" msgstr "meghiúsult" -#: dashboard/templates/dashboard/_disk-list-element.html:15 -#: dashboard/templates/dashboard/_disk-list-element.html:17 -#: dashboard/templates/dashboard/template-edit.html:53 -#: dashboard/templates/dashboard/template-edit.html:68 -#: dashboard/templates/dashboard/vm-detail/access.html:23 -#: dashboard/templates/dashboard/vm-detail/network.html:101 -#: dashboard/templates/dashboard/vm-detail/network.html:133 +#: dashboard/templates/dashboard/_disk-list-element.html:16 +#: dashboard/templates/dashboard/_disk-list-element.html:18 +#: dashboard/templates/dashboard/_manage_access.html:34 +#: dashboard/templates/dashboard/_manage_access.html:56 +#: dashboard/templates/dashboard/group-detail.html:61 +#: dashboard/templates/dashboard/lease-edit.html:60 +#: dashboard/templates/dashboard/lease-edit.html:80 +#: dashboard/templates/dashboard/confirm/base-remove.html:12 +#: dashboard/templates/dashboard/store/_list-box.html:133 +#: dashboard/templates/dashboard/store/remove.html:36 +#: dashboard/templates/dashboard/vm-detail/network.html:79 +#: dashboard/templates/dashboard/vm-detail/network.html:111 msgid "Remove" msgstr "Eltávolítás" +#: dashboard/templates/dashboard/_display-name.html:14 +msgid "username" +msgstr "felhasználónév" + +#: dashboard/templates/dashboard/_manage_access.html:7 +#: dashboard/templates/dashboard/group-detail.html:61 +#: dashboard/templates/dashboard/lease-edit.html:35 +msgid "Who" +msgstr "Ki" + +#: dashboard/templates/dashboard/_manage_access.html:8 +#: dashboard/templates/dashboard/lease-edit.html:36 +msgid "What" +msgstr "Mi" + #: dashboard/templates/dashboard/_notifications-timeline.html:18 msgid "You have no notifications." msgstr "Nincs értesítése." -#: dashboard/templates/dashboard/_template-choose.html:4 -msgid "Customize an existing template or create a brand new one from scratch!" +#: dashboard/templates/dashboard/_template-choose.html:5 +msgid "Customize an existing template or create a brand new one from scratch." msgstr "Szabjon testre egy meglévő sablont, vagy készítsen egyet az alapoktól." -#: dashboard/templates/dashboard/_template-choose.html:21 +#: dashboard/templates/dashboard/_template-choose.html:7 +msgid "Customize an existing template." +msgstr "Létrehozhat példánysablont." + +#: dashboard/templates/dashboard/_template-choose.html:26 msgid "Create a new base VM without disk" msgstr "Új alap-VM létrehozása lemez nélkül" -#: dashboard/templates/dashboard/_template-choose.html:23 +#: dashboard/templates/dashboard/_template-choose.html:29 msgid "Next" msgstr "Tovább" @@ -610,15 +811,17 @@ msgid "Create a new lease now." msgstr "Hozzon létre egy bérlési módot." #: dashboard/templates/dashboard/_vm-create-1.html:14 +#: dashboard/templates/dashboard/node-list/column-monitor.html:4 msgid "CPU" msgstr "CPU" #: dashboard/templates/dashboard/_vm-create-1.html:23 +#: dashboard/templates/dashboard/node-list/column-monitor.html:20 msgid "Memory" msgstr "Memória" #: dashboard/templates/dashboard/_vm-create-1.html:33 -#: dashboard/templates/dashboard/vm-detail/resources.html:48 +#: dashboard/templates/dashboard/vm-detail/resources.html:57 msgid "Disks" msgstr "Lemezek" @@ -627,11 +830,6 @@ msgstr "Lemezek" msgid "Type" msgstr "Típus" -#: dashboard/templates/dashboard/_vm-create-1.html:53 -#: dashboard/templates/dashboard/vm-detail/home.html:30 -msgid "Description" -msgstr "Leírás" - #: dashboard/templates/dashboard/_vm-create-1.html:62 msgid "Customize" msgstr "Testreszabás" @@ -677,9 +875,34 @@ msgstr "" msgid "Name of template" msgstr "Sablon neve" -#: dashboard/templates/dashboard/group-detail.html:8 -#: dashboard/templates/dashboard/group-detail.html:17 -#: dashboard/templates/dashboard/group-detail.html:27 +#: dashboard/templates/dashboard/base.html:26 +#: dashboard/templates/dashboard/notifications.html:9 +msgid "Notifications" +msgstr "Értesítések" + +#: dashboard/templates/dashboard/base.html:32 +msgid "Loading..." +msgstr "Betöltés..." + +#: dashboard/templates/dashboard/base.html:38 +msgid "Log out" +msgstr "Kijelentkezés" + +#: dashboard/templates/dashboard/base.html:40 +msgid "User profile" +msgstr "Felhasználói profil" + +#: dashboard/templates/dashboard/base.html:47 +msgid "Admin" +msgstr "Adminisztráció" + +#: dashboard/templates/dashboard/base.html:50 +msgid "Log in " +msgstr "Bejelentkezés" + +#: dashboard/templates/dashboard/group-detail.html:9 +#: dashboard/templates/dashboard/group-detail.html:18 +#: dashboard/templates/dashboard/group-detail.html:31 #: dashboard/templates/dashboard/node-detail.html:10 #: dashboard/templates/dashboard/node-detail.html:22 #: dashboard/templates/dashboard/node-detail.html:32 @@ -693,8 +916,8 @@ msgstr "Sablon neve" msgid "Rename" msgstr "Átnevezés" -#: dashboard/templates/dashboard/group-detail.html:9 -#: dashboard/templates/dashboard/group-detail.html:31 +#: dashboard/templates/dashboard/group-detail.html:10 +#: dashboard/templates/dashboard/group-detail.html:35 #: dashboard/templates/dashboard/node-detail.html:14 #: dashboard/templates/dashboard/node-detail.html:48 #: dashboard/templates/dashboard/confirm/ajax-delete.html:18 @@ -702,76 +925,99 @@ msgstr "Átnevezés" #: dashboard/templates/dashboard/node-list/column-actions.html:9 #: dashboard/templates/dashboard/template-list/column-lease-actions.html:5 #: dashboard/templates/dashboard/template-list/column-template-actions.html:5 +#: dashboard/templates/dashboard/userkey-list/column-userkey-actions.html:5 msgid "Delete" msgstr "Törlés" -#: dashboard/templates/dashboard/group-detail.html:28 +#: dashboard/templates/dashboard/group-detail.html:32 msgid "Change the name of the group." msgstr "Válasszon csoportnevet." -#: dashboard/templates/dashboard/group-detail.html:32 +#: dashboard/templates/dashboard/group-detail.html:36 msgid "Delete group." msgstr "Csoport törlése." -#: dashboard/templates/dashboard/group-detail.html:42 +#: dashboard/templates/dashboard/group-detail.html:53 msgid "User list" msgstr "Felhasználók" -#: dashboard/templates/dashboard/group-detail.html:45 -#: dashboard/templates/dashboard/group-detail.html:60 -#: dashboard/templates/dashboard/template-edit.html:37 -#: dashboard/templates/dashboard/vm-detail/access.html:21 -msgid "Who" -msgstr "Ki" +#: dashboard/templates/dashboard/group-detail.html:55 +msgid "Create user" +msgstr "Új felhasználó" -#: dashboard/templates/dashboard/group-detail.html:48 -#: dashboard/templates/dashboard/group-detail.html:69 -#: dashboard/templates/dashboard/vm-detail/network.html:51 +#: dashboard/templates/dashboard/group-detail.html:72 +#: dashboard/templates/dashboard/group-detail.html:85 +#: dashboard/templates/dashboard/vm-detail/network.html:27 msgid "remove" msgstr "eltávolítás" -#: dashboard/templates/dashboard/group-detail.html:52 -#: dashboard/templates/dashboard/group-detail.html:73 -#: dashboard/templates/dashboard/template-edit.html:74 -#: dashboard/templates/dashboard/vm-detail/access.html:58 -msgid "Name of group or user" -msgstr "Csoport vagy felhasználó neve" +#: dashboard/templates/dashboard/group-detail.html:93 +msgid "Name of user" +msgstr "Felhasználó neve" -#: dashboard/templates/dashboard/group-detail.html:57 -#: dashboard/templates/dashboard/vm-detail/access.html:16 -msgid "Permissions" -msgstr "Jogosultságok" +#: dashboard/templates/dashboard/group-detail.html:99 +msgid "Add multiple users at once (one identifier per line)." +msgstr "Több felhasználó hozzáadása (egy azonosító soronként)." -#: dashboard/templates/dashboard/group-detail.html:60 -#: dashboard/templates/dashboard/template-edit.html:38 -#: dashboard/templates/dashboard/vm-detail/access.html:22 -msgid "What" -msgstr "Mi" +#: dashboard/templates/dashboard/group-detail.html:106 +msgid "Access permissions" +msgstr "Hozzáférési jogosultságok" -#: dashboard/templates/dashboard/group-detail.html:85 -#: dashboard/templates/dashboard/template-edit.html:84 -#: dashboard/templates/dashboard/vm-detail/access.html:68 -msgid "Save" -msgstr "Mentés" +#: dashboard/templates/dashboard/group-detail.html:115 +msgid "Group permissions" +msgstr "Csoportjogosultságok" #: dashboard/templates/dashboard/group-list.html:5 msgid "Group list" msgstr "Csoportok" +#: dashboard/templates/dashboard/groupprofile_form.html:10 +msgid "Update group" +msgstr "Csoport frissítése" + +#: dashboard/templates/dashboard/index-groups.html:5 +msgid "List of groups that you have access to." +msgstr "Azon csoportok, amelyekhez hozzáférése van." + #: dashboard/templates/dashboard/index-groups.html:7 -#: dashboard/templates/dashboard/vm-detail/network.html:61 +#: dashboard/templates/dashboard/profile.html:50 +#: dashboard/templates/dashboard/vm-detail/network.html:37 #: network/templates/network/host-edit.html:30 msgid "Groups" msgstr "Csoportok" -#: dashboard/templates/dashboard/index-groups.html:18 -#: dashboard/templates/dashboard/index-nodes.html:25 -#: dashboard/templates/dashboard/index-nodes.html:59 -#: dashboard/templates/dashboard/index-vm.html:38 +#: dashboard/templates/dashboard/index-groups.html:21 +#: dashboard/templates/dashboard/index-nodes.html:32 +#: dashboard/templates/dashboard/index-vm.html:48 #: dashboard/templates/dashboard/vm-list.html:20 msgid "Search..." msgstr "Keresés..." +#: dashboard/templates/dashboard/index-groups.html:30 +#, python-format +msgid "" +"\n" +" %(more)s more\n" +" " +msgid_plural "" +"\n" +" %(more)s more\n" +" " +msgstr[0] "" +"\n" +" még %(more)s\n" +" " +msgstr[1] "" +"\n" +" még %(more)s\n" +" " + +#: dashboard/templates/dashboard/index-groups.html:36 +#: dashboard/templates/dashboard/index-nodes.html:43 +#: dashboard/templates/dashboard/index-vm.html:63 +msgid "list" +msgstr "felsorolás" + #: dashboard/templates/dashboard/index-nodes.html:10 msgid "" "List of compute nodes, also called worker nodes or hypervisors, which run " @@ -785,23 +1031,31 @@ msgstr "" msgid "Nodes" msgstr "Csomópontok" -#: dashboard/templates/dashboard/index-nodes.html:67 -#: dashboard/templates/dashboard/index-vm.html:46 +#: dashboard/templates/dashboard/index-nodes.html:41 +#: dashboard/templates/dashboard/index-nodes.html:68 #, python-format msgid "%(count)s more" msgstr "még %(count)s" +#: dashboard/templates/dashboard/index-templates.html:4 +msgid "" +"List of VM templates that are available for you. You can create new ones " +"from scratch or customize existing ones (preferred)." +msgstr "" +"Az Ön számára elérhető VM-sablonok. Létrehozhat egy újat az alapokból, vagy " +"személyre szabhat egy meglévőt (ajánlott)." + #: dashboard/templates/dashboard/index-templates.html:7 #: dashboard/templates/dashboard/template-list.html:5 #: dashboard/templates/dashboard/template-list.html:16 msgid "Templates" msgstr "Sablonok" -#: dashboard/templates/dashboard/index-templates.html:17 +#: dashboard/templates/dashboard/index-templates.html:19 msgid "Start vm instance" msgstr "VM-példány indítása" -#: dashboard/templates/dashboard/index-templates.html:23 +#: dashboard/templates/dashboard/index-templates.html:26 msgid "" "You don't have any templates, however you can still start virtual machines " "and even save them as new templates!" @@ -809,30 +1063,78 @@ msgstr "" "Még nincs saját sablonja, de ettől függetlenül indíthat virtuális gépeket, " "amelyeket el is menthet sablonként." -#: dashboard/templates/dashboard/index-templates.html:30 +#: dashboard/templates/dashboard/index-templates.html:35 msgid "show all" msgstr "összes" +#: dashboard/templates/dashboard/index-vm.html:9 +msgid "" +"List of your current virtual machines. Favourited ones are ahead of others." +msgstr "A meglévő virtuális gépei. A megjelöltek megelőzik a többit." + #: dashboard/templates/dashboard/index-vm.html:12 #: dashboard/templates/dashboard/vm-list.html:4 #: dashboard/templates/dashboard/vm-list.html:16 msgid "Virtual machines" msgstr "Virtuális gépek" -#: dashboard/templates/dashboard/index-vm.html:26 +#: dashboard/templates/dashboard/index-vm.html:27 msgid "Unfavourite" msgstr "Kedvencnek jelölés törlése" -#: dashboard/templates/dashboard/index-vm.html:28 +#: dashboard/templates/dashboard/index-vm.html:29 msgid "Mark as favorite" msgstr "Kedvencnek jelölés" -#: dashboard/templates/dashboard/index-vm.html:56 +#: dashboard/templates/dashboard/index-vm.html:36 +#: dashboard/templates/dashboard/vm-list.html:78 +msgid "You have no virtual machines." +msgstr "Még nincs virtuális gépe." + +#: dashboard/templates/dashboard/index-vm.html:57 +#, python-format +msgid "" +"\n" +" %(counter)s more\n" +" " +msgid_plural "" +"\n" +" %(counter)s more\n" +" " +msgstr[0] "" +"\n" +" még %(counter)s\n" +" " +msgstr[1] "" +"\n" +" még %(counter)s\n" +" " + +#: dashboard/templates/dashboard/index-vm.html:75 #, python-format msgid "%(count)s running" msgstr "%(count)s fut" -#: dashboard/templates/dashboard/index-vm.html:74 +#: dashboard/templates/dashboard/index-vm.html:91 +#, python-format +msgid "" +"\n" +" %(counter)s machine total\n" +" " +msgid_plural "" +"\n" +" %(counter)s machines total\n" +" " +msgstr[0] "" +"\n" +" összesen %(counter)s gép\n" +" " +msgstr[1] "" +"\n" +" összesen %(counter)s gép\n" +" " + +#: dashboard/templates/dashboard/index-vm.html:97 #, python-format msgid "%(count)s stopped" msgstr "%(count)s leállítva" @@ -845,29 +1147,29 @@ msgstr "Kezdőoldal" msgid "You have no permission to start or manage virtual machines." msgstr "Nincs jogosultsága virtuális gépek indítására vagy kezelésére." -#: dashboard/templates/dashboard/instanceactivity_detail.html:24 +#: dashboard/templates/dashboard/instanceactivity_detail.html:29 #: dashboard/templates/dashboard/node-detail.html:82 -#: dashboard/templates/dashboard/vm-detail.html:127 +#: dashboard/templates/dashboard/vm-detail.html:150 #: dashboard/templates/dashboard/node-detail/activity.html:3 #: dashboard/templates/dashboard/vm-detail/activity.html:3 msgid "Activity" msgstr "Tevékenységek" -#: dashboard/templates/dashboard/instanceactivity_detail.html:30 -#: vm/models/activity.py:56 vm/models/instance.py:260 vm/models/network.py:67 +#: dashboard/templates/dashboard/instanceactivity_detail.html:35 +#: vm/models/activity.py:71 vm/models/instance.py:276 vm/models/network.py:68 msgid "instance" msgstr "példány" -#: dashboard/templates/dashboard/instanceactivity_detail.html:33 +#: dashboard/templates/dashboard/instanceactivity_detail.html:38 msgid "time" msgstr "idő" -#: dashboard/templates/dashboard/instanceactivity_detail.html:39 -#: firewall/models.py:830 firewall/models.py:956 +#: dashboard/templates/dashboard/instanceactivity_detail.html:44 +#: firewall/models.py:850 firewall/models.py:976 msgid "type" msgstr "típus" -#: dashboard/templates/dashboard/instanceactivity_detail.html:42 +#: dashboard/templates/dashboard/instanceactivity_detail.html:47 #, python-format msgid "" "\n" @@ -878,19 +1180,19 @@ msgstr "" " %(name)s résztevékenysége\n" " " -#: dashboard/templates/dashboard/instanceactivity_detail.html:45 +#: dashboard/templates/dashboard/instanceactivity_detail.html:50 msgid "top level activity" msgstr "felső szintű tevékenység" -#: dashboard/templates/dashboard/instanceactivity_detail.html:48 +#: dashboard/templates/dashboard/instanceactivity_detail.html:53 msgid "task uuid" msgstr "feladat uuid" -#: dashboard/templates/dashboard/instanceactivity_detail.html:51 +#: dashboard/templates/dashboard/instanceactivity_detail.html:56 msgid "status" msgstr "állapot" -#: dashboard/templates/dashboard/instanceactivity_detail.html:58 +#: dashboard/templates/dashboard/instanceactivity_detail.html:63 msgid "resultant state" msgstr "új állapot" @@ -901,8 +1203,10 @@ msgstr "Bérlési mód létrehozása" #: dashboard/templates/dashboard/lease-create.html:13 #: dashboard/templates/dashboard/lease-edit.html:12 -#: dashboard/templates/dashboard/profile_form.html:13 +#: dashboard/templates/dashboard/profile.html:13 #: dashboard/templates/dashboard/template-edit.html:14 +#: dashboard/templates/dashboard/userkey-create.html:13 +#: dashboard/templates/dashboard/userkey-edit.html:14 #: dashboard/templates/dashboard/confirm/base-renew.html:26 #: dashboard/templates/dashboard/confirm/node-flush.html:26 msgid "Back" @@ -913,6 +1217,12 @@ msgstr "Vissza" msgid "Edit lease" msgstr "Bérlési mód szerkesztése" +#: dashboard/templates/dashboard/lease-edit.html:27 +#: dashboard/templates/dashboard/template-edit.html:29 +#: network/templates/network/vlan-edit.html:24 +msgid "Manage access" +msgstr "Jogosultságok kezelése" + #: dashboard/templates/dashboard/node-add-trait.html:19 msgid "Add Trait" msgstr "Jellemző hozzáadása" @@ -957,7 +1267,7 @@ msgid "Remove node and it's host." msgstr "Csomópont és hoszt törlése." #: dashboard/templates/dashboard/node-detail.html:70 -#: dashboard/templates/dashboard/vm-detail.html:103 +#: dashboard/templates/dashboard/vm-detail.html:126 msgid "Home" msgstr "Kezdőoldal" @@ -969,72 +1279,142 @@ msgstr "Virtuális gépek" msgid "Compute nodes" msgstr "Számítási csomópontok" -#: dashboard/templates/dashboard/operate.html:5 +#: dashboard/templates/dashboard/operate.html:6 #, python-format msgid "" "\n" -"Do you want to do the following operation on %(obj)s:\n" +"Do you want to do the following operation on %(obj)s:\n" "%(op)s?\n" msgstr "" "\n" "Biztosan végrehajtja a(z) %(op)s műveletet\n" -"a következőn: %(obj)s?\n" +"a következőn: %(obj)s?\n" -#: dashboard/templates/dashboard/operate.html:16 +#: dashboard/templates/dashboard/operate.html:21 #: dashboard/templates/dashboard/confirm/ajax-delete.html:15 #: dashboard/templates/dashboard/confirm/ajax-node-flush.html:17 #: dashboard/templates/dashboard/confirm/ajax-node-status.html:17 #: dashboard/templates/dashboard/confirm/base-delete.html:27 #: dashboard/templates/dashboard/confirm/mass-delete.html:11 #: dashboard/templates/dashboard/confirm/node-status.html:27 +#: dashboard/templates/dashboard/store/remove.html:34 msgid "Cancel" msgstr "Mégsem" -#: dashboard/templates/dashboard/profile_form.html:5 +#: dashboard/templates/dashboard/profile.html:5 +#: dashboard/templates/dashboard/profile_form.html:6 msgid "Profile" msgstr "Profil" -#: dashboard/templates/dashboard/profile_form.html:14 +#: dashboard/templates/dashboard/profile.html:26 +msgid "Organization ID" +msgstr "Címtári azonosító" + +#: dashboard/templates/dashboard/profile.html:27 +msgid "First name" +msgstr "Keresztnév" + +#: dashboard/templates/dashboard/profile.html:28 +msgid "Last name" +msgstr "Vezetéknév" + +#: dashboard/templates/dashboard/profile.html:36 +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:39 +msgid "What's Gravatar?" +msgstr "Mi az a Gravatar?" + +#: dashboard/templates/dashboard/profile.html:41 +msgid "Change my preferences" +msgstr "Személyes beállítások" + +#: dashboard/templates/dashboard/profile.html:56 +msgid "This user is not in any group." +msgstr "A felhasználó nem tagja csoportnak." + +#: dashboard/templates/dashboard/profile.html:65 +msgid "Virtual machines owned by the user" +msgstr "A felhasználó virtuális gépei" + +#: dashboard/templates/dashboard/profile.html:77 +msgid "This user have no virtual machines." +msgstr "A felhasználónak nincs virtuális gépe." + +#: dashboard/templates/dashboard/profile.html:86 +msgid "Virtual machines with access" +msgstr "Elérhető virtuális gépek" + +#: dashboard/templates/dashboard/profile.html:98 +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_form.html:16 +#: dashboard/templates/dashboard/profile_form.html:42 +msgid "Go to my profile" +msgstr "Ugrás a profilra" + +#: dashboard/templates/dashboard/profile_form.html:17 msgid "My profile" msgstr "Saját profil" -#: dashboard/templates/dashboard/profile_form.html:20 +#: dashboard/templates/dashboard/profile_form.html:23 msgid "Password change" msgstr "Jelszóváltoztatás" -#: dashboard/templates/dashboard/profile_form.html:26 -msgid "Language selection" -msgstr "Nyelvválasztás" +#: dashboard/templates/dashboard/profile_form.html:29 +msgid "My preferences" +msgstr "Személyes beállítások" + +#: dashboard/templates/dashboard/profile_form.html:35 +msgid "Current avatar" +msgstr "Jelenlegi avatar" + +#: dashboard/templates/dashboard/profile_form.html:58 +msgid "add SSH key" +msgstr "SSH kulcs hozzáadása" + +#: dashboard/templates/dashboard/profile_form.html:60 +msgid "SSH public keys" +msgstr "SSH publikus kulcsok" #: dashboard/templates/dashboard/template-edit.html:6 #: dashboard/templates/dashboard/template-edit.html:15 msgid "Edit template" msgstr "Sablon szerkesztése" -#: dashboard/templates/dashboard/template-edit.html:29 -msgid "Manage access" -msgstr "Jogosultságok kezelése" - -#: dashboard/templates/dashboard/template-edit.html:93 +#: dashboard/templates/dashboard/template-edit.html:39 msgid "Disk list" msgstr "Lemezek" -#: dashboard/templates/dashboard/template-edit.html:111 -msgid "Create new disk" -msgstr "Új lemez létrehozása" - #: dashboard/templates/dashboard/template-list.html:14 -msgid "new base vm" -msgstr "alap VM létrehozása" +msgid "new template" +msgstr "új sablon" -#: dashboard/templates/dashboard/template-list.html:30 +#: dashboard/templates/dashboard/template-list.html:31 msgid "new lease" msgstr "bérlési mód létrehozása" -#: dashboard/templates/dashboard/template-list.html:32 +#: dashboard/templates/dashboard/template-list.html:34 msgid "Leases" msgstr "Bérlési módok" +#: dashboard/templates/dashboard/unsubscribe.html:10 +msgid "Subscription settings" +msgstr "Feliratkozások" + +#: dashboard/templates/dashboard/userkey-create.html:5 +#: dashboard/templates/dashboard/userkey-create.html:14 +msgid "Create SSH public key" +msgstr "SSH publikus kulcs létrehozása" + +#: dashboard/templates/dashboard/userkey-edit.html:6 +#: dashboard/templates/dashboard/userkey-edit.html:15 +msgid "Edit SSH public key" +msgstr "SSH publikus kulcs módosítása" + #: dashboard/templates/dashboard/vm-detail.html:10 msgid "This is the master vm of your new template" msgstr "Ez a mesterpéldány egy új sablonhoz" @@ -1089,35 +1469,47 @@ msgid "Delete this virtual machine (optional)" msgstr "Törölje a virtális gépet (ha szükséges)" #: dashboard/templates/dashboard/vm-detail.html:70 -msgid "Connection" -msgstr "Kapcsolat" +msgid "Connection details" +msgstr "Kapcsolat részletei" -#: dashboard/templates/dashboard/vm-detail.html:83 -msgid "Generate new password!" -msgstr "Új jelszó generálása" +#: dashboard/templates/dashboard/vm-detail.html:72 +msgid "Protocol" +msgstr "Protokoll" -#: dashboard/templates/dashboard/vm-detail.html:88 -msgid "Are you sure?" -msgstr "Biztos benne?" +#: dashboard/templates/dashboard/vm-detail.html:79 +msgid "The required port for this protocol is not forwarded." +msgstr "A csatlakozáshoz szükséges port nincs továbbítva." -#: dashboard/templates/dashboard/vm-detail.html:91 -#: dashboard/templates/dashboard/confirm/ajax-node-flush.html:19 -#: dashboard/templates/dashboard/confirm/base-delete.html:31 -#: dashboard/templates/dashboard/confirm/base-transfer-ownership.html:22 -#: dashboard/templates/dashboard/confirm/node-flush.html:28 -msgid "Yes" -msgstr "Igen" +#: dashboard/templates/dashboard/vm-detail.html:84 +msgid "Host (IPv6)" +msgstr "Gép (IPv6)" -#: dashboard/templates/dashboard/vm-detail.html:92 -#: dashboard/templates/dashboard/confirm/base-transfer-ownership.html:20 -msgid "No" -msgstr "Nem" +#: dashboard/templates/dashboard/vm-detail.html:103 +msgid "Start the VM to change the password." +msgstr "Jelszóváltoztatáshoz el kell indítani a gépet." + +#: dashboard/templates/dashboard/vm-detail.html:103 +msgid "Generate new password!" +msgstr "Új jelszó generálása" + +#: dashboard/templates/dashboard/vm-detail.html:110 +msgid "Command" +msgstr "Parancs" #: dashboard/templates/dashboard/vm-detail.html:113 +msgid "Connection is not possible." +msgstr "A csatlakozás nem lehetséges." + +#: dashboard/templates/dashboard/vm-detail.html:116 +#: dashboard/templates/dashboard/vm-list.html:29 +msgid "Select all" +msgstr "Összes kiválasztása" + +#: dashboard/templates/dashboard/vm-detail.html:136 msgid "Console" msgstr "Konzol" -#: dashboard/templates/dashboard/vm-detail.html:117 +#: dashboard/templates/dashboard/vm-detail.html:140 msgid "Access" msgstr "Hozzáférés" @@ -1129,10 +1521,6 @@ msgstr "Rendezés…" msgid "Group actions" msgstr "Csoportos műveletek" -#: dashboard/templates/dashboard/vm-list.html:29 -msgid "Select all" -msgstr "Összes kiválasztása" - #: dashboard/templates/dashboard/vm-list.html:30 msgid "Migrate" msgstr "Migrálás" @@ -1162,11 +1550,11 @@ msgstr "Állapot" msgid "Owner" msgstr "Tulajdonos" -#: dashboard/templates/dashboard/vm-list.html:71 -msgid "You have no virtual machines." -msgstr "Még nincs virtuális gépe." +#: dashboard/templates/dashboard/vm-list.html:76 +msgid "No result." +msgstr "Nincs eredmény." -#: dashboard/templates/dashboard/vm-list.html:82 +#: dashboard/templates/dashboard/vm-list.html:92 msgid "" "You can select multiple vm instances while holding down the CTRL key." @@ -1174,7 +1562,7 @@ msgstr "" "Több virtuális gépet is kiválaszthat a CTRL billentyű " "lenyomásával." -#: dashboard/templates/dashboard/vm-list.html:83 +#: dashboard/templates/dashboard/vm-list.html:93 msgid "" "If you want to select multiple instances by one click select an instance " "then hold down SHIFT key and select another one!" @@ -1205,6 +1593,13 @@ msgstr "" " Biztosan üríti a következőt: %(object)s?\n" " " +#: dashboard/templates/dashboard/confirm/ajax-node-flush.html:19 +#: dashboard/templates/dashboard/confirm/base-delete.html:31 +#: dashboard/templates/dashboard/confirm/base-transfer-ownership.html:22 +#: dashboard/templates/dashboard/confirm/node-flush.html:28 +msgid "Yes" +msgstr "Igen" + #: dashboard/templates/dashboard/confirm/ajax-node-status.html:10 #: dashboard/templates/dashboard/confirm/node-status.html:20 #, python-format @@ -1225,6 +1620,19 @@ msgstr "" msgid "Yes, %(status)s" msgstr "Igen, %(status)s" +#: dashboard/templates/dashboard/confirm/ajax-remove.html:9 +#, python-format +msgid "" +"\n" +"\t Are you sure you want to remove %(member)s from " +"%(object)s?\n" +" " +msgstr "" +"\n" +"\t Biztosan eltávolítja a(z) %(member)s tagot a " +"következőből: %(object)s?\n" +" " + #: dashboard/templates/dashboard/confirm/base-delete.html:12 msgid "Delete confirmation" msgstr "Törlés megerősítése" @@ -1240,6 +1648,15 @@ msgstr "" " Biztosan törli a következőt: %(object)s?\n" " " +#: dashboard/templates/dashboard/confirm/base-remove.html:7 +#, python-format +msgid "" +"\n" +"Do you really want to remove %(member)s from %(group)s?\n" +msgstr "" +"\n" +"Biztosan törli %(member)s tagot a(z) %(group)s csoportból?\n" + #: dashboard/templates/dashboard/confirm/base-renew.html:9 #, python-format msgid "" @@ -1298,6 +1715,10 @@ msgstr "" " Elfogadja a gép birtoklásával járó felelősséget?\n" " " +#: dashboard/templates/dashboard/confirm/base-transfer-ownership.html:20 +msgid "No" +msgstr "Nem" + #: dashboard/templates/dashboard/confirm/mass-delete.html:6 msgid "Are you sure you want to delete the following objects?" msgstr "Biztosan törli a következő objektumokat?" @@ -1327,7 +1748,7 @@ msgid "CPU cores" msgstr "CPU-magok" #: dashboard/templates/dashboard/node-detail/resources.html:7 -#: vm/models/common.py:39 +#: vm/models/common.py:41 msgid "RAM size" msgstr "RAM-méret" @@ -1351,92 +1772,223 @@ msgstr "Gép elérhető" msgid "Host owner" msgstr "Gép tulajdonosa" -#: dashboard/templates/dashboard/node-detail/resources.html:14 -msgid "Vlan" -msgstr "Vlan" - #: dashboard/templates/dashboard/node-detail/resources.html:15 msgid "Host name" msgstr "Gépnév" -#: dashboard/templates/dashboard/notifications/ownership-accepted.html:2 +#: dashboard/templates/dashboard/notifications/email.txt:2 #, python-format -msgid "" -"\n" -"Your ownership offer of %(instance)s has been accepted by %(user)s.\n" -msgstr "" -"\n" -"%(instance)s gépre vonatkozó átruházási ajánlatát elfogadta %(user)s.\n" +msgid "Dear %(u)s," +msgstr "Kedves %(u)s!" -#: dashboard/templates/dashboard/notifications/ownership-offer.html:2 +#: dashboard/templates/dashboard/notifications/email.txt:5 #, python-format -msgid "" -"\n" -"%(user)s offered you to take the ownership of his/her virtual machine\n" -"called %(instance)s." -msgstr "" -"\n" -"%(user)s át kívánja ruházni %(instance)s nevű virtuális gépét Önre." +msgid "You have a new notification:" +msgid_plural "You have %(n)s new notifications:" +msgstr[0] "Egy új értesítése van:" +msgstr[1] "%(n)s új értesítése van:" -#: dashboard/templates/dashboard/notifications/ownership-offer.html:5 -msgid "Accept" -msgstr "Elfogadás" +#: dashboard/templates/dashboard/notifications/email.txt:10 +#, python-format +msgid "See it in detail on <%(url)s>." +msgid_plural " See them in detail on <%(url)s>." +msgstr[0] "Részletek: <%(url)s>." +msgstr[1] "Részletek: <%(url)s>." + +#: dashboard/templates/dashboard/notifications/email.txt:15 +msgid "You can change your subscription without logging in:" +msgstr "Bejelentkezés nélkül módosíthatja feliratkozásait:" + +#: dashboard/templates/dashboard/store/_list-box.html:9 +#: dashboard/templates/dashboard/store/_list-box.html:27 +#: dashboard/templates/dashboard/store/upload.html:4 +#: dashboard/templates/dashboard/store/upload.html:29 +msgid "Upload" +msgstr "Feltöltés" + +#: dashboard/templates/dashboard/store/_list-box.html:20 +msgid "Browse..." +msgstr "Tallózás..." + +#: dashboard/templates/dashboard/store/_list-box.html:38 +msgid "Remove directory" +msgstr "Könyvtár törlése" + +#: dashboard/templates/dashboard/store/_list-box.html:43 +msgid "Download directory" +msgstr "Könyvtár letöltése" + +#: dashboard/templates/dashboard/store/_list-box.html:51 +msgid "New directory" +msgstr "Új könyvtár" + +#: dashboard/templates/dashboard/store/_list-box.html:55 +msgid "Name " +msgstr "Név " + +#: dashboard/templates/dashboard/store/_list-box.html:73 +#: dashboard/templates/dashboard/store/index-files.html:51 +msgid "Refresh" +msgstr "Frissítés" + +#: dashboard/templates/dashboard/store/_list-box.html:114 +msgid "Filename" +msgstr "Fájlnév" + +#: dashboard/templates/dashboard/store/_list-box.html:120 +msgid "Latest modification" +msgstr "Legutóbbi változtatás" + +#: dashboard/templates/dashboard/store/_list-box.html:128 +#: dashboard/templates/dashboard/store/index-files.html:29 +msgid "Download" +msgstr "Letöltés" -#: dashboard/templates/dashboard/notifications/vm-destroyed.html:2 +#: dashboard/templates/dashboard/store/_list-box.html:140 +msgid "This folder is empty." +msgstr "A mappa üres." + +#: dashboard/templates/dashboard/store/index-files.html:6 +msgid "A list of your most recent files." +msgstr "A legújabb fájlok listája." + +#: dashboard/templates/dashboard/store/index-files.html:11 #, python-format msgid "" "\n" -"Your instance %(instance)s has been destroyed due to " -"expiration.\n" +" You are currently using %(used)s, your soft limit is %(soft)s, your " +"hard limit is %(hard)s.\n" +" " msgstr "" "\n" -"%(instance)s gépe megsemmisítésre került, mivel " -"lejárt.\n" +" Jelenlegi használat: %(used)s, puha korlát: %(soft)s, kemény korlát: %" +"(hard)s.\n" +" " + +#: dashboard/templates/dashboard/store/index-files.html:16 +msgid "Files" +msgstr "Fájlok" + +#: dashboard/templates/dashboard/store/index-files.html:33 +msgid "Show in directory" +msgstr "Megjelenítés könyvtában" + +#: dashboard/templates/dashboard/store/index-files.html:56 +msgid "show my files" +msgstr "fájlok megjelenítése" -#: dashboard/templates/dashboard/notifications/vm-expiring.html:2 +#: dashboard/templates/dashboard/store/index-files.html:59 +msgid "upload" +msgstr "feltöltés" + +#: dashboard/templates/dashboard/store/list.html:4 +#: network/templates/network/dashboard.html:24 +#: network/templates/network/dashboard.html:40 +#: network/templates/network/dashboard.html:56 +#: network/templates/network/dashboard.html:72 +#: network/templates/network/dashboard.html:88 +#: network/templates/network/dashboard.html:103 +#: network/templates/network/dashboard.html:119 +#: network/templates/network/dashboard.html:135 +msgid "List" +msgstr "Felsorolás" + +#: dashboard/templates/dashboard/store/list.html:4 +#: dashboard/templates/dashboard/store/upload.html:4 +msgid "Store" +msgstr "Tárhely" + +#: dashboard/templates/dashboard/store/list.html:21 #, python-format msgid "" "\n" -"Your instance %(instance)s is going to expire.\n" -"It will be suspended at %(suspend)s and destroyed at %(delete)s.\n" +" %(used)s used\n" +" " msgstr "" "\n" -"%(instance)s gépe hamarosan lejár:\n" -"%(suspend)s időpontban felfüggesztésre, %(delete)s időpontban törlésre " -"kerül.\n" +" %(used)s használatban\n" +" " -#: dashboard/templates/dashboard/notifications/vm-expiring.html:7 +#: dashboard/templates/dashboard/store/list.html:28 +msgid "Hard limit" +msgstr "Kemény korlát" + +#: dashboard/templates/dashboard/store/list.html:32 +msgid "Soft limit" +msgstr "Puha korlát" + +#: dashboard/templates/dashboard/store/remove.html:11 +msgid "Directory removal confirmation" +msgstr "Könyvtár törlésének megerősítése" + +#: dashboard/templates/dashboard/store/remove.html:13 +msgid "File removal confirmation" +msgstr "Fájl törlésének megerősítése" + +#: dashboard/templates/dashboard/store/remove.html:19 +msgid "File directory" +msgstr "Fájl helye" + +#: dashboard/templates/dashboard/store/remove.html:20 +msgid "File name" +msgstr "Fájlnév" + +#: dashboard/templates/dashboard/store/remove.html:22 #, python-format msgid "" "\n" -"Please, either renew or destroy\n" -"it now.\n" +" Are you sure you want to remove the file at %(path)s?\n" +" " msgstr "" "\n" -"Újítsa meg vagy törölje\n" -"most.\n" +" Biztosan törli a következőt fájlt: %(path)s " +"?\n" +" " -#: dashboard/templates/dashboard/notifications/vm-suspended.html:2 +#: dashboard/templates/dashboard/store/remove.html:26 #, python-format msgid "" "\n" -"Your instance %(instance)s has been suspended due to " -"expiration.\n" +" Are you sure you want to remove the directory " +"%(directory)s?\n" +" " msgstr "" "\n" -"%(instance)s gépe felfüggesztésre került, mivel " -"lejárt.\n" +" Biztosan törli a következő könyvtárat: " +"%(directory)s?\n" +" " + +#: dashboard/templates/dashboard/store/upload.html:16 +msgid "File upload" +msgstr "Fájlfeltöltés" + +#: dashboard/templates/dashboard/store/upload.html:22 +msgid "Curently uploading to" +msgstr "Feltöltés helye:" #: dashboard/templates/dashboard/template-list/column-lease-actions.html:2 #: dashboard/templates/dashboard/template-list/column-template-actions.html:2 +#: dashboard/templates/dashboard/userkey-list/column-userkey-actions.html:2 msgid "Edit" msgstr "Szerkesztés" -#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:17 +#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:29 msgid "Abort" msgstr "Megszakítás" +#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:59 +msgid "Show less activities" +msgstr "Kevesebb tevékenység megjelenítése" + +#: dashboard/templates/dashboard/vm-detail/_activity-timeline.html:61 +msgid "Show all activities" +msgstr "Összes tevékenység megjelenítése" + +#: dashboard/templates/dashboard/vm-detail/_network-port-add.html:14 +msgid "Add" +msgstr "Hozzáadás" + #: dashboard/templates/dashboard/vm-detail/access.html:5 msgid "You are the current owner of this instance." msgstr "Ön a jelenlegi tulajdonosa a példánynak." @@ -1456,14 +2008,27 @@ msgstr "" msgid "Transfer ownership..." msgstr "Tulajdon átruházása..." -#: dashboard/templates/dashboard/vm-detail/console.html:3 +#: dashboard/templates/dashboard/vm-detail/access.html:16 +msgid "Permissions" +msgstr "Jogosultságok" + +#: dashboard/templates/dashboard/vm-detail/console.html:4 msgid "Send Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del küldése" -#: dashboard/templates/dashboard/vm-detail/console.html:4 +#: dashboard/templates/dashboard/vm-detail/console.html:5 msgid "Type password" msgstr "Jelszó begépelése" +#: dashboard/templates/dashboard/vm-detail/console.html:7 +#: dashboard/templates/dashboard/vm-detail/console.html:16 +msgid "Screenshot" +msgstr "Képernyőkép" + +#: dashboard/templates/dashboard/vm-detail/console.html:15 +msgid "Close" +msgstr "Bezárás" + #: dashboard/templates/dashboard/vm-detail/home.html:5 msgid "System" msgstr "Rendszer" @@ -1476,76 +2041,77 @@ msgstr "Frissítés" msgid "Expiration" msgstr "Lejárat" -#: dashboard/templates/dashboard/vm-detail/home.html:50 -msgid "renew" -msgstr "megújítás" - -#: dashboard/templates/dashboard/vm-detail/home.html:53 +#: dashboard/templates/dashboard/vm-detail/home.html:60 msgid "Suspended at:" msgstr "Felfüggesztve:" -#: dashboard/templates/dashboard/vm-detail/home.html:55 +#: dashboard/templates/dashboard/vm-detail/home.html:62 msgid "Destroyed at:" msgstr "Megsemmisítve:" -#: dashboard/templates/dashboard/vm-detail/home.html:59 +#: dashboard/templates/dashboard/vm-detail/home.html:66 msgid "Tags" msgstr "Címkék" -#: dashboard/templates/dashboard/vm-detail/home.html:70 +#: dashboard/templates/dashboard/vm-detail/home.html:77 msgid "No tag added!" msgstr "Nincs címke." -#: dashboard/templates/dashboard/vm-detail/home.html:81 +#: dashboard/templates/dashboard/vm-detail/home.html:88 msgid "Add tag" msgstr "Címke hozzáadása" -#: dashboard/templates/dashboard/vm-detail/network.html:4 vm/operations.py:82 +#: dashboard/templates/dashboard/vm-detail/network.html:8 vm/operations.py:112 msgid "add interface" msgstr "új interfész" -#: dashboard/templates/dashboard/vm-detail/network.html:6 +#: dashboard/templates/dashboard/vm-detail/network.html:11 msgid "Interfaces" msgstr "Interfészek" -#: dashboard/templates/dashboard/vm-detail/network.html:14 -msgid "Add new network interface!" -msgstr "Új hálózati interfész hozzáadása." - -#: dashboard/templates/dashboard/vm-detail/network.html:48 +#: dashboard/templates/dashboard/vm-detail/network.html:19 msgid "unmanaged" msgstr "nem menedzselt" -#: dashboard/templates/dashboard/vm-detail/network.html:58 -#: firewall/models.py:482 +#: dashboard/templates/dashboard/vm-detail/network.html:22 +msgid "edit" +msgstr "szerkesztés" + +#: dashboard/templates/dashboard/vm-detail/network.html:34 +#: firewall/models.py:491 msgid "IPv4 address" msgstr "IPv4 cím" -#: dashboard/templates/dashboard/vm-detail/network.html:59 -#: firewall/models.py:492 +#: dashboard/templates/dashboard/vm-detail/network.html:35 +#: firewall/models.py:501 msgid "IPv6 address" msgstr "IPv6 cím" -#: dashboard/templates/dashboard/vm-detail/network.html:60 +#: dashboard/templates/dashboard/vm-detail/network.html:36 msgid "DNS name" msgstr "DNS név" -#: dashboard/templates/dashboard/vm-detail/network.html:71 +#: dashboard/templates/dashboard/vm-detail/network.html:49 msgid "IPv4" msgstr "IPv4" -#: dashboard/templates/dashboard/vm-detail/network.html:72 +#: dashboard/templates/dashboard/vm-detail/network.html:50 msgid "IPv6" msgstr "IPv6" -#: dashboard/templates/dashboard/vm-detail/network.html:74 +#: dashboard/templates/dashboard/vm-detail/network.html:52 msgid "Port access" msgstr "Portok elérése" -#: dashboard/templates/dashboard/vm-detail/network.html:141 +#: dashboard/templates/dashboard/vm-detail/network.html:119 msgid "This VM doesn't have an IPv6 address!" msgstr "A VM-nek nincs IPv6 címe." +#: dashboard/templates/dashboard/vm-detail/raw_data.html:5 +#: dashboard/templates/dashboard/vm-detail/raw_data.html:12 +msgid "Edit raw data" +msgstr "Nyers adat szerkesztése" + #: dashboard/templates/dashboard/vm-detail/resources.html:9 msgid "CPU priority" msgstr "CPU prioritás" @@ -1558,13 +2124,21 @@ msgstr "CPU-k száma" msgid "RAM amount" msgstr "RAM mennyiség" -#: dashboard/templates/dashboard/vm-detail/resources.html:38 +#: dashboard/templates/dashboard/vm-detail/resources.html:42 msgid "Save resources" msgstr "Erőforrások mentése" -#: dashboard/templates/dashboard/vm-detail/resources.html:51 -msgid "Add new disk" -msgstr "Lemez hozzáadása" +#: dashboard/templates/dashboard/vm-detail/resources.html:46 +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:86 +msgid "Required traits" +msgstr "Elvárt jellemzők" + +#: dashboard/templates/dashboard/vm-detail/resources.html:98 +msgid "Raw data" +msgstr "Nyers adat" #: dashboard/templates/dashboard/vm-detail/tx-owner.html:9 msgid "Transfer ownership" @@ -1575,254 +2149,259 @@ msgid "E-mail address or identifier of user" msgstr "A felhasználó e-mail címe vagy azonosítója" #: firewall/fields.py:39 -msgid "Enter a valid MAC address." -msgstr "Érvénytelen MAC cím." +#, python-format +msgid "Enter a valid MAC address. %s" +msgstr "Érvénytelen MAC cím. %s" + +#: firewall/fields.py:51 +msgid "MAC Address object" +msgstr "MAC cím objektum" -#: firewall/fields.py:71 +#: firewall/fields.py:90 #, python-format msgid "Enter a valid IP address. %s" msgstr "Érvénytelen IP cím. %s" -#: firewall/fields.py:88 firewall/fields.py:158 +#: firewall/fields.py:107 firewall/fields.py:165 msgid "IP Network object" msgstr "IP hálózat objektum" -#: firewall/fields.py:141 +#: firewall/fields.py:148 #, python-format msgid "Enter a valid IP network. %s" msgstr "Érvénytelen IP hálózat. %s" -#: firewall/fields.py:215 +#: firewall/fields.py:211 #, python-format msgid "%s - only letters, numbers, underscores and hyphens are allowed!" msgstr "%s – csak betűk, számok, alulvonások és kötőjelek." -#: firewall/fields.py:227 +#: firewall/fields.py:228 firewall/fields.py:234 #, python-format msgid "%s - invalid domain name" msgstr "%s – érvénytelen tartománynév" -#: firewall/fields.py:260 +#: firewall/fields.py:267 #, python-format msgid "%s - not an IPv4 address" msgstr "%s – nem egy IPv4 cím" -#: firewall/fields.py:266 +#: firewall/fields.py:273 #, python-format msgid "%s - not an IPv6 address" msgstr "%s – nem egy IPv6 cím" -#: firewall/fields.py:277 +#: firewall/fields.py:284 msgid "Bad MX address format. Should be: :" msgstr "Érvénytelen MX címformátum. Elvárt formátum: :" -#: firewall/models.py:57 +#: firewall/models.py:60 msgid "out" msgstr "ki" -#: firewall/models.py:57 +#: firewall/models.py:60 msgid "in" msgstr "be" -#: firewall/models.py:58 +#: firewall/models.py:61 msgid "accept" msgstr "elfogadás" -#: firewall/models.py:58 +#: firewall/models.py:61 msgid "drop" msgstr "eldobás" -#: firewall/models.py:59 +#: firewall/models.py:62 msgid "ignore" msgstr "ignorálás" -#: firewall/models.py:62 +#: firewall/models.py:65 msgid "direction" msgstr "irány" -#: firewall/models.py:63 +#: firewall/models.py:66 msgid "If the rule matches egress or ingress packets." msgstr "A szabály kimenő vagy bejövő csomagokra illeszkedik." -#: firewall/models.py:65 firewall/models.py:334 firewall/models.py:419 -#: firewall/models.py:442 firewall/models.py:499 firewall/models.py:811 -#: firewall/models.py:835 firewall/models.py:905 vm/models/instance.py:136 -#: vm/models/instance.py:213 +#: firewall/models.py:68 firewall/models.py:343 firewall/models.py:428 +#: firewall/models.py:451 firewall/models.py:508 firewall/models.py:831 +#: firewall/models.py:855 firewall/models.py:925 vm/models/instance.py:139 +#: vm/models/instance.py:226 msgid "description" msgstr "leírás" -#: firewall/models.py:66 +#: firewall/models.py:69 msgid "Why is the rule needed, or how does it work." msgstr "Miért szükséges a szabály, vagy hogyan működik." -#: firewall/models.py:69 +#: firewall/models.py:72 msgid "foreign network" msgstr "idegen hálózat" -#: firewall/models.py:70 +#: firewall/models.py:73 msgid "" "The group of vlans the matching packet goes to (direction out) or from (in)." msgstr "" "A vlanok azon csoportja, amelybe a csomag megy (ki irány) vagy emelyből jön " "(be irány)." -#: firewall/models.py:74 +#: firewall/models.py:77 msgid "dest. port" msgstr "célport" -#: firewall/models.py:76 +#: firewall/models.py:79 msgid "Destination port number of packets that match." msgstr "Az illeszkedő csomagok célportjának száma." -#: firewall/models.py:78 +#: firewall/models.py:81 msgid "source port" msgstr "forrásport" -#: firewall/models.py:80 +#: firewall/models.py:83 msgid "Source port number of packets that match." msgstr "Az illeszkedő csomagok forrásportjának száma." -#: firewall/models.py:82 +#: firewall/models.py:85 msgid "weight" msgstr "súly" -#: firewall/models.py:84 +#: firewall/models.py:87 msgid "Rule weight" msgstr "A szabály súlya" -#: firewall/models.py:87 +#: firewall/models.py:90 msgid "protocol" msgstr "protokoll" -#: firewall/models.py:88 +#: firewall/models.py:91 msgid "Protocol of packets that match." msgstr "Az illeszkedő csomagok protokollja." -#: firewall/models.py:89 +#: firewall/models.py:92 msgid "extra arguments" msgstr "további argumentumok" -#: firewall/models.py:90 +#: firewall/models.py:93 msgid "Additional arguments passed literally to the iptables-rule." msgstr "Az iptables-szabályhoz változtatás nélkül hozzáadott argumentumok." -#: firewall/models.py:93 +#: firewall/models.py:96 msgid "action" msgstr "művelet" -#: firewall/models.py:94 +#: firewall/models.py:97 msgid "Accept, drop or ignore the matching packets." msgstr "Az illeszkedő csomagok elfogadása, eldobása vagy ignorálása." -#: firewall/models.py:98 +#: firewall/models.py:101 msgid "The user responsible for this rule." msgstr "A szabályért felelős felhasználó." -#: firewall/models.py:101 +#: firewall/models.py:104 msgid "NAT" msgstr "NAT" -#: firewall/models.py:102 +#: firewall/models.py:105 msgid "If network address translation should be done." msgstr "Hálózati címfordítás engedélyezése." -#: firewall/models.py:106 +#: firewall/models.py:109 msgid "Rewrite destination port number to this if NAT is needed." msgstr "Célport számának átírása a megadottra NAT esetén." -#: firewall/models.py:111 +#: firewall/models.py:114 msgid "external IPv4 address" msgstr "külső IPv4 cím" -#: firewall/models.py:115 firewall/models.py:367 firewall/models.py:424 -#: firewall/models.py:447 firewall/models.py:518 +#: firewall/models.py:118 firewall/models.py:376 firewall/models.py:433 +#: firewall/models.py:456 firewall/models.py:527 msgid "created at" msgstr "létrehozva" -#: firewall/models.py:118 firewall/models.py:371 firewall/models.py:426 -#: firewall/models.py:449 firewall/models.py:520 +#: firewall/models.py:121 firewall/models.py:380 firewall/models.py:435 +#: firewall/models.py:458 firewall/models.py:529 msgid "modified at" msgstr "módosítva" -#: firewall/models.py:121 firewall/models.py:507 vm/models/network.py:39 -#: vm/models/network.py:64 +#: firewall/models.py:124 firewall/models.py:516 vm/models/network.py:40 +#: vm/models/network.py:65 msgid "vlan" msgstr "vlan" -#: firewall/models.py:122 +#: firewall/models.py:125 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:126 +#: firewall/models.py:129 msgid "vlan group" msgstr "vlan-csoport" -#: firewall/models.py:127 +#: firewall/models.py:130 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:130 firewall/models.py:828 firewall/models.py:948 -#: vm/models/network.py:66 vm/models/node.py:67 +#: firewall/models.py:133 firewall/models.py:848 firewall/models.py:968 +#: vm/models/network.py:67 vm/models/node.py:67 msgid "host" msgstr "gép" -#: firewall/models.py:131 +#: firewall/models.py:134 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:134 +#: firewall/models.py:137 msgid "host group" msgstr "gépcsoport" -#: firewall/models.py:135 +#: firewall/models.py:138 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:138 +#: firewall/models.py:141 msgid "firewall" msgstr "tűzfal" -#: firewall/models.py:139 +#: firewall/models.py:142 msgid "Firewall the rule applies to (if type is firewall)." msgstr "Erre a tűzfalra vonatkozik a szabály (ha a típus tűzfal)." -#: firewall/models.py:151 +#: firewall/models.py:154 msgid "Only one field can be selected." msgstr "Csak egy mező választható ki." -#: firewall/models.py:247 +#: firewall/models.py:256 msgid "rule" msgstr "szabály" -#: firewall/models.py:248 +#: firewall/models.py:257 msgid "rules" msgstr "szabályok" -#: firewall/models.py:276 +#: firewall/models.py:285 msgid "public" msgstr "nyilvános" -#: firewall/models.py:277 +#: firewall/models.py:286 msgid "portforward" msgstr "porttovábbítás" -#: firewall/models.py:279 +#: firewall/models.py:288 msgid "VID" msgstr "VID" -#: firewall/models.py:280 +#: firewall/models.py:289 msgid "The vlan ID of the subnet." msgstr "Az alhálózat vlan-azonosítója." -#: firewall/models.py:286 +#: firewall/models.py:295 msgid "The short name of the subnet." msgstr "Az alhálózat rövid neve." -#: firewall/models.py:290 +#: firewall/models.py:299 msgid "IPv4 address/prefix" msgstr "IPv4 cím/prefixhossz" -#: firewall/models.py:292 +#: firewall/models.py:301 msgid "" "The IPv4 address and the prefix length of the gateway. Recommended value is " "the last valid address of the subnet, for example 10.4.255.254/16 for " @@ -1831,11 +2410,11 @@ msgstr "" "Az útválasztó IPv4 címe és prefixhossza. Az ajánlott érték az alhálózat " "utolsó érvényes címe, például 10.4.255.254/16 a 10.4.0.0/16 hálózat esetén." -#: firewall/models.py:299 +#: firewall/models.py:308 msgid "IPv6 prefixlen/host" msgstr "IPv6 prefixhossz/gép" -#: firewall/models.py:300 +#: firewall/models.py:309 msgid "" "The prefix length of the subnet assigned to a host. For example /112 = 65536 " "addresses/host." @@ -1843,19 +2422,19 @@ msgstr "" "A géphez rendelt alhálózat prefixhossza. Például a /112 beállítás 65536 " "címet jelent gépenként." -#: firewall/models.py:308 +#: firewall/models.py:317 msgid "IPv6 address/prefix" msgstr "IPv6 cím/prefixhossz" -#: firewall/models.py:310 +#: firewall/models.py:319 msgid "The IPv6 address and the prefix length of the gateway." msgstr "Az útválasztó IPv6 címe és prefixhossza." -#: firewall/models.py:314 +#: firewall/models.py:323 msgid "NAT IP address" msgstr "NAT IP cím" -#: firewall/models.py:316 +#: firewall/models.py:325 msgid "" "Common IPv4 address used for address translation of connections to the " "networks selected below (typically to the internet)." @@ -1863,11 +2442,11 @@ msgstr "" "Közös címfordításra használt IPv4 cím a kiválasztott hálózatok felé irányuló " "kapcsolatokhoz (tipikusan az internet felé)." -#: firewall/models.py:322 +#: firewall/models.py:331 msgid "NAT to" msgstr "NAT ide" -#: firewall/models.py:324 +#: firewall/models.py:333 msgid "" "Connections to these networks should be network address translated, i.e. " "their source address is rewritten to the value of NAT IP address." @@ -1875,39 +2454,39 @@ msgstr "" "A megadott hálózatok felé induló kapcsolatok címfordításra kerülnek: a " "forráscímük a megadott NAT IP címre lesz átírva." -#: firewall/models.py:330 +#: firewall/models.py:339 msgid "network type" msgstr "hálózat típusa" -#: firewall/models.py:333 vm/models/network.py:41 +#: firewall/models.py:342 vm/models/network.py:42 msgid "managed" msgstr "menedzselt" -#: firewall/models.py:336 +#: firewall/models.py:345 msgid "Description of the goals and elements of the vlan network." msgstr "A vlan hálózat céljainak és elemeinek leírása." -#: firewall/models.py:338 +#: firewall/models.py:347 msgid "comment" msgstr "megjegyzés" -#: firewall/models.py:340 +#: firewall/models.py:349 msgid "Notes, comments about the network" msgstr "Jegyzetek, megjegyzések a hálózatról" -#: firewall/models.py:341 +#: firewall/models.py:350 msgid "domain name" msgstr "tartománynév" -#: firewall/models.py:342 +#: firewall/models.py:351 msgid "Domain name of the members of this network." msgstr "A hálózat tagjainak tartományneve." -#: firewall/models.py:346 +#: firewall/models.py:355 msgid "reverse domain" msgstr "reverz tartomány" -#: firewall/models.py:347 +#: firewall/models.py:356 #, python-format msgid "" "Template of the IPv4 reverse domain name that should be generated for each " @@ -1922,15 +2501,15 @@ msgstr "" "Például a szabványos reverz név sablonja: \"%(d)d.%(c)d.%(b)d.%(a)d.in-addr." "arpa\"." -#: firewall/models.py:357 +#: firewall/models.py:366 msgid "ipv6 template" msgstr "ipv6 sablon" -#: firewall/models.py:359 +#: firewall/models.py:368 msgid "DHCP pool" msgstr "DHCP készlet" -#: firewall/models.py:361 +#: firewall/models.py:370 msgid "" "The address range of the DHCP pool: empty for no DHCP service, \"manual\" " "for no DHCP pool, or the first and last address of the range separated by a " @@ -1940,47 +2519,47 @@ 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:405 +#: firewall/models.py:414 msgid "All IP addresses are already in use." msgstr "Minden IP cím használatban van." -#: firewall/models.py:413 firewall/models.py:440 firewall/models.py:796 -#: firewall/models.py:804 firewall/models.py:825 storage/models.py:49 -#: storage/models.py:88 vm/models/common.py:58 vm/models/common.py:77 -#: vm/models/common.py:146 vm/models/instance.py:134 vm/models/instance.py:211 +#: firewall/models.py:422 firewall/models.py:449 firewall/models.py:802 +#: firewall/models.py:824 firewall/models.py:845 storage/models.py:45 +#: storage/models.py:84 vm/models/common.py:60 vm/models/common.py:84 +#: vm/models/common.py:160 vm/models/instance.py:137 vm/models/instance.py:224 #: vm/models/node.py:62 msgid "name" msgstr "név" -#: firewall/models.py:414 firewall/models.py:441 +#: firewall/models.py:423 firewall/models.py:450 msgid "The name of the group." msgstr "A csoport neve." -#: firewall/models.py:416 +#: firewall/models.py:425 msgid "vlans" msgstr "vlanok" -#: firewall/models.py:417 +#: firewall/models.py:426 msgid "The vlans which are members of the group." msgstr "A csoport tagjait képező vlanok." -#: firewall/models.py:420 firewall/models.py:443 +#: firewall/models.py:429 firewall/models.py:452 msgid "Description of the group." msgstr "A csoport leírása." -#: firewall/models.py:465 storage/models.py:52 +#: firewall/models.py:474 storage/models.py:48 msgid "hostname" msgstr "gépnév" -#: firewall/models.py:466 +#: firewall/models.py:475 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:472 +#: firewall/models.py:481 msgid "reverse" msgstr "reverz" -#: firewall/models.py:473 +#: firewall/models.py:482 msgid "" "The fully qualified reverse hostname of the host, if different than hostname." "domain." @@ -1988,139 +2567,139 @@ msgstr "" "A gép teljes reverz tartományneve, amennyiben különbözik ettől: gépnév." "tartomány." -#: firewall/models.py:477 +#: firewall/models.py:486 msgid "MAC address" msgstr "MAC cím" -#: firewall/models.py:478 +#: firewall/models.py:487 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:483 +#: firewall/models.py:492 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:487 +#: firewall/models.py:496 msgid "WAN IPv4 address" msgstr "WAN IPv4 cím" -#: firewall/models.py:488 +#: firewall/models.py:497 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:493 +#: firewall/models.py:502 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:495 +#: firewall/models.py:504 msgid "shared IP" msgstr "osztott IP" -#: firewall/models.py:497 +#: firewall/models.py:506 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:500 +#: firewall/models.py:509 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:503 +#: firewall/models.py:512 msgid "Notes" msgstr "Jegyzetek" -#: firewall/models.py:504 +#: firewall/models.py:513 msgid "location" msgstr "elhelyezés" -#: firewall/models.py:506 +#: firewall/models.py:515 msgid "The physical location of the machine." msgstr "A gép fizikai helye." -#: firewall/models.py:509 +#: firewall/models.py:518 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:512 +#: firewall/models.py:521 msgid "The person responsible for this host." msgstr "A gépért felelős személy." -#: firewall/models.py:514 +#: firewall/models.py:523 msgid "groups" msgstr "csoportok" -#: firewall/models.py:516 +#: firewall/models.py:525 msgid "Host groups the machine is part of." msgstr "Gépcsoportok, amelyeknek tagja a gép." -#: firewall/models.py:553 +#: firewall/models.py:562 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:556 +#: firewall/models.py:565 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:645 +#: firewall/models.py:654 #, python-format msgid "All %s ports are already in use." msgstr "Minden %s port használatban van." -#: firewall/models.py:663 +#: firewall/models.py:672 #, 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:681 +#: firewall/models.py:690 msgid "Only ports above 1024 can be used." msgstr "Csak az 1024 feletti portok használhatóak." -#: firewall/models.py:807 firewall/models.py:837 firewall/models.py:907 -#: firewall/models.py:935 firewall/models.py:959 +#: firewall/models.py:827 firewall/models.py:857 firewall/models.py:927 +#: firewall/models.py:955 firewall/models.py:979 msgid "created_at" msgstr "létrehozva" -#: firewall/models.py:809 firewall/models.py:839 firewall/models.py:909 -#: firewall/models.py:937 firewall/models.py:961 +#: firewall/models.py:829 firewall/models.py:859 firewall/models.py:929 +#: firewall/models.py:957 firewall/models.py:981 msgid "modified_at" msgstr "módosítva" -#: firewall/models.py:810 firewall/models.py:833 +#: firewall/models.py:830 firewall/models.py:853 msgid "ttl" msgstr "ttl" -#: firewall/models.py:826 +#: firewall/models.py:846 msgid "domain" msgstr "tartomány" -#: firewall/models.py:832 +#: firewall/models.py:852 msgid "address" msgstr "cím" -#: firewall/models.py:854 +#: firewall/models.py:874 msgid "Address must be specified!" msgstr "A cím megadása kötelező." -#: firewall/models.py:867 +#: firewall/models.py:887 msgid "Unknown record type." msgstr "Ismeretlen rekordtípus." -#: firewall/models.py:901 +#: firewall/models.py:921 msgid "untagged vlan" msgstr "untagged vlan" -#: firewall/models.py:904 +#: firewall/models.py:924 msgid "tagged vlans" msgstr "tagged vlanok" -#: firewall/models.py:927 +#: firewall/models.py:947 msgid "interface" msgstr "interfész" -#: firewall/models.py:928 +#: firewall/models.py:948 msgid "" "The name of network interface the gateway should serve this network on. For " "example eth2." @@ -2128,23 +2707,23 @@ 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:933 +#: firewall/models.py:953 msgid "switch port" msgstr "switch port" -#: firewall/models.py:949 +#: firewall/models.py:969 msgid "reason" msgstr "indok" -#: firewall/models.py:951 +#: firewall/models.py:971 msgid "short message" msgstr "rövid üzenet" -#: firewall/models.py:971 +#: firewall/models.py:991 msgid "blacklist item" msgstr "tiltólista eleme" -#: firewall/models.py:972 +#: firewall/models.py:992 msgid "blacklist" msgstr "tiltólista" @@ -2188,155 +2767,155 @@ msgstr "" msgid "Something went wrong!\n" msgstr "Baj van!\n" -#: network/views.py:112 +#: network/views.py:114 #, python-format msgid "Successfully modified blacklist item%(ipv4)s - %(type)s!" msgstr "Tiltólista eleme sikeresen módosítva (%(ipv4)s %(type)s)." -#: network/views.py:130 +#: network/views.py:132 #, python-format msgid "Successfully created blacklist item %(ipv4)s - %(type)s!" msgstr "Tiltólista eleme sikeresen létrehozva (%(ipv4)s %(type)s)." -#: network/views.py:168 +#: network/views.py:170 #, python-format msgid "Successfully modified domain %(name)s!" msgstr "A(z) %(name)s tartománynév módosításra került." -#: network/views.py:195 +#: network/views.py:197 #, python-format msgid "Successfully created domain %(name)s!" msgstr "A(z) %(name)s tartománynév létrehozásra került." -#: network/views.py:212 network/views.py:444 network/views.py:680 +#: network/views.py:214 network/views.py:446 network/views.py:689 msgid "Object name does not match!" msgstr "Az objektum neve nem egyezik." -#: network/views.py:216 +#: network/views.py:218 msgid "Domain successfully deleted!" msgstr "A tartománynév törlésre került." -#: network/views.py:222 +#: network/views.py:224 msgid "Records from hosts" msgstr "A gépek rekordjai" -#: network/views.py:228 network/templates/network/menu.html:8 +#: network/views.py:230 network/templates/network/menu.html:8 msgid "Vlans" msgstr "Vlanok" -#: network/views.py:236 network/views.py:695 +#: network/views.py:238 network/views.py:704 #: network/templates/network/host-list.html:11 #: network/templates/network/menu.html:5 msgid "Hosts" msgstr "Gépek" -#: network/views.py:279 +#: network/views.py:281 #, python-format msgid "Successfully created host group %(name)s!" msgstr "%(name)s gépcsoport létrehozásra került." -#: network/views.py:287 +#: network/views.py:289 #, python-format msgid "Successfully modified host group %(name)s!" msgstr "%(name)s gépcsoport módosításra került." -#: network/views.py:347 +#: network/views.py:349 #, python-format msgid "Successfully modified host %(hostname)s!" msgstr "%(hostname)s gép módosításra került." -#: network/views.py:412 +#: network/views.py:414 #, python-format msgid "Successfully created host %(hostname)s!" msgstr "%(hostname)s gép létrehozásra került." -#: network/views.py:433 network/views.py:703 +#: network/views.py:435 network/views.py:712 #: network/templates/network/menu.html:14 #: network/templates/network/record-list.html:11 msgid "Records" msgstr "Rekordok" -#: network/views.py:448 +#: network/views.py:450 msgid "Host successfully deleted!" msgstr "A gép törlésre került." -#: network/views.py:478 +#: network/views.py:480 msgid "Successfully modified record!" msgstr "A rekord módosításra került." -#: network/views.py:497 +#: network/views.py:499 msgid "Successfully created record!" msgstr "A rekord létrehozásra került." -#: network/views.py:535 +#: network/views.py:537 msgid "Successfully modified rule!" msgstr "A szabály módosításra került." -#: network/views.py:555 +#: network/views.py:557 msgid "Successfully created rule!" msgstr "A szabály létrehozásra került." -#: network/views.py:590 +#: network/views.py:592 msgid "Succesfully modified switch port!" msgstr "A switch-port módosításra került." -#: network/views.py:609 +#: network/views.py:611 msgid "Successfully created switch port!" msgstr "A switch-port létrehozásra került." -#: network/views.py:638 +#: network/views.py:644 #, python-format msgid "Succesfully modified vlan %(name)s!" msgstr "A(z) %(name)s vlan módosításra került." -#: network/views.py:659 +#: network/views.py:668 #, python-format msgid "Successfully created vlan %(name)s!" msgstr "A(z) %(name)s vlan létrehozásra került." -#: network/views.py:684 +#: network/views.py:693 msgid "Vlan successfully deleted!" msgstr "A vlan törlésre került." -#: network/views.py:726 +#: network/views.py:735 #, python-format msgid "Successfully modified vlan group %(name)s!" msgstr "A(z) %(name)s vlan-csoport módosításra került." -#: network/views.py:739 +#: network/views.py:748 #, python-format msgid "Successfully created vlan group %(name)s!" msgstr "A(z) %(name)s vlan-csoport módosításra került." -#: network/views.py:771 +#: network/views.py:780 #, python-format 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:787 +#: network/views.py:796 #, python-format 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:806 +#: network/views.py:815 #, python-format msgid "Successfully deleted ethernet device %(name)s!" msgstr "A(z) %(name)s ethernet-eszköz törlésre került." -#: network/views.py:824 +#: network/views.py:833 #, python-format msgid "Successfully added %(name)s to this switch port" msgstr "%(name)s hozzáadásra került a switch-porthoz." -#: network/views.py:831 +#: network/views.py:840 msgid "Ethernet device name cannot be empty!" msgstr "Az ethernet-eszköz megadása kötelező." -#: network/views.py:834 +#: network/views.py:843 msgid "There is already an ethernet device with that name!" msgstr "Már létezik a megadott nevű ethernet-eszköz." -#: network/templates/network/base.html:56 +#: network/templates/network/base.html:44 msgid "dashboard" msgstr "műszerfal" @@ -2357,28 +2936,6 @@ msgstr "Tiltólista elemének hozzáadása" msgid "Blacklist" msgstr "Tiltólista" -#: network/templates/network/dashboard.html:24 -#: network/templates/network/dashboard.html:40 -#: network/templates/network/dashboard.html:56 -#: network/templates/network/dashboard.html:72 -#: network/templates/network/dashboard.html:88 -#: network/templates/network/dashboard.html:103 -#: network/templates/network/dashboard.html:119 -#: network/templates/network/dashboard.html:135 -msgid "List" -msgstr "Felsorolás" - -#: network/templates/network/dashboard.html:25 -#: network/templates/network/dashboard.html:41 -#: network/templates/network/dashboard.html:57 -#: network/templates/network/dashboard.html:73 -#: network/templates/network/dashboard.html:89 -#: network/templates/network/dashboard.html:104 -#: network/templates/network/dashboard.html:120 -#: network/templates/network/dashboard.html:136 -msgid "Create" -msgstr "Létrehozás" - #: network/templates/network/domain-create.html:10 #: network/templates/network/domain-list.html:9 msgid "Create a new domain" @@ -2404,7 +2961,7 @@ msgstr "Tartománynevek" #: network/templates/network/group-create.html:10 #: network/templates/network/group-list.html:9 msgid "Create a new host group" -msgstr "Gépcsoport léterhozása" +msgstr "Gépcsoport létrehozása" #: network/templates/network/group-edit.html:10 #: network/templates/network/vlan-group-edit.html:10 @@ -2581,6 +3138,10 @@ msgstr "Vlan törlése" msgid "details of vlan" msgstr "vlan részletei" +#: network/templates/network/vlan-edit.html:20 +msgid "Host list" +msgstr "Gépek" + #: network/templates/network/vlan-group-create.html:10 #: network/templates/network/vlan-group-list.html:9 msgid "Create a new vlan group" @@ -2599,13 +3160,13 @@ msgstr "Gyártó: %(vendor)s" #, python-format msgid "" "\n" -" Are you sure you want to delete \n" +" Are you sure you want to delete\n" " \"%(object)s\"?\n" " " msgstr "" "\n" " Biztosan törli a(z) \n" -" \"%(object)s\" elemet?\n" +" „%(object)s” elemet?\n" " " #: network/templates/network/confirm/base_delete.html:18 @@ -2624,41 +3185,116 @@ msgstr "Amennyiben biztos benne, gépelje be az objektum nevét." msgid "Yes, delete it!" msgstr "Igen, törlés!" -#: storage/models.py:50 +#: storage/models.py:46 msgid "path" msgstr "útvonal" -#: storage/models.py:56 storage/models.py:91 +#: storage/models.py:52 storage/models.py:87 msgid "datastore" msgstr "adattár" -#: storage/models.py:57 +#: storage/models.py:53 msgid "datastores" msgstr "adattárak" -#: storage/models.py:90 +#: storage/models.py:86 msgid "filename" msgstr "fájlnév" -#: storage/models.py:92 +#: storage/models.py:88 msgid "The datastore that holds the disk." msgstr "A lemezt tároló adattár." -#: storage/models.py:98 +#: storage/models.py:94 msgid "device number" msgstr "eszközazonosító" -#: storage/models.py:103 storage/models.py:597 +#: storage/models.py:101 msgid "disk" msgstr "lemez" -#: storage/models.py:104 vm/models/instance.py:141 vm/models/instance.py:238 +#: storage/models.py:102 vm/models/instance.py:144 vm/models/instance.py:251 msgid "disks" msgstr "lemezek" -#: storage/models.py:596 -msgid "Disk this activity works on." -msgstr "A lemez, amelyre a művelet vonatkozik." +#: storage/models.py:104 +msgid "Can create an empty disk." +msgstr "Létrehozhat új lemezt." + +#: storage/models.py:105 +msgid "Can download a disk." +msgstr "Letölthet lemezt." + +#: storage/models.py:118 +#, python-format +msgid "Operation can't be invoked on disk '%(name)s' of type '%(type)s'." +msgstr "" +"A kér művelet nem hajtható végre a(z) %(type)s típusú „%(name)s” lemezen." + +#: storage/models.py:122 +#, python-format +msgid "" +"Operation can't be invoked on disk '%(name)s' (%(pk)s) of type '%(type)s'." +msgstr "" +"A kér művelet nem hajtható végre a(z) %(type)s típusú „%(name)s” (%(pk)s) " +"lemezen." + +#: storage/models.py:131 +#, python-format +msgid "" +"The requested operation can't be performed on disk '%(name)s' because it is " +"in use." +msgstr "" +"A kér művelet nem hajtható végre a(z) „%(name)s” lemezen, mivel használatban " +"van." + +#: storage/models.py:135 +#, python-format +msgid "" +"The requested operation can't be performed on disk '%(name)s' (%(pk)s) " +"because it is in use." +msgstr "" +"A kér művelet nem hajtható végre a(z) „%(name)s” (%(pk)s) lemezen, mivel " +"használatban van." + +#: storage/models.py:144 +#, python-format +msgid "" +"The requested operation can't be performed on disk '%(name)s' because it has " +"never been deployed." +msgstr "" +"A kér művelet nem hajtható végre a(z) „%(name)s” lemezen, mivel nem volt még " +"csatolva." + +#: storage/models.py:148 +#, python-format +msgid "" +"The requested operation can't be performed on disk '%(name)s' (%(pk)s) " +"[%(filename)s] because it has never beendeployed." +msgstr "" +"A kér művelet nem hajtható végre a(z) „%(name)s” (%(pk)s) " +"[%(filename)s] lemezen, mivel nem volt még csatolva." + +#: storage/models.py:159 +#, python-format +msgid "" +"The requested operation can't be performed on disk '%(name)s' because its " +"base has never been deployed." +msgstr "" +"A kér művelet nem hajtható végre a(z) „%(name)s” lemezen, mivel az alapja nem " +"volt még csatolva." + +#: storage/models.py:163 +#, python-format +msgid "" +"The requested operation can't be performed on disk '%(name)s' (%(pk)s) " +"[%(filename)s] because its base '%(b_name)s' (%(b_pk)s) [%(b_filename)s] has " +"never beendeployed." +msgstr "" +"A kér művelet nem hajtható végre a(z) „%(name)s” (%(pk)s) " +"[%(filename)s] lemezen, mivel az alapja, „%(b_name)s” (%(b_pk)s) [%" +"(b_filename)s] " +"nem volt még csatolva." #: templates/404.html:4 templates/404.html.py:6 msgid "Page not found" @@ -2672,7 +3308,7 @@ msgstr "Az oldal nem létezik." msgid ":(" msgstr ":(" -#: templates/500.html:9 +#: templates/500.html:13 msgid "Internal Server Error... Please leave the server alone..." msgstr "Kiszolgálóoldali hiba. Ne bántsa a szervert." @@ -2680,15 +3316,15 @@ msgstr "Kiszolgálóoldali hiba. Ne bántsa a szervert." msgid "Login" msgstr "Bejelentkezés" -#: templates/registration/login.html:25 +#: templates/registration/login.html:32 msgid "Login with SSO" msgstr "SSO bejelentkezés" -#: templates/registration/login.html:26 +#: templates/registration/login.html:33 msgid "Click here!" msgstr "Kattintson ide!" -#: templates/registration/login.html:32 +#: templates/registration/login.html:39 msgid "Forgot your password?" msgstr "Elfelejtett jelszó" @@ -2696,11 +3332,11 @@ msgstr "Elfelejtett jelszó" msgid "Password reset complete" msgstr "A jelszó visszaállítása megtörtént" -#: templates/registration/password_reset_complete.html:15 +#: templates/registration/password_reset_complete.html:16 msgid "Password change successful!" msgstr "A jelszó megváltoztatásra került." -#: templates/registration/password_reset_complete.html:16 +#: templates/registration/password_reset_complete.html:17 msgid "Click here to login" msgstr "Kattintson ide a bejelentkezéshez" @@ -2708,13 +3344,13 @@ msgstr "Kattintson ide a bejelentkezéshez" msgid "Password reset confirm" msgstr "Megerősítés jelszó-visszaállításról" -#: templates/registration/password_reset_confirm.html:15 +#: templates/registration/password_reset_confirm.html:16 msgid "" "Please enter your new password twice so we can verify you typed it in " "correctly!" msgstr "Adja meg a választott jelszót kétszer a hiba elkerülése érdekében." -#: templates/registration/password_reset_confirm.html:23 +#: templates/registration/password_reset_confirm.html:24 #, python-format msgid "" "This token is expired, please request a new password " @@ -2726,96 +3362,212 @@ msgstr "" msgid "Password reset done" msgstr "A jelszó-visszaállítás megtörtént" -#: templates/registration/password_reset_done.html:14 -#: templates/registration/password_reset_form.html:14 +#: templates/registration/password_reset_done.html:15 +#: templates/registration/password_reset_form.html:15 msgid "Back to login" msgstr "Vissza a bejelentkezéshez" -#: templates/registration/password_reset_done.html:15 +#: templates/registration/password_reset_done.html:16 msgid "We have sent you an email about your next steps!" msgstr "Küldtünk egy e-mailt a további teendőkről." -#: templates/registration/password_reset_form.html:6 +#: templates/registration/password_reset_form.html:6 vm/operations.py:878 msgid "Password reset" msgstr "Jelszó visszaállítása" -#: templates/registration/password_reset_form.html:15 +#: templates/registration/password_reset_form.html:16 msgid "Enter your email address to reset your password!" msgstr "Adja meg e-mail címét a jelszó visszaállításához." -#: vm/operations.py:83 +#: vm/operations.py:78 +#, python-format +msgid "%(acl_level)s level is required for this operation." +msgstr "%(acl_level)s jogosultság szükséges a művelethez." + +#: vm/operations.py:113 msgid "Add a new network interface for the specified VLAN to the VM." msgstr "Új hálózati interfész hozzáadása a megadott VLAN-ba." -#: vm/operations.py:105 -msgid "add disk" -msgstr "lemez hozzáadása" +#: vm/operations.py:121 +msgid "destroy network (rollback)" +msgstr "hálózat megsemmisítése (visszagörgetés)" -#: vm/operations.py:106 -msgid "Add the specified disk to the VM." -msgstr "A megadott lemez hozzáadása a VM-hez." +#: vm/operations.py:128 +#, python-format +msgid "User acces to vlan %(vlan)s is required." +msgstr "Használói jogosultság szükséges a(z) %(vlan)s vlan-hoz." -#: vm/operations.py:125 +#: vm/operations.py:140 +msgid "attach network" +msgstr "hálózat csatolása" + +#: vm/operations.py:149 +#, python-format +msgid "add %(vlan)s interface" +msgstr "új %(vlan)s interfész" + +#: vm/operations.py:160 +msgid "create disk" +msgstr "lemez létrehozása" + +#: vm/operations.py:161 +msgid "Create empty disk for the VM." +msgstr "Üres lemez létrehozása a VM-hez." + +#: vm/operations.py:182 +msgid "deploying disk" +msgstr "lemez létrehozása" + +#: vm/operations.py:187 vm/operations.py:229 +msgid "attach disk" +msgstr "lemez csatolása" + +#: vm/operations.py:193 +#, python-format +msgid "create disk %(name)s (%(size)s)" +msgstr "%(name)s lemez létrehozása (%(size)s)" + +#: vm/operations.py:203 +msgid "download disk" +msgstr "lemez letöltése" + +#: vm/operations.py:204 +msgid "Download disk for the VM." +msgstr "Lemez letöltése a VM-hez." + +#: vm/operations.py:223 +#, python-format +msgid "download %(name)s" +msgstr "%(name)s letöltése" + +#: vm/operations.py:239 msgid "deploy" msgstr "indítás" -#: vm/operations.py:126 +#: vm/operations.py:240 msgid "Deploy new virtual machine with network." -msgstr "Virtiális gép indítása (hálózattal)" +msgstr "Virtuális gép indítása és a hálózat beállítása." -#: vm/operations.py:161 +#: vm/operations.py:260 +msgid "deploy disks" +msgstr "lemez létrehozása" + +#: vm/operations.py:267 +msgid "deploy virtual machine" +msgstr "virtuális gép létrehozása" + +#: vm/operations.py:273 vm/operations.py:387 vm/operations.py:700 +msgid "deploy network" +msgstr "hálózati kapcsolat létrehozása" + +#: vm/operations.py:279 +msgid "boot virtual machine" +msgstr "virtuális gép indítása" + +#: vm/operations.py:291 msgid "destroy" msgstr "megsemmisítés" -#: vm/operations.py:162 +#: vm/operations.py:292 msgid "Destroy virtual machine and its networks." msgstr "Virtuális gép és hálózatainak eltávolítása." -#: vm/operations.py:202 +#: vm/operations.py:302 +msgid "destroy network" +msgstr "hálózat megsemmisítése" + +#: vm/operations.py:311 +msgid "destroy virtual machine" +msgstr "virtuális gép megsemmisítése" + +#: vm/operations.py:317 +msgid "destroy disks" +msgstr "lemez megsemmisítése" + +#: vm/operations.py:340 msgid "migrate" msgstr "migrálás" -#: vm/operations.py:203 +#: vm/operations.py:341 msgid "Live migrate running VM to another node." msgstr "Futó VM üzem közbeni migrálása másik csomópontra." -#: vm/operations.py:232 +#: vm/operations.py:348 +msgid "redeploy network (rollback)" +msgstr "hálózati kapcsolat újraépítése (visszagörgetés)" + +#: vm/operations.py:361 +msgid "schedule" +msgstr "ütemezés" + +#: vm/operations.py:368 +#, python-format +msgid "migrate to %(node)s" +msgstr "migrálás %(node)s csomópontra" + +#: vm/operations.py:378 vm/operations.py:650 +msgid "shutdown network" +msgstr "hálózati kapcsolat leállítása" + +#: vm/operations.py:397 msgid "reboot" msgstr "újraindítás" -#: vm/operations.py:233 +#: vm/operations.py:398 msgid "Reboot virtual machine with Ctrl+Alt+Del signal." msgstr "Virtuális gép újraindítása Ctrl+Alt+Del kombinációval." -#: vm/operations.py:245 +#: vm/operations.py:412 msgid "remove interface" msgstr "interfész törlése" -#: vm/operations.py:246 +#: vm/operations.py:413 msgid "Remove the specified network interface from the VM." msgstr "A megadott interfész törlése a VM-ből." -#: vm/operations.py:262 +#: vm/operations.py:421 +msgid "detach network" +msgstr "hálózat lecsatolása" + +#: vm/operations.py:430 +#, python-format +msgid "remove %(vlan)s interface" +msgstr "%(vlan)s interfész törlése" + +#: vm/operations.py:440 msgid "remove disk" msgstr "lemez eltávolítása" -#: vm/operations.py:263 +#: vm/operations.py:441 msgid "Remove the specified disk from the VM." msgstr "A megadott lemez eltávolítása a VM-ből." -#: vm/operations.py:282 +#: vm/operations.py:449 +msgid "detach disk" +msgstr "lemez leválasztása" + +#: vm/operations.py:454 +msgid "destroy disk" +msgstr "lemez megsemmisítése" + +#: vm/operations.py:459 +#, python-format +msgid "remove disk %(name)s" +msgstr "%(name)s lemez eltávolítása" + +#: vm/operations.py:468 msgid "reset" msgstr "újraindítás" -#: vm/operations.py:283 +#: vm/operations.py:469 msgid "Reset virtual machine (reset button)." msgstr "Virtuális gép újraindítása (reset gomb)." -#: vm/operations.py:294 +#: vm/operations.py:482 msgid "save as template" msgstr "mentés sablonként" -#: vm/operations.py:295 +#: vm/operations.py:483 msgid "" "Save Virtual Machine as a Template.\n" "\n" @@ -2829,35 +3581,44 @@ msgstr "" " A felhasználók virtuális gépeket példányosíthatnak a sablonokból.\n" " " -#: vm/operations.py:374 +#: vm/operations.py:553 +#, python-format +msgid "saving disk %(name)s" +msgstr "%(name)s lemez mentése" + +#: vm/operations.py:580 msgid "shutdown" msgstr "leállítás" -#: vm/operations.py:375 +#: vm/operations.py:581 msgid "Shutdown virtual machine with ACPI signal." msgstr "Virtuális gép leállítása ACPI jelzéssel." -#: vm/operations.py:398 +#: vm/operations.py:601 msgid "shut off" msgstr "kikapcsolás" -#: vm/operations.py:399 +#: vm/operations.py:602 msgid "Shut off VM (plug-out)." msgstr "VM kikapcsolása (drót kihúzása)" -#: vm/operations.py:424 +#: vm/operations.py:629 msgid "sleep" msgstr "altatás" -#: vm/operations.py:425 +#: vm/operations.py:630 msgid "Suspend virtual machine with memory dump." msgstr "Virtuális gép felfüggesztése memóriamentéssel." -#: vm/operations.py:460 +#: vm/operations.py:656 +msgid "suspend virtual machine" +msgstr "virtuális gép felfüggesztése" + +#: vm/operations.py:669 msgid "wake up" -msgstr "ébresztés" +msgstr "virtuális gép ébresztése" -#: vm/operations.py:461 +#: vm/operations.py:670 msgid "" "Wake up Virtual Machine from SUSPENDED state.\n" "\n" @@ -2869,309 +3630,476 @@ msgstr "" " A virtuális gép elindítása a kimentett memória betöltésével.\n" " " -#: vm/operations.py:525 +#: vm/operations.py:694 +msgid "resume virtual machine" +msgstr "virtuális gép ébresztése" + +#: vm/operations.py:713 +msgid "renew" +msgstr "megújítás" + +#: vm/operations.py:714 +msgid "Renew expiration times" +msgstr "Lejárati idők megújítása" + +#: vm/operations.py:731 +msgid "emergency change state" +msgstr "vész-állapotváltás" + +#: vm/operations.py:732 +msgid "Change the virtual machine state to NOSTATE" +msgstr "Virtuális gép állapotának változtatása NOSTATE-re" + +#: vm/operations.py:774 msgid "flush" msgstr "ürítés" -#: vm/operations.py:526 +#: vm/operations.py:775 msgid "Disable node and move all instances to other ones." msgstr "A csomópont tiltása és az összes példány másikakra mozgatása." -#: vm/models/activity.py:55 +#: vm/operations.py:787 +msgid "Superuser privileges are required." +msgstr "Rendszergazdai jogosultság szükséges." + +#: vm/operations.py:796 +#, python-format +msgid "migrate %(instance)s (%(pk)s)" +msgstr "%(instance)s (%(pk)s) migrálása" + +#: vm/operations.py:808 +msgid "screenshot" +msgstr "képernyőkép" + +#: vm/operations.py:809 +msgid "Get screenshot" +msgstr "Képernyőkép készítése" + +#: vm/operations.py:824 +msgid "recover" +msgstr "visszaállítás" + +#: vm/operations.py:825 +msgid "Recover virtual machine from destroyed state." +msgstr "Megsemmisített virtuális gép visszaállítása." + +#: vm/operations.py:854 +msgid "resources change" +msgstr "erőforrások módosítása" + +#: vm/operations.py:855 +msgid "Change resources" +msgstr "Erőforrások módosítása" + +#: vm/operations.py:877 +msgid "password reset" +msgstr "jelszó visszaállítása" + +#: vm/models/activity.py:47 +#, python-format +msgid "%(activity)s activity is currently in progress." +msgstr "%(activity)s folyamatban van." + +#: vm/models/activity.py:48 +#, python-format +msgid "%(activity)s (%(pk)s) activity is currently in progress." +msgstr "%(activity)s (%(pk)s) folyamatban van." + +#: vm/models/activity.py:70 msgid "Instance this activity works on." msgstr "A tevékenység tárgyát képező példány." -#: vm/models/activity.py:171 +#: vm/models/activity.py:211 msgid "Node this activity works on." msgstr "A tevékenység tárgyát képező csomópont." -#: vm/models/activity.py:172 +#: vm/models/activity.py:212 msgid "node" msgstr "csomópont" -#: vm/models/common.py:36 +#: vm/models/activity.py:270 +msgid "Manager is restarted, activity is cleaned up. You can try again now." +msgstr "" +"A menedzser újraindítása miatt a tevékenység lezárásra került. Próbálja újra." + +#: vm/models/common.py:38 msgid "number of cores" msgstr "magok száma" -#: vm/models/common.py:37 +#: vm/models/common.py:39 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:40 +#: vm/models/common.py:42 msgid "Mebibytes of memory." msgstr "Memória mebibyte-okban." -#: vm/models/common.py:41 +#: vm/models/common.py:43 msgid "maximal RAM size" msgstr "maximális RAM-méret" -#: vm/models/common.py:42 +#: vm/models/common.py:44 msgid "Upper memory size limit for balloning." msgstr "Felső memóriaméret-korlát ballooning esetén." -#: vm/models/common.py:44 +#: vm/models/common.py:46 msgid "architecture" msgstr "architektúra" -#: vm/models/common.py:46 vm/models/node.py:65 +#: vm/models/common.py:48 vm/models/node.py:65 msgid "priority" msgstr "prioritás" -#: vm/models/common.py:47 +#: vm/models/common.py:49 msgid "CPU priority." msgstr "CPU prioritás." -#: vm/models/common.py:59 +#: vm/models/common.py:61 msgid "Name of base resource configuration." msgstr "Alap erőforráskonfiguráció neve." -#: vm/models/common.py:79 +#: vm/models/common.py:86 msgid "suspend interval" msgstr "felfüggesztés ideje" -#: vm/models/common.py:80 +#: vm/models/common.py:87 msgid "Number of seconds after the an instance is suspended." msgstr "A példány felfüggesztését megelőző másodpercek száma." -#: vm/models/common.py:83 +#: vm/models/common.py:90 msgid "delete interval" msgstr "törlés ideje" -#: vm/models/common.py:84 +#: vm/models/common.py:91 msgid "Number of seconds after the an instance is deleted." msgstr "A példány felfüggesztését megelőző másodpercek száma." -#: vm/models/common.py:128 vm/models/common.py:136 +#: vm/models/common.py:99 +msgid "Can create new leases." +msgstr "Létrehozhat bérlési módot." + +#: vm/models/common.py:138 vm/models/common.py:146 msgid "never" msgstr "soha" -#: vm/models/common.py:139 +#: vm/models/common.py:149 #, 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:101 +#: vm/models/instance.py:104 msgid "access method" msgstr "elérés módja" -#: vm/models/instance.py:102 +#: vm/models/instance.py:105 msgid "Primary remote access method." msgstr "Elsődleges távoli elérési mód." -#: vm/models/instance.py:103 +#: vm/models/instance.py:106 msgid "boot menu" msgstr "rendszerbetöltő menüje" -#: vm/models/instance.py:105 +#: vm/models/instance.py:108 msgid "Show boot device selection menu on boot." msgstr "" "A rendszerbetöltés eszközének kiválasztását lehetővé tevő menü megjelenítése " "indításkor." -#: vm/models/instance.py:106 +#: vm/models/instance.py:109 msgid "Preferred expiration periods." msgstr "Javasolt bérlési mód." -#: vm/models/instance.py:108 +#: vm/models/instance.py:111 msgid "raw_data" msgstr "nyers adat" -#: vm/models/instance.py:109 +#: vm/models/instance.py:112 msgid "Additional libvirt domain parameters in XML format." msgstr "További libvirt domain-paraméterek XML formátumban." -#: vm/models/instance.py:111 +#: vm/models/instance.py:114 msgid "" "A set of traits required for a node to declare to be suitable for hosting " "the VM." msgstr "A VM indításához szükséges csomópontjellemzők halmaza." -#: vm/models/instance.py:114 +#: vm/models/instance.py:117 msgid "required traits" msgstr "elvárt jellemzők" -#: vm/models/instance.py:115 +#: vm/models/instance.py:118 msgid "operating system" msgstr "operációs rendszer" -#: vm/models/instance.py:116 +#: vm/models/instance.py:119 #, python-format msgid "Name of operating system in format like \"%s\"." msgstr "Az operációs rendszer neve. Például „%s”." -#: vm/models/instance.py:119 vm/models/node.py:75 +#: vm/models/instance.py:122 vm/models/node.py:75 msgid "tags" msgstr "címkék" -#: vm/models/instance.py:135 -msgid "Human readable name of template." -msgstr "A sablon olvasható neve." - -#: vm/models/instance.py:138 +#: vm/models/instance.py:141 msgid "parent template" msgstr "szülősablon" -#: vm/models/instance.py:140 +#: vm/models/instance.py:143 msgid "Template which this one is derived of." msgstr "Az a sablon, amelyből az aktuális származik." -#: vm/models/instance.py:143 +#: vm/models/instance.py:146 msgid "Disks which are to be mounted." msgstr "A csatolandó lemezek." -#: vm/models/instance.py:151 +#: vm/models/instance.py:154 msgid "Can create an instance template." msgstr "Létrehozhat példánysablont." -#: vm/models/instance.py:153 vm/models/instance.py:217 vm/models/network.py:44 +#: vm/models/instance.py:156 +msgid "Can create an instance template (base)." +msgstr "Létrehozhat példánysablont (alapokból)." + +#: vm/models/instance.py:158 +msgid "Can change resources of a template." +msgstr "Változtathatja egy sablon erőforrásait." + +#: vm/models/instance.py:160 vm/models/instance.py:230 vm/models/network.py:45 msgid "template" msgstr "sablon" -#: vm/models/instance.py:154 +#: vm/models/instance.py:161 msgid "templates" msgstr "sablonok" -#: vm/models/instance.py:203 +#: vm/models/instance.py:216 msgid "no state" msgstr "nincs állapot" -#: vm/models/instance.py:204 +#: vm/models/instance.py:217 msgid "running" msgstr "fut" -#: vm/models/instance.py:205 +#: vm/models/instance.py:218 msgid "stopped" msgstr "leállítva" -#: vm/models/instance.py:206 +#: vm/models/instance.py:219 msgid "suspended" msgstr "felfüggesztve" -#: vm/models/instance.py:207 +#: vm/models/instance.py:220 msgid "error" msgstr "hiba" -#: vm/models/instance.py:208 +#: vm/models/instance.py:221 msgid "pending" msgstr "függő" -#: vm/models/instance.py:209 +#: vm/models/instance.py:222 msgid "destroyed" msgstr "megsemmisítve" -#: vm/models/instance.py:212 +#: vm/models/instance.py:225 msgid "Human readable name of instance." msgstr "A példány olvasható neve." -#: vm/models/instance.py:216 +#: vm/models/instance.py:229 msgid "Template the instance derives from." msgstr "Az a sablon, amelyből a példány származik." -#: vm/models/instance.py:218 +#: vm/models/instance.py:231 msgid "Original password of the instance." msgstr "A példány eredeti jelszava." -#: vm/models/instance.py:219 +#: vm/models/instance.py:232 msgid "password" msgstr "jelszó" -#: vm/models/instance.py:221 +#: vm/models/instance.py:234 msgid "time of suspend" msgstr "felfüggesztés ideje" -#: vm/models/instance.py:222 +#: vm/models/instance.py:235 msgid "Proposed time of automatic suspension." msgstr "A felfüggesztés kijelölt ideje." -#: vm/models/instance.py:225 +#: vm/models/instance.py:238 msgid "time of delete" msgstr "törlés ideje" -#: vm/models/instance.py:226 +#: vm/models/instance.py:239 msgid "Proposed time of automatic deletion." msgstr "Automatikus törlés kijelölt ideje." -#: vm/models/instance.py:229 +#: vm/models/instance.py:242 msgid "Time stamp of successful boot report." msgstr "A gép sikeres indításjelzésének ideje." -#: vm/models/instance.py:231 +#: vm/models/instance.py:244 msgid "active since" msgstr "aktív ezóta" -#: vm/models/instance.py:234 +#: vm/models/instance.py:247 msgid "Current hypervisor of this instance." msgstr "A példány jelenlegi hypervisorja." -#: vm/models/instance.py:235 +#: vm/models/instance.py:248 msgid "host node" msgstr "csomópont" -#: vm/models/instance.py:237 +#: vm/models/instance.py:250 msgid "Set of mounted disks." msgstr "1Csatolt lemezek halmaza." -#: vm/models/instance.py:240 +#: vm/models/instance.py:253 msgid "TCP port where VNC console listens." msgstr "Az a TCP port, amelyen a VNC konzol hallgat." -#: vm/models/instance.py:241 +#: vm/models/instance.py:254 msgid "vnc_port" msgstr "VNC port" -#: vm/models/instance.py:245 +#: vm/models/instance.py:258 msgid "The virtual machine's time of destruction." msgstr "A virtuális gép megsemmisítésének ideje." -#: vm/models/instance.py:255 +#: vm/models/instance.py:268 msgid "Can access the graphical console of a VM." msgstr "Elérheti a VM grafikus konzolját." -#: vm/models/instance.py:256 +#: vm/models/instance.py:269 msgid "Can change resources of a running VM." msgstr "Megváltoztathatja a VM erőforrásait." -#: vm/models/instance.py:257 +#: vm/models/instance.py:270 msgid "Can change resources of a new VM." msgstr "Megválaszthatja egy új VM erőforrásait." -#: vm/models/instance.py:258 +#: vm/models/instance.py:271 +msgid "Can create a new VM." +msgstr "Létrehozhat új VM-et." + +#: vm/models/instance.py:272 msgid "Can configure port forwards." msgstr "Beállíthat porttovábbításokat." -#: vm/models/instance.py:261 +#: vm/models/instance.py:273 +msgid "Can recover a destroyed VM." +msgstr "Visszaállíthat egy megsemmisített VM-et." + +#: vm/models/instance.py:274 +msgid "Can change VM state to NOSTATE." +msgstr "Átállíthatja a VM állapotát NOSTATE-re." + +#: vm/models/instance.py:277 msgid "instances" msgstr "példányok" -#: vm/models/instance.py:568 -msgid "None" -msgstr "Nincs" +#: vm/models/instance.py:289 +#, python-format +msgid "Instance %(instance)s has already been destroyed." +msgstr "%(instance)s példány már meg van semmisítve." + +#: vm/models/instance.py:293 +#, python-format +msgid "" +"Current state (%(state)s) of instance %(instance)s is inappropriate for the " +"invoked operation." +msgstr "" +"A(z) %(instance)s példány aktuális állapota (%(state)s) nem megfelelő a " +"választott művelethez." + +#: vm/models/instance.py:367 +msgid "create instance" +msgstr "példány létrehozása" -#: vm/models/instance.py:667 +#: vm/models/instance.py:445 #, python-format -msgid "%s expiring soon" -msgstr "%s hamarosan lejár" +msgid "vm state changed to %(state)s" +msgstr "VM állapota erre változott: %(state)s" -#: vm/models/network.py:40 +#: vm/models/instance.py:648 +#, python-format +msgid "" +"Your instance %(instance)s is going to expire. It " +"will be suspended at %(suspend)s and destroyed at %(delete)s. Please, either " +"renew or destroy it now." +msgstr "" +"%(instance)s gépe hamarosan lejár:\n" +"%(suspend)s időpontban felfüggesztésre, %(delete)s időpontban törlésre " +"kerül. Kérjük, újítsa meg vagy törölje most." + +#: vm/models/instance.py:660 +#, python-format +msgid "" +"%(failed)s notifications failed and %(success) succeeded. Failed ones are: " +"%(faileds)s." +msgstr "" +"%(failed)s értesítés sikertelen és %(success) sikeres. A sikertelenek: " +"%(faileds)s." + +#: vm/models/instance.py:662 +#, python-format +msgid "" +"%(failed)s notifications failed and %(success) succeeded. Failed ones are: " +"%(faileds_ex)s." +msgstr "" +"%(failed)s értesítés sikertelen és %(success) sikeres. A sikertelenek: " +"%(faileds_ex)s." + +#: vm/models/instance.py:670 +#, python-format +msgid "%(success)s notifications succeeded." +msgstr "%(success)s sikeres értesítés." + +#: vm/models/instance.py:675 +msgid "notify owner about expiration" +msgstr "tulaj értesítése a lejáratról" + +#: vm/models/instance.py:683 +#, python-format +msgid "%(instance)s expiring soon" +msgstr "%(instance)s hamarosan lejár" + +#: vm/models/network.py:41 msgid "Network the interface belongs to." msgstr "Az a hálózat, amelyhez a példány tartozik." -#: vm/models/network.py:42 +#: vm/models/network.py:43 msgid "If a firewall host (i.e. IP address association) should be generated." msgstr "Tűzfal host generálása (IP cím hozzárendelése)." -#: vm/models/network.py:46 +#: vm/models/network.py:47 msgid "Template the interface template belongs to." msgstr "Sablon, amelyhez az interfészsablon tartozik." -#: vm/models/network.py:53 +#: vm/models/network.py:54 msgid "interface template" msgstr "interfészsablon" -#: vm/models/network.py:54 +#: vm/models/network.py:55 msgid "interface templates" msgstr "interfészsablonok" +#: vm/models/network.py:125 vm/models/network.py:130 +msgid "allocate IP address" +msgstr "IP cím foglalása" + +#: vm/models/network.py:136 +msgid "Interface successfully created." +msgstr "Az interfész létrehozásra került." + +#: vm/models/network.py:137 +#, python-format +msgid "" +"Interface successfully created. New addresses: ipv4: %(ip4)s, ipv6: %(ip6)s, " +"vlan: %(vlan)s." +msgstr "" +"Az interfész létrehozásra került.Az új címek: %(ip4)s (ipv4), %(ip6)s " +"(ipv6). Vlan: %(vlan)s." + #: vm/models/node.py:63 msgid "Human readable name of node." msgstr "A csomópont olvasható neve." @@ -3208,31 +4136,185 @@ msgstr "túlfoglalási arány" msgid "The ratio of total memory with to without overcommit." msgstr "Az összes memória és a túlfoglalható memória aránya." -#: vm/models/node.py:124 +#: vm/models/node.py:125 msgid "offline" msgstr "nem elérhető" -#: vm/models/node.py:125 +#: vm/models/node.py:126 msgid "disabled" msgstr "tiltva" -#: vm/models/node.py:126 +#: vm/models/node.py:127 msgid "missing" msgstr "eltűnt" -#: vm/models/node.py:127 +#: vm/models/node.py:128 msgid "online" msgstr "elérhető" +#: vm/models/node.py:145 vm/models/node.py:149 +msgid "disable node" +msgstr "csomópont tiltása" + +#: vm/tasks/local_agent_tasks.py:35 +msgid "cleanup" +msgstr "takarítás" + +#: vm/tasks/local_agent_tasks.py:38 +msgid "restart networking" +msgstr "hálózat újratöltése" + +#: vm/tasks/local_agent_tasks.py:41 +msgid "change password" +msgstr "jelszóváltoztatás" + +#: vm/tasks/local_agent_tasks.py:43 +msgid "set time" +msgstr "óra beállítása" + +#: vm/tasks/local_agent_tasks.py:46 +msgid "set hostname" +msgstr "gépnév beállítása" + +#: vm/tasks/local_agent_tasks.py:80 +msgid "agent" +msgstr "ügynök" + +#: vm/tasks/local_agent_tasks.py:83 +msgid "starting" +msgstr "indítás" + +#: vm/tasks/local_agent_tasks.py:90 +#, python-format +msgid "update to %(version)s" +msgstr "frissítés erre: %(version)s" + +#: vm/tasks/local_agent_tasks.py:106 +msgid "start access server" +msgstr "távoli elérés indítása" + +#: vm/tasks/local_agent_tasks.py:138 +msgid "stopping" +msgstr "leállítás" + #: vm/tasks/local_periodic_tasks.py:51 #, python-format -msgid "%s destroyed" -msgstr "%s megsemmisítve" +msgid "%(instance)s destroyed" +msgstr "%(instance)s megsemmisítve" -#: vm/tasks/local_periodic_tasks.py:63 +#: vm/tasks/local_periodic_tasks.py:53 #, python-format -msgid "%s suspended" -msgstr "%s felfüggesztve" +msgid "" +"Your instance %(instance)s has been destroyed due to " +"expiration." +msgstr "" +"%(instance)s gépe megsemmisítésre került, mivel " +"lejárt." + +#: vm/tasks/local_periodic_tasks.py:65 +#, python-format +msgid "%(instance)s suspended" +msgstr "%(instance)s felfüggesztve" + +#: vm/tasks/local_periodic_tasks.py:67 +#, python-format +msgid "" +"Your instance %(instance)s has been suspended due to " +"expiration. You can resume or destroy it." +msgstr "" +"%(instance)s gépe felfüggesztésre került, mivel " +"lejárt. Felébresztheti vagy megsemmisítheti." + +#: vm/tests/test_models.py:213 +msgid "x" +msgstr "x" + +#~ msgid "VM successfully deleted." +#~ msgstr "A VM törlésre került." + +#~ msgid "create %(size)s disk" +#~ msgstr "lemez létrehozása (%(size)s)" + +#~ msgid "You have to either specify size or URL" +#~ msgstr "A méret vagy az URL megadása kötelező." + +#~ msgid "Global" +#~ msgstr "Általános" + +#~ msgid "Disk size (for example: 20GB, 1500MB)" +#~ msgstr "Lemezméret (például 20GB vagy 1500MB)" + +#~ msgid "URL to an ISO image" +#~ msgstr "ISO lemezkép URL-je" + +#~ msgid "Either specify the size for an empty disk or a URL to an ISO image!" +#~ msgstr "" +#~ "Az üres lemez méretének vagy egy ISO lemezkép URL-jének megadása kötelező." + +#~ msgid "Change language" +#~ msgstr "Válasszon nyelvet" + +#~ msgid "Password changed!" +#~ msgstr "A jelszó megváltoztatva." + +#~ msgid "Resources successfully updated!" +#~ msgstr "Az erőforrások megváltoztatásra kerültek." + +#~ msgid "Successfully added new interface!" +#~ msgstr "Az interfész hozzáadása sikeres." + +#~ msgid "Failed to perform requested action." +#~ msgstr "A kért művelet végrehajtása meghiúsult." + +#~ msgid "This token is invalid." +#~ msgstr "A token érvénytelen." + +#~ msgid "Virtual machine is successfully renewed." +#~ msgstr "A virtuális gép megújításra került." + +#~ msgid "Disk successfully added!" +#~ msgstr "A lemez hozzáadásra került." + +#~ msgid "Disk download started!" +#~ msgstr "A lemez letöltése megkezdődött." + +#~ msgid "Language selection" +#~ msgstr "Nyelvválasztás" + +#~ msgid "new base vm" +#~ msgstr "alap VM létrehozása" + +#~ msgid "Are you sure?" +#~ msgstr "Biztos benne?" + +#~ msgid "Accept" +#~ msgstr "Elfogadás" + +#~ msgid "" +#~ "\n" +#~ "Please, either renew or destroy\n" +#~ "it now.\n" +#~ msgstr "" +#~ "\n" +#~ "Újítsa meg vagy törölje\n" +#~ "most.\n" + +#~ msgid "Add new network interface!" +#~ msgstr "Új hálózati interfész hozzáadása." + +#~ msgid "Add new disk" +#~ msgstr "Lemez hozzáadása" + +#~ msgid "Disk this activity works on." +#~ msgstr "A lemez, amelyre a művelet vonatkozik." + +#~ msgid "Add the specified disk to the VM." +#~ msgstr "A megadott lemez hozzáadása a VM-hez." + +#~ msgid "None" +#~ msgstr "Nincs" #~ msgid "External" #~ msgstr "Külső" @@ -3246,9 +4328,6 @@ msgstr "%s felfüggesztve" #~ msgid "The template has been created, you can now add disks to it!" #~ msgstr "A sablon létrehozásra került, hozzáadhat lemezeket." -#~ msgid "Migrate %(name)s" -#~ msgstr "%(name)s migrálása" - #~ msgid "You didn't select a node!" #~ msgstr "Nem választott ki csomópontot." diff --git a/circle/locale/hu/LC_MESSAGES/djangojs.po b/circle/locale/hu/LC_MESSAGES/djangojs.po index 1252b7e..d5ddbe6 100644 --- a/circle/locale/hu/LC_MESSAGES/djangojs.po +++ b/circle/locale/hu/LC_MESSAGES/djangojs.po @@ -1,14 +1,14 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # -# , 2014. +# Mate Ory , 2014. msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-05-07 14:25+0200\n" -"PO-Revision-Date: 2014-05-07 15:32+0200\n" -"Last-Translator: Mate Ory \n" +"POT-Creation-Date: 2014-07-31 13:20+0200\n" +"PO-Revision-Date: 2014-07-31 14:30+0200\n" +"Last-Translator: Mate Ory \n" "Language-Team: Hungarian \n" "Language: en_US\n" "MIME-Version: 1.0\n" @@ -17,105 +17,144 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 1.5\n" -#: dashboard/static/dashboard/dashboard.js:54 +#: dashboard/static/dashboard/dashboard.js:68 +#: static_collected/dashboard/dashboard.js:68 msgid "Select an option to proceed!" msgstr "Válasszon a folytatáshoz." +#: dashboard/static/dashboard/dashboard.js:258 +#: dashboard/static/dashboard/dashboard.js:305 +#: dashboard/static/dashboard/dashboard.js:315 +#: static_collected/dashboard/dashboard.js:257 +#: static_collected/dashboard/dashboard.js:304 +#: static_collected/dashboard/dashboard.js:314 +msgid "No result" +msgstr "Nincs eredmény" + +#: dashboard/static/dashboard/profile.js:18 +#: static_collected/dashboard/profile.js:18 +msgid "You have no permission to change this profile." +msgstr "Nincs jogosultsága a profil módosításához." + +#: dashboard/static/dashboard/profile.js:20 +#: static_collected/dashboard/profile.js:20 +msgid "Unknown error." +msgstr "Ismeretlen hiba." + #: dashboard/static/dashboard/vm-tour.js:20 +#: static_collected/dashboard/vm-tour.js:20 msgid "Prev" msgstr "Vissza" #: dashboard/static/dashboard/vm-tour.js:22 +#: static_collected/dashboard/vm-tour.js:22 msgid "Next" msgstr "Tovább" #: dashboard/static/dashboard/vm-tour.js:26 +#: static_collected/dashboard/vm-tour.js:26 msgid "End tour" msgstr "Befejezés" #: dashboard/static/dashboard/vm-tour.js:33 +#: static_collected/dashboard/vm-tour.js:33 msgid "Template Tutorial Tour" msgstr "Sablon-kalauz" #: dashboard/static/dashboard/vm-tour.js:34 +#: static_collected/dashboard/vm-tour.js:34 msgid "" "Welcome to the template tutorial. In this quick tour, we gonna show you how " "to do the steps described above." msgstr "" -"Üdvözöli a sablon-kalauz. A túra során bemutatjuk, hogyan végezze el " -"a fenti lépéseket." +"Üdvözöli a sablon-kalauz. A túra során bemutatjuk, hogyan végezze el a fenti " +"lépéseket." #: dashboard/static/dashboard/vm-tour.js:35 +#: static_collected/dashboard/vm-tour.js:35 msgid "" "For the next tour step press the \"Next\" button or the right arrow (or " "\"Back\" button/left arrow for the previous step)." msgstr "" -"A következő lépéshez kattintson a \"Tovább\" gombra vagy használja " -"a nyílbillentyűket." +"A következő lépéshez kattintson a \"Tovább\" gombra vagy használja a " +"nyílbillentyűket." #: dashboard/static/dashboard/vm-tour.js:36 +#: static_collected/dashboard/vm-tour.js:36 msgid "" "During the tour please don't try the functions because it may lead to " "graphical glitches, however " msgstr "A túra során még ne próbálja ki a bemutatott funkciókat." #: dashboard/static/dashboard/vm-tour.js:45 +#: static_collected/dashboard/vm-tour.js:45 msgid "Home tab" msgstr "Kezdőoldal" #: dashboard/static/dashboard/vm-tour.js:46 +#: static_collected/dashboard/vm-tour.js:46 msgid "" "In this tab you can tag your virtual machine and modify the name and " "description." msgstr "" -"Ezen a lapon címkéket adhat a virtuális géphez, vagy módosíthatja " -"a nevét, leírását." +"Ezen a lapon címkéket adhat a virtuális géphez, vagy módosíthatja a nevét, " +"leírását." #: dashboard/static/dashboard/vm-tour.js:55 +#: static_collected/dashboard/vm-tour.js:55 msgid "Resources tab" msgstr "Erőforrások lap" #: dashboard/static/dashboard/vm-tour.js:58 +#: static_collected/dashboard/vm-tour.js:58 msgid "" "On the resources tab you can edit the CPU/RAM options and add/remove disks!" msgstr "" "Az erőforrások lapon szerkesztheti a CPU/memória-beállításokat, valamint " -"hozzáadhat " -"és törölhet lemezeket." +"hozzáadhat és törölhet lemezeket." #: dashboard/static/dashboard/vm-tour.js:68 +#: static_collected/dashboard/vm-tour.js:68 msgid "Resources" msgstr "Erőforrások" #: dashboard/static/dashboard/vm-tour.js:69 +#: static_collected/dashboard/vm-tour.js:69 msgid "CPU priority" msgstr "CPU prioritás" #: dashboard/static/dashboard/vm-tour.js:69 +#: static_collected/dashboard/vm-tour.js:69 msgid "higher is better" msgstr "a nagyobb érték a jobb" #: dashboard/static/dashboard/vm-tour.js:70 +#: static_collected/dashboard/vm-tour.js:70 msgid "CPU count" msgstr "CPU-k száma" #: dashboard/static/dashboard/vm-tour.js:70 +#: static_collected/dashboard/vm-tour.js:70 msgid "number of CPU cores." msgstr "A CPU-magok száma." #: dashboard/static/dashboard/vm-tour.js:71 +#: static_collected/dashboard/vm-tour.js:71 msgid "RAM amount" msgstr "RAM mennyiség" #: dashboard/static/dashboard/vm-tour.js:71 +#: static_collected/dashboard/vm-tour.js:71 msgid "amount of RAM." msgstr "a memória mennyisége." #: dashboard/static/dashboard/vm-tour.js:81 +#: static_collected/dashboard/vm-tour.js:81 msgid "Disks" msgstr "Lemezek" #: dashboard/static/dashboard/vm-tour.js:82 +#: static_collected/dashboard/vm-tour.js:82 msgid "" "You can add empty disks, download new ones and remove existing ones here." msgstr "" @@ -123,65 +162,75 @@ msgstr "" "meglévőket." #: dashboard/static/dashboard/vm-tour.js:92 +#: static_collected/dashboard/vm-tour.js:92 msgid "Network tab" msgstr "Hálózat lap" #: dashboard/static/dashboard/vm-tour.js:93 +#: static_collected/dashboard/vm-tour.js:93 msgid "You can add new network interfaces or remove existing ones here." msgstr "Hozzáadhat új hálózati interfészeket, vagy törölheti a meglévőket." #: dashboard/static/dashboard/vm-tour.js:102 +#: static_collected/dashboard/vm-tour.js:102 msgid "Deploy" msgstr "Indítás" #: dashboard/static/dashboard/vm-tour.js:105 +#: static_collected/dashboard/vm-tour.js:105 msgid "Deploy the virtual machine." msgstr "A virtuális gép elindítása." #: dashboard/static/dashboard/vm-tour.js:110 +#: static_collected/dashboard/vm-tour.js:110 msgid "Connect" msgstr "Csatlakozás" #: dashboard/static/dashboard/vm-tour.js:113 +#: static_collected/dashboard/vm-tour.js:113 msgid "Use the connection string or connect with your choice of client!" msgstr "Használja a megadott parancsot, vagy kedvenc kliensét." #: dashboard/static/dashboard/vm-tour.js:120 +#: static_collected/dashboard/vm-tour.js:120 msgid "Customize the virtual machine" msgstr "Szabja testre a gépet" #: dashboard/static/dashboard/vm-tour.js:121 +#: static_collected/dashboard/vm-tour.js:121 msgid "" "After you have connected to the virtual machine do your modifications then " "log off." msgstr "" -"Miután csatlakozott, végezze el a szükséges módosításokat, majd " -"jelentkezzen ki." +"Miután csatlakozott, végezze el a szükséges módosításokat, majd jelentkezzen " +"ki." #: dashboard/static/dashboard/vm-tour.js:126 +#: static_collected/dashboard/vm-tour.js:126 msgid "Save as" msgstr "Mentés sablonként" #: dashboard/static/dashboard/vm-tour.js:129 +#: static_collected/dashboard/vm-tour.js:129 msgid "" "Press the \"Save as template\" button and wait until the activity finishes." msgstr "" -"Kattintson a „mentés sablonként” gombra, majd várjon, amíg a lemez " -"mentése elkészül." +"Kattintson a „mentés sablonként” gombra, majd várjon, amíg a lemez mentése " +"elkészül." #: dashboard/static/dashboard/vm-tour.js:135 +#: static_collected/dashboard/vm-tour.js:135 msgid "Finish" msgstr "Befejezés" #: dashboard/static/dashboard/vm-tour.js:138 +#: static_collected/dashboard/vm-tour.js:138 msgid "" "This is the last message, if something is not clear you can do the the tour " "again!" -msgstr "" -"A túra véget ért. Ha valami nem érthető, újrakezdheti az " -"útmutatót." +msgstr "A túra véget ért. Ha valami nem érthető, újrakezdheti az útmutatót." -#: network/static/js/host.js:10 +#: network/static/js/host.js:10 static_collected/js/host.js:10 msgid "" "Are you sure you want to remove host group \"%(group)s\" " "from \"%(host)s\"?" @@ -189,19 +238,184 @@ msgstr "" "Biztosan törli a(z)„%(host)s” gépet a(z) " "„%(group)s” gépcsoportból?" -#: network/static/js/host.js:13 +#: network/static/js/host.js:13 static_collected/js/host.js:13 msgid "Are you sure you want to delete this rule?" msgstr "Biztosan törli ezt a szabályt?" #: network/static/js/host.js:20 network/static/js/switch-port.js:14 +#: static_collected/admin/js/admin/DateTimeShortcuts.js:95 +#: static_collected/admin/js/admin/DateTimeShortcuts.js:208 +#: static_collected/js/host.js:20 static_collected/js/switch-port.js:14 msgid "Cancel" msgstr "Mégsem" #: network/static/js/host.js:25 network/static/js/switch-port.js:19 +#: static_collected/admin/js/SelectFilter2.js:69 +#: static_collected/js/host.js:25 static_collected/js/switch-port.js:19 msgid "Remove" msgstr "Eltávolítás" -#: network/static/js/switch-port.js:8 +#: network/static/js/switch-port.js:8 static_collected/js/switch-port.js:8 msgid "Are you sure you want to delete this device?" msgstr "Biztosan törli ezt az eszközt?" +#: static_collected/admin/js/SelectFilter2.js:45 +#, c-format +msgid "Available %s" +msgstr "Elérhető %s" + +#: static_collected/admin/js/SelectFilter2.js:46 +#, c-format +msgid "" +"This is the list of available %s. You may choose some by selecting them in " +"the box below and then clicking the \"Choose\" arrow between the two boxes." +msgstr "" +"Ez az elérhető %s listája. Úgy választhat közülük, hogy rákattint az alábbi " +"dobozban, és megnyomja a dobozok közti \"Választás\" nyilat." + +#: static_collected/admin/js/SelectFilter2.js:53 +#, c-format +msgid "Type into this box to filter down the list of available %s." +msgstr "Írjon a mezőbe az elérhető %s szűréséhez." + +#: static_collected/admin/js/SelectFilter2.js:57 +msgid "Filter" +msgstr "Szűrő" + +#: static_collected/admin/js/SelectFilter2.js:61 +msgid "Choose all" +msgstr "Mindet kijelölni" + +#: static_collected/admin/js/SelectFilter2.js:61 +#, c-format +msgid "Click to choose all %s at once." +msgstr "Kattintson az összes %s kiválasztásához." + +#: static_collected/admin/js/SelectFilter2.js:67 +msgid "Choose" +msgstr "Választás" + +#: static_collected/admin/js/SelectFilter2.js:75 +#, c-format +msgid "Chosen %s" +msgstr "%s kiválasztva" + +#: static_collected/admin/js/SelectFilter2.js:76 +#, c-format +msgid "" +"This is the list of chosen %s. You may remove some by selecting them in the " +"box below and then clicking the \"Remove\" arrow between the two boxes." +msgstr "" +"Ez a kiválasztott %s listája. Eltávolíthat közülük, ha rákattint, majd a két " +"doboz közti \"Eltávolítás\" nyílra kattint." + +#: static_collected/admin/js/SelectFilter2.js:80 +msgid "Remove all" +msgstr "Összes törlése" + +#: static_collected/admin/js/SelectFilter2.js:80 +#, c-format +msgid "Click to remove all chosen %s at once." +msgstr "Kattintson az összes %s eltávolításához." + +#: static_collected/admin/js/actions.js:18 +#: static_collected/admin/js/actions.min.js:1 +msgid "%(sel)s of %(cnt)s selected" +msgid_plural "%(sel)s of %(cnt)s selected" +msgstr[0] "%(sel)s/%(cnt)s kijelölve" +msgstr[1] "%(sel)s/%(cnt)s kijelölve" + +#: static_collected/admin/js/actions.js:109 +#: static_collected/admin/js/actions.min.js:5 +msgid "" +"You have unsaved changes on individual editable fields. If you run an " +"action, your unsaved changes will be lost." +msgstr "" +"Még el nem mentett módosításai vannak egyes szerkeszthető mezőkön. Ha most " +"futtat egy műveletet, akkor a módosítások elvesznek. " + +#: static_collected/admin/js/actions.js:121 +#: static_collected/admin/js/actions.min.js:5 +msgid "" +"You have selected an action, but you haven't saved your changes to " +"individual fields yet. Please click OK to save. You'll need to re-run the " +"action." +msgstr "" +"Kiválasztott egy műveletet, de nem mentette az egyes mezőkhöz kapcsolódó " +"módosításait. Kattintson az OK gombra a mentéshez. Újra kell futtatnia az " +"műveletet." + +#: static_collected/admin/js/actions.js:123 +#: static_collected/admin/js/actions.min.js:6 +msgid "" +"You have selected an action, and you haven't made any changes on individual " +"fields. You're probably looking for the Go button rather than the Save " +"button." +msgstr "" +"Kiválasztott egy műveletet, és nem módosított egyetlen mezőt sem. " +"Feltehetően a Mehet gombot keresi a Mentés helyett." + +#: static_collected/admin/js/calendar.js:8 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"január február március április május június július augusztus szeptember " +"október november december" + +#: static_collected/admin/js/calendar.js:9 +msgid "S M T W T F S" +msgstr "V H K Sz Cs P Szo" + +#: static_collected/admin/js/collapse.js:8 +#: static_collected/admin/js/collapse.js:19 +#: static_collected/admin/js/collapse.min.js:1 +msgid "Show" +msgstr "Mutat" + +#: static_collected/admin/js/collapse.js:16 +#: static_collected/admin/js/collapse.min.js:1 +msgid "Hide" +msgstr "Elrejt" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:52 +#: static_collected/admin/js/admin/DateTimeShortcuts.js:88 +msgid "Now" +msgstr "Most" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:56 +msgid "Clock" +msgstr "Óra" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:84 +msgid "Choose a time" +msgstr "Válassza ki az időt" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:89 +msgid "Midnight" +msgstr "Éjfél" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:90 +msgid "6 a.m." +msgstr "Reggel 6 óra" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:91 +msgid "Noon" +msgstr "Dél" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:148 +#: static_collected/admin/js/admin/DateTimeShortcuts.js:201 +msgid "Today" +msgstr "Ma" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:152 +msgid "Calendar" +msgstr "Naptár" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:199 +msgid "Yesterday" +msgstr "Tegnap" + +#: static_collected/admin/js/admin/DateTimeShortcuts.js:203 +msgid "Tomorrow" +msgstr "Holnap" diff --git a/circle/locale/hu/LC_MESSAGES/lokalize-scripts/scripts.rc b/circle/locale/hu/LC_MESSAGES/lokalize-scripts/scripts.rc new file mode 100644 index 0000000..779cc18 --- /dev/null +++ b/circle/locale/hu/LC_MESSAGES/lokalize-scripts/scripts.rc @@ -0,0 +1,14 @@ + diff --git a/circle/storage/migrations/0015_set_is_ready_to_base_images.py b/circle/storage/migrations/0015_set_is_ready_to_base_images.py new file mode 100644 index 0000000..2c4af52 --- /dev/null +++ b/circle/storage/migrations/0015_set_is_ready_to_base_images.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +import logging + +logging.basicConfig(filename='/var/tmp/0015_disk_migration.log',level=logging.INFO) +logger = logging.getLogger() + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + # Note: Don't use "from appname.models import ModelName". + # Use orm.ModelName to refer to models in this application, + # and orm['appname.ModelName'] for models in other applications. + disks = orm.Disk + for disk in disks.objects.all(): + if disk.base is None and disk.destroyed is None: + logger.info("Set disk %s (%s) with filename: %s to ready.", + disk.name, disk.pk, disk.filename) + disk.is_ready = True + disk.save() + + + def backwards(self, orm): + "Write your backwards methods here." + + models = { + u'storage.datastore': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'DataStore'}, + 'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}) + }, + u'storage.disk': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'Disk'}, + 'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'derivatives'", 'null': 'True', 'to': u"orm['storage.Disk']"}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['storage.DataStore']"}), + 'destroyed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'dev_num': ('django.db.models.fields.CharField', [], {'default': "u'a'", 'max_length': '1'}), + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_ready': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'size': ('sizefield.models.FileSizeField', [], {'default': 'None', 'null': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '10'}) + } + } + + complete_apps = ['storage'] + symmetrical = True diff --git a/circle/storage/models.py b/circle/storage/models.py index a4d7fe6..54a1eb8 100644 --- a/circle/storage/models.py +++ b/circle/storage/models.py @@ -27,13 +27,15 @@ from celery.contrib.abortable import AbortableAsyncResult from django.db.models import (Model, BooleanField, CharField, DateTimeField, ForeignKey) from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, ugettext_noop from model_utils.models import TimeStampedModel from sizefield.models import FileSizeField from .tasks import local_tasks, storage_tasks from celery.exceptions import TimeoutError -from common.models import WorkerNotFound +from common.models import ( + WorkerNotFound, HumanReadableException, humanize_exception +) logger = logging.getLogger(__name__) @@ -104,43 +106,73 @@ class Disk(TimeStampedModel): ('create_empty_disk', _('Can create an empty disk.')), ('download_disk', _('Can download a disk.'))) - class WrongDiskTypeError(Exception): - - def __init__(self, type, message=None): - if message is None: - message = ("Operation can't be invoked on a disk of type '%s'." - % type) - - Exception.__init__(self, message) - - self.type = type - - class DiskInUseError(Exception): - - def __init__(self, disk, message=None): - if message is None: - message = ("The requested operation can't be performed on " - "disk '%s (%s)' because it is in use." % - (disk.name, disk.filename)) - - Exception.__init__(self, message) - - self.disk = disk - - class DiskIsNotReady(Exception): - - """ Exception for operations that need a deployed disk. - """ - - def __init__(self, disk, message=None): - if message is None: - message = ("The requested operation can't be performed on " - "disk '%s (%s)' because it has never been" - "deployed." % (disk.name, disk.filename)) - - Exception.__init__(self, message) - - self.disk = disk + class DiskError(HumanReadableException): + admin_message = None + + def __init__(self, disk, params=None, level=None, **kwargs): + kwargs.update(params or {}) + self.disc = kwargs["disk"] = disk + super(Disk.DiskError, self).__init__( + level, self.message, self.admin_message or self.message, + kwargs) + + class WrongDiskTypeError(DiskError): + message = ugettext_noop("Operation can't be invoked on disk " + "'%(name)s' of type '%(type)s'.") + + admin_message = ugettext_noop( + "Operation can't be invoked on disk " + "'%(name)s' (%(pk)s) of type '%(type)s'.") + + def __init__(self, disk, params=None, **kwargs): + super(Disk.WrongDiskTypeError, self).__init__( + disk, params, type=disk.type, name=disk.name, pk=disk.pk) + + class DiskInUseError(DiskError): + message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' because it is in use.") + + admin_message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' (%(pk)s) because it is in use.") + + def __init__(self, disk, params=None, **kwargs): + super(Disk.DiskInUseError, self).__init__( + disk, params, name=disk.name, pk=disk.pk) + + class DiskIsNotReady(DiskError): + message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' because it has never been deployed.") + + admin_message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' (%(pk)s) [%(filename)s] because it has never been" + "deployed.") + + def __init__(self, disk, params=None, **kwargs): + super(Disk.DiskIsNotReady, self).__init__( + disk, params, name=disk.name, pk=disk.pk, + filename=disk.filename) + + class DiskBaseIsNotReady(DiskError): + message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' because its base has never been deployed.") + + admin_message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' (%(pk)s) [%(filename)s] because its base " + "'%(b_name)s' (%(b_pk)s) [%(b_filename)s] has never been" + "deployed.") + + def __init__(self, disk, params=None, **kwargs): + base = kwargs.get('base') + super(Disk.DiskBaseIsNotReady, self).__init__( + disk, params, name=disk.name, pk=disk.pk, + filename=disk.filename, b_name=base.name, + b_pk=base.pk, b_filename=base.filename) @property def path(self): @@ -191,7 +223,7 @@ class Disk(TimeStampedModel): return { 'qcow2-norm': 'virtio', 'qcow2-snap': 'virtio', - 'iso': 'scsi', + 'iso': 'ide', 'raw-ro': 'virtio', 'raw-rw': 'virtio', }[self.type] @@ -240,7 +272,7 @@ class Disk(TimeStampedModel): } if self.type not in type_mapping.keys(): - raise self.WrongDiskTypeError(self.type) + raise self.WrongDiskTypeError(self) new_type = type_mapping[self.type] @@ -313,6 +345,8 @@ class Disk(TimeStampedModel): if self.is_ready: return True + if self.base and not self.base.is_ready: + raise self.DiskBaseIsNotReady(self, base=self.base) queue_name = self.get_remote_queue_name('storage', priority="fast") disk_desc = self.get_disk_desc() if self.base is not None: @@ -372,10 +406,11 @@ class Disk(TimeStampedModel): try: result = remote.get(timeout=5) break - except TimeoutError: + except TimeoutError as e: if task is not None and task.is_aborted(): AbortableAsyncResult(remote.id).abort() - raise Exception("Download aborted by user.") + raise humanize_exception(ugettext_noop( + "Operation aborted by user."), e) disk.size = result['size'] disk.type = result['type'] disk.is_ready = True @@ -417,7 +452,7 @@ class Disk(TimeStampedModel): 'iso': ("iso", self), } if self.type not in mapping.keys(): - raise self.WrongDiskTypeError(self.type) + raise self.WrongDiskTypeError(self) if self.is_in_use: raise self.DiskInUseError(self) @@ -433,7 +468,7 @@ class Disk(TimeStampedModel): disk = Disk.create(datastore=self.datastore, base=new_base, name=self.name, size=self.size, - type=new_type) + type=new_type, dev_num=self.dev_num) queue_name = self.get_remote_queue_name("storage", priority="slow") remote = storage_tasks.merge.apply_async(kwargs={ @@ -445,9 +480,12 @@ class Disk(TimeStampedModel): try: remote.get(timeout=5) break - except TimeoutError: + except TimeoutError as e: if task is not None and task.is_aborted(): AbortableAsyncResult(remote.id).abort() disk.destroy() - raise Exception("Save as aborted by use.") + raise humanize_exception(ugettext_noop( + "Operation aborted by user."), e) + disk.is_ready = True + disk.save() return disk diff --git a/circle/vm/models/common.py b/circle/vm/models/common.py index 9adf9d4..c86c0b1 100644 --- a/circle/vm/models/common.py +++ b/circle/vm/models/common.py @@ -18,6 +18,7 @@ from __future__ import absolute_import, unicode_literals from datetime import timedelta, datetime +from django.core.validators import MinValueValidator from django.db.models import Model, CharField, IntegerField, permalink from django.utils.translation import ugettext_lazy as _ from django.utils.timesince import timeuntil @@ -37,16 +38,20 @@ class BaseResourceConfigModel(Model): """ num_cores = IntegerField(verbose_name=_('number of cores'), help_text=_('Number of virtual CPU cores ' - 'available to the virtual machine.')) + 'available to the virtual machine.'), + validators=[MinValueValidator(0)]) ram_size = IntegerField(verbose_name=_('RAM size'), - help_text=_('Mebibytes of memory.')) + help_text=_('Mebibytes of memory.'), + validators=[MinValueValidator(0)]) max_ram_size = IntegerField(verbose_name=_('maximal RAM size'), help_text=_('Upper memory size limit ' - 'for balloning.')) + 'for balloning.'), + validators=[MinValueValidator(0)]) arch = CharField(max_length=10, verbose_name=_('architecture'), choices=ARCHITECTURES) priority = IntegerField(verbose_name=_('priority'), - help_text=_('CPU priority.')) + help_text=_('CPU priority.'), + validators=[MinValueValidator(0)]) class Meta: abstract = True diff --git a/circle/vm/models/instance.py b/circle/vm/models/instance.py index 4736de3..19b87da 100644 --- a/circle/vm/models/instance.py +++ b/circle/vm/models/instance.py @@ -41,7 +41,9 @@ from model_utils.models import TimeStampedModel, StatusModel from taggit.managers import TaggableManager from acl.models import AclBase -from common.models import create_readable +from common.models import ( + create_readable, HumanReadableException, humanize_exception +) from common.operations import OperatedMixin from ..tasks import vm_tasks, agent_tasks from .activity import (ActivityInProgressError, instance_activity, @@ -276,28 +278,26 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, verbose_name = _('instance') verbose_name_plural = _('instances') - class InstanceDestroyedError(Exception): + class InstanceError(HumanReadableException): - def __init__(self, instance, message=None): - if message is None: - message = ("The instance (%s) has already been destroyed." - % instance) + def __init__(self, instance, params=None, level=None, **kwargs): + kwargs.update(params or {}) + self.instance = kwargs["instance"] = instance + super(Instance.InstanceError, self).__init__( + level, self.message, self.message, kwargs) - Exception.__init__(self, message) + class InstanceDestroyedError(InstanceError): + message = ugettext_noop( + "Instance %(instance)s has already been destroyed.") - self.instance = instance + class WrongStateError(InstanceError): + message = ugettext_noop( + "Current state (%(state)s) of instance %(instance)s is " + "inappropriate for the invoked operation.") - class WrongStateError(Exception): - - def __init__(self, instance, message=None): - if message is None: - message = ("The instance's current state (%s) is " - "inappropriate for the invoked operation." - % instance.status) - - Exception.__init__(self, message) - - self.instance = instance + def __init__(self, instance, params=None, **kwargs): + super(Instance.WrongStateError, self).__init__( + instance, params, state=instance.status) def __unicode__(self): parts = (self.name, "(" + str(self.id) + ")") @@ -441,9 +441,12 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, def vm_state_changed(self, new_state): # log state change try: - act = InstanceActivity.create(code_suffix='vm_state_changed', - instance=self, - readable_name="vm state changed") + act = InstanceActivity.create( + code_suffix='vm_state_changed', + readable_name=create_readable( + ugettext_noop("vm state changed to %(state)s"), + state=new_state), + instance=self) except ActivityInProgressError: pass # discard state change if another activity is in progress. else: @@ -876,10 +879,11 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, while True: try: return remote.get(timeout=step) - except TimeoutError: + except TimeoutError as e: if task is not None and task.is_aborted(): AbortableAsyncResult(remote.id).abort() - raise Exception("Shutdown aborted by user.") + raise humanize_exception(ugettext_noop( + "Operation aborted by user."), e) def suspend_vm(self, timeout=230): queue_name = self.get_remote_queue_name('vm', 'slow') @@ -956,7 +960,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, for a in acts: if (latest == a.activity_code and - merged_acts[-1].result == a.result and + merged_acts[-1].result_data == a.result_data and a.finished and merged_acts[-1].finished and a.user == merged_acts[-1].user and (merged_acts[-1].finished - a.finished).days < 7 and @@ -974,3 +978,10 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, return vm_tasks.screenshot.apply_async(args=[self.vm_name], queue=queue_name ).get(timeout=timeout) + + def get_latest_activity_in_progress(self): + try: + return InstanceActivity.objects.filter( + instance=self, succeeded=None, parent=None).latest("started") + except InstanceActivity.DoesNotExist: + return None diff --git a/circle/vm/operations.py b/circle/vm/operations.py index b060519..085e587 100644 --- a/circle/vm/operations.py +++ b/circle/vm/operations.py @@ -24,9 +24,13 @@ from django.core.exceptions import PermissionDenied from django.utils import timezone from django.utils.translation import ugettext_lazy as _, ugettext_noop +from sizefield.utils import filesizeformat + from celery.exceptions import TimeLimitExceeded -from common.models import create_readable +from common.models import ( + create_readable, humanize_exception, HumanReadableException +) from common.operations import Operation, register_operation from .tasks.local_tasks import ( abortable_async_instance_operation, abortable_async_node_operation, @@ -45,6 +49,8 @@ class InstanceOperation(Operation): async_operation = abortable_async_instance_operation host_cls = Instance concurrency_check = True + accept_states = None + deny_states = None def __init__(self, instance): super(InstanceOperation, self).__init__(subject=instance) @@ -53,11 +59,26 @@ class InstanceOperation(Operation): def check_precond(self): if self.instance.destroyed_at: raise self.instance.InstanceDestroyedError(self.instance) + if self.accept_states: + if self.instance.status not in self.accept_states: + logger.debug("precond failed for %s: %s not in %s", + unicode(self.__class__), + unicode(self.instance.status), + unicode(self.accept_states)) + raise self.instance.WrongStateError(self.instance) + if self.deny_states: + if self.instance.status in self.deny_states: + logger.debug("precond failed for %s: %s in %s", + unicode(self.__class__), + unicode(self.instance.status), + unicode(self.accept_states)) + raise self.instance.WrongStateError(self.instance) def check_auth(self, user): if not self.instance.has_level(user, self.acl_level): - raise PermissionDenied("%s doesn't have the required ACL level." % - user) + raise humanize_exception(ugettext_noop( + "%(acl_level)s level is required for this operation."), + PermissionDenied(), acl_level=self.acl_level) super(InstanceOperation, self).check_auth(user=user) @@ -94,6 +115,7 @@ class AddInterfaceOperation(InstanceOperation): description = _("Add a new network interface for the specified VLAN to " "the VM.") required_perms = () + accept_states = ('STOPPED', 'PENDING', 'RUNNING') def rollback(self, net, activity): with activity.sub_activity( @@ -102,14 +124,11 @@ class AddInterfaceOperation(InstanceOperation): net.destroy() net.delete() - def check_precond(self): - super(AddInterfaceOperation, self).check_precond() - if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']: - raise self.instance.WrongStateError(self.instance) - def _operation(self, activity, user, system, vlan, managed=None): if not vlan.has_level(user, 'user'): - raise PermissionDenied() + raise humanize_exception(ugettext_noop( + "User acces to vlan %(vlan)s is required."), + PermissionDenied(), vlan=vlan) if managed is None: managed = vlan.managed @@ -118,7 +137,9 @@ class AddInterfaceOperation(InstanceOperation): if self.instance.is_running: try: - with activity.sub_activity('attach_network'): + with activity.sub_activity( + 'attach_network', + readable_name=ugettext_noop("attach network")): self.instance.attach_network(net) except Exception as e: if hasattr(e, 'libvirtError'): @@ -141,11 +162,7 @@ class CreateDiskOperation(InstanceOperation): name = _("create disk") description = _("Create empty disk for the VM.") required_perms = ('storage.create_empty_disk', ) - - def check_precond(self): - super(CreateDiskOperation, self).check_precond() - if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']: - raise self.instance.WrongStateError(self.instance) + accept_states = ('STOPPED', 'PENDING', 'RUNNING') def _operation(self, user, size, activity, name=None): from storage.models import Disk @@ -162,14 +179,21 @@ class CreateDiskOperation(InstanceOperation): self.instance.disks.add(disk) if self.instance.is_running: - with activity.sub_activity('deploying_disk'): + with activity.sub_activity( + 'deploying_disk', + readable_name=ugettext_noop("deploying disk") + ): disk.deploy() - with activity.sub_activity('attach_disk'): + with activity.sub_activity( + 'attach_disk', + readable_name=ugettext_noop("attach disk") + ): self.instance.attach_disk(disk) def get_activity_name(self, kwargs): - return create_readable(ugettext_noop("create %(size)s disk"), - size=kwargs['size']) + return create_readable( + ugettext_noop("create disk %(name)s (%(size)s)"), + size=filesizeformat(kwargs['size']), name=kwargs['name']) register_operation(CreateDiskOperation) @@ -183,11 +207,7 @@ class DownloadDiskOperation(InstanceOperation): abortable = True has_percentage = True required_perms = ('storage.download_disk', ) - - def check_precond(self): - super(DownloadDiskOperation, self).check_precond() - if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']: - raise self.instance.WrongStateError(self.instance) + accept_states = ('STOPPED', 'PENDING', 'RUNNING') def _operation(self, user, url, task, activity, name=None): activity.result = url @@ -206,7 +226,10 @@ class DownloadDiskOperation(InstanceOperation): # TODO iso (cd) hot-plug is not supported by kvm/guests if self.instance.is_running and disk.type not in ["iso"]: - with activity.sub_activity('attach_disk'): + with activity.sub_activity( + 'attach_disk', + readable_name=ugettext_noop("attach disk") + ): self.instance.attach_disk(disk) register_operation(DownloadDiskOperation) @@ -218,11 +241,7 @@ class DeployOperation(InstanceOperation): name = _("deploy") description = _("Deploy new virtual machine with network.") required_perms = () - - def check_precond(self): - super(DeployOperation, self).check_precond() - if self.instance.status in ['RUNNING', 'SUSPENDED']: - raise self.instance.WrongStateError(self.instance) + deny_states = ('SUSPENDED', 'RUNNING') def is_preferred(self): return self.instance.status in (self.instance.STATUS.STOPPED, @@ -262,7 +281,10 @@ class DeployOperation(InstanceOperation): "boot virtual machine")): self.instance.resume_vm(timeout=timeout) - self.instance.renew(parent_activity=activity) + try: + self.instance.renew(parent_activity=activity) + except: + pass register_operation(DeployOperation) @@ -323,6 +345,7 @@ class MigrateOperation(InstanceOperation): name = _("migrate") description = _("Live migrate running VM to another node.") required_perms = () + accept_states = ('RUNNING', ) def rollback(self, activity): with activity.sub_activity( @@ -330,11 +353,6 @@ class MigrateOperation(InstanceOperation): "redeploy network (rollback)")): self.instance.deploy_net() - def check_precond(self): - super(MigrateOperation, self).check_precond() - if self.instance.status not in ['RUNNING']: - raise self.instance.WrongStateError(self.instance) - def check_auth(self, user): if not user.is_superuser: raise PermissionDenied() @@ -384,11 +402,7 @@ class RebootOperation(InstanceOperation): name = _("reboot") description = _("Reboot virtual machine with Ctrl+Alt+Del signal.") required_perms = () - - def check_precond(self): - super(RebootOperation, self).check_precond() - if self.instance.status not in ['RUNNING']: - raise self.instance.WrongStateError(self.instance) + accept_states = ('RUNNING', ) def _operation(self, timeout=5): self.instance.reboot_vm(timeout=timeout) @@ -403,21 +417,24 @@ class RemoveInterfaceOperation(InstanceOperation): name = _("remove interface") description = _("Remove the specified network interface from the VM.") required_perms = () - - def check_precond(self): - super(RemoveInterfaceOperation, self).check_precond() - if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']: - raise self.instance.WrongStateError(self.instance) + accept_states = ('STOPPED', 'PENDING', 'RUNNING') def _operation(self, activity, user, system, interface): if self.instance.is_running: - with activity.sub_activity('detach_network'): + with activity.sub_activity( + 'detach_network', + readable_name=ugettext_noop("detach network") + ): self.instance.detach_network(interface) interface.shutdown() interface.destroy() interface.delete() + def get_activity_name(self, kwargs): + return create_readable(ugettext_noop("remove %(vlan)s interface"), + vlan=kwargs['interface'].vlan) + register_operation(RemoveInterfaceOperation) @@ -428,18 +445,24 @@ class RemoveDiskOperation(InstanceOperation): name = _("remove disk") description = _("Remove the specified disk from the VM.") required_perms = () - - def check_precond(self): - super(RemoveDiskOperation, self).check_precond() - if self.instance.status not in ['STOPPED', 'PENDING', 'RUNNING']: - raise self.instance.WrongStateError(self.instance) + accept_states = ('STOPPED', 'PENDING', 'RUNNING') def _operation(self, activity, user, system, disk): if self.instance.is_running and disk.type not in ["iso"]: - with activity.sub_activity('detach_disk'): + with activity.sub_activity( + 'detach_disk', + readable_name=ugettext_noop('detach disk') + ): self.instance.detach_disk(disk) - return self.instance.disks.remove(disk) + with activity.sub_activity( + 'destroy_disk', + readable_name=ugettext_noop('destroy disk') + ): + return self.instance.disks.remove(disk) + def get_activity_name(self, kwargs): + return create_readable(ugettext_noop('remove disk %(name)s'), + name=kwargs["disk"].name) register_operation(RemoveDiskOperation) @@ -450,11 +473,7 @@ class ResetOperation(InstanceOperation): name = _("reset") description = _("Reset virtual machine (reset button).") required_perms = () - - def check_precond(self): - super(ResetOperation, self).check_precond() - if self.instance.status not in ['RUNNING']: - raise self.instance.WrongStateError(self.instance) + accept_states = ('RUNNING', ) def _operation(self, timeout=5): self.instance.reset_vm(timeout=timeout) @@ -473,6 +492,7 @@ class SaveAsTemplateOperation(InstanceOperation): """) abortable = True required_perms = ('vm.create_template', ) + accept_states = ('RUNNING', 'PENDING', 'STOPPED') def is_preferred(self): return (self.instance.is_base and @@ -493,11 +513,6 @@ class SaveAsTemplateOperation(InstanceOperation): for disk in self.disks: disk.destroy() - def check_precond(self): - super(SaveAsTemplateOperation, self).check_precond() - if self.instance.status not in ['RUNNING', 'PENDING', 'STOPPED']: - raise self.instance.WrongStateError(self.instance) - def _operation(self, activity, user, system, timeout=300, name=None, with_shutdown=True, task=None, **kwargs): if with_shutdown: @@ -536,9 +551,13 @@ class SaveAsTemplateOperation(InstanceOperation): return disk self.disks = [] - with activity.sub_activity('saving_disks', - readable_name=ugettext_noop("save disks")): - for disk in self.instance.disks.all(): + for disk in self.instance.disks.all(): + with activity.sub_activity( + 'saving_disk', + readable_name=create_readable( + ugettext_noop("saving disk %(name)s"), + name=disk.name) + ): self.disks.append(__try_save_disk(disk)) # create template and do additional setup @@ -567,11 +586,7 @@ class ShutdownOperation(InstanceOperation): description = _("Shutdown virtual machine with ACPI signal.") abortable = True required_perms = () - - def check_precond(self): - super(ShutdownOperation, self).check_precond() - if self.instance.status not in ['RUNNING']: - raise self.instance.WrongStateError(self.instance) + accept_states = ('RUNNING', ) def on_commit(self, activity): activity.resultant_state = 'STOPPED' @@ -591,11 +606,7 @@ class ShutOffOperation(InstanceOperation): name = _("shut off") description = _("Shut off VM (plug-out).") required_perms = () - - def check_precond(self): - super(ShutOffOperation, self).check_precond() - if self.instance.status not in ['RUNNING']: - raise self.instance.WrongStateError(self.instance) + accept_states = ('RUNNING', ) def on_commit(self, activity): activity.resultant_state = 'STOPPED' @@ -623,16 +634,12 @@ class SleepOperation(InstanceOperation): name = _("sleep") description = _("Suspend virtual machine with memory dump.") required_perms = () + accept_states = ('RUNNING', ) def is_preferred(self): return (not self.instance.is_base and self.instance.status == self.instance.STATUS.RUNNING) - def check_precond(self): - super(SleepOperation, self).check_precond() - if self.instance.status not in ['RUNNING']: - raise self.instance.WrongStateError(self.instance) - def on_abort(self, activity, error): if isinstance(error, TimeLimitExceeded): activity.resultant_state = None @@ -670,15 +677,11 @@ class WakeUpOperation(InstanceOperation): Power on Virtual Machine and load its memory from dump. """) required_perms = () + accept_states = ('SUSPENDED', ) def is_preferred(self): return self.instance.status == self.instance.STATUS.SUSPENDED - def check_precond(self): - super(WakeUpOperation, self).check_precond() - if self.instance.status not in ['SUSPENDED']: - raise self.instance.WrongStateError(self.instance) - def on_abort(self, activity, error): activity.resultant_state = 'ERROR' @@ -702,8 +705,10 @@ class WakeUpOperation(InstanceOperation): "deploy network")): self.instance.deploy_net() - # Renew vm - self.instance.renew(parent_activity=activity) + try: + self.instance.renew(parent_activity=activity) + except: + pass register_operation(WakeUpOperation) @@ -718,15 +723,24 @@ class RenewOperation(InstanceOperation): required_perms = () concurrency_check = False - def check_precond(self): - super(RenewOperation, self).check_precond() - if self.instance.status == 'DESTROYED': - raise self.instance.WrongStateError(self.instance) - - def _operation(self, lease=None): - (self.instance.time_of_suspend, - self.instance.time_of_delete) = self.instance.get_renew_times(lease) + def _operation(self, activity, lease=None, force=False): + suspend, delete = self.instance.get_renew_times(lease) + if (not force and suspend and self.instance.time_of_suspend and + suspend < self.instance.time_of_suspend): + raise HumanReadableException.create(ugettext_noop( + "Renewing the machine with the selected lease would result " + "in its suspension time get earlier than before.")) + if (not force and delete and self.instance.time_of_delete and + delete < self.instance.time_of_delete): + raise HumanReadableException.create(ugettext_noop( + "Renewing the machine with the selected lease would result " + "in its delete time get earlier than before.")) + self.instance.time_of_suspend = suspend + self.instance.time_of_delete = delete self.instance.save() + activity.result = create_readable(ugettext_noop( + "Renewed to suspend at %(suspend)s and destroy at %(delete)s."), + suspend=suspend, delete=delete) register_operation(RenewOperation) @@ -790,7 +804,8 @@ class FlushOperation(NodeOperation): def check_auth(self, user): if not user.is_superuser: - raise PermissionDenied() + raise humanize_exception(ugettext_noop( + "Superuser privileges are required."), PermissionDenied()) super(FlushOperation, self).check_auth(user=user) @@ -815,11 +830,7 @@ class ScreenshotOperation(InstanceOperation): description = _("Get screenshot") acl_level = "owner" required_perms = () - - def check_precond(self): - super(ScreenshotOperation, self).check_precond() - if self.instance.status not in ['RUNNING']: - raise self.instance.WrongStateError(self.instance) + accept_states = ('RUNNING', ) def _operation(self): return self.instance.get_screenshot(timeout=20) @@ -835,10 +846,13 @@ class RecoverOperation(InstanceOperation): description = _("Recover virtual machine from destroyed state.") acl_level = "owner" required_perms = ('vm.recover', ) + accept_states = ('DESTROYED', ) def check_precond(self): - if not self.instance.destroyed_at: - raise self.instance.WrongStateError(self.instance) + try: + super(RecoverOperation, self).check_precond() + except Instance.InstanceDestroyedError: + pass def on_commit(self, activity): activity.resultant_state = 'PENDING' @@ -862,13 +876,10 @@ class ResourcesOperation(InstanceOperation): description = _("Change resources") acl_level = "owner" required_perms = ('vm.change_resources', ) + accept_states = ('STOPPED', 'PENDING', ) - def check_precond(self): - super(ResourcesOperation, self).check_precond() - if self.instance.status not in ["STOPPED", "PENDING"]: - raise self.instance.WrongStateError(self.instance) - - def _operation(self, user, num_cores, ram_size, max_ram_size, priority): + def _operation(self, user, activity, + num_cores, ram_size, max_ram_size, priority): self.instance.num_cores = num_cores self.instance.ram_size = ram_size @@ -878,6 +889,12 @@ class ResourcesOperation(InstanceOperation): self.instance.full_clean() self.instance.save() + activity.result = create_readable(ugettext_noop( + "Priority: %(priority)s, Num cores: %(num_cores)s, " + "Ram size: %(ram_size)s"), priority=priority, num_cores=num_cores, + ram_size=ram_size + ) + register_operation(ResourcesOperation) @@ -889,11 +906,7 @@ class PasswordResetOperation(InstanceOperation): description = _("Password reset") acl_level = "owner" required_perms = () - - def check_precond(self): - super(PasswordResetOperation, self).check_precond() - if self.instance.status not in ["RUNNING"]: - raise self.instance.WrongStateError(self.instance) + accept_states = ('RUNNING', ) def _operation(self): self.instance.pw = pwgen() diff --git a/circle/vm/tasks/local_agent_tasks.py b/circle/vm/tasks/local_agent_tasks.py index a024201..7cd5052 100644 --- a/circle/vm/tasks/local_agent_tasks.py +++ b/circle/vm/tasks/local_agent_tasks.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License along # with CIRCLE. If not, see . +from common.models import create_readable from manager.mancelery import celery from vm.tasks.agent_tasks import (restart_networking, change_password, set_time, set_hostname, start_access_server, @@ -25,22 +26,25 @@ from StringIO import StringIO from tarfile import TarFile, TarInfo from django.conf import settings from django.utils import timezone +from django.utils.translation import ugettext_noop from celery.result import TimeoutError from monitor.client import Client def send_init_commands(instance, act, vm): queue = instance.get_remote_queue_name("agent") - - with act.sub_activity('cleanup'): + with act.sub_activity('cleanup', readable_name=ugettext_noop('cleanup')): cleanup.apply_async(queue=queue, args=(vm, )) - with act.sub_activity('restart_networking'): + with act.sub_activity('restart_networking', + readable_name=ugettext_noop('restart networking')): restart_networking.apply_async(queue=queue, args=(vm, )) - with act.sub_activity('change_password'): + with act.sub_activity('change_password', + readable_name=ugettext_noop('change password')): change_password.apply_async(queue=queue, args=(vm, instance.pw)) - with act.sub_activity('set_time'): + with act.sub_activity('set_time', readable_name=ugettext_noop('set time')): set_time.apply_async(queue=queue, args=(vm, time.time())) - with act.sub_activity('set_hostname'): + with act.sub_activity('set_hostname', + readable_name=ugettext_noop('set hostname')): set_hostname.apply_async( queue=queue, args=(vm, instance.primary_host.hostname)) @@ -73,13 +77,21 @@ def agent_started(vm, version=None): initialized = InstanceActivity.objects.filter( instance=instance, activity_code='vm.Instance.agent.cleanup').exists() - with instance_activity(code_suffix='agent', instance=instance) as act: - with act.sub_activity('starting'): + with instance_activity(code_suffix='agent', + readable_name=ugettext_noop('agent'), + instance=instance) as act: + with act.sub_activity('starting', + readable_name=ugettext_noop('starting')): pass if version and version != settings.AGENT_VERSION: try: - with act.sub_activity('update'): + with act.sub_activity( + 'update', + readable_name=create_readable( + ugettext_noop('update to %(version)s'), + version=settings.AGENT_VERSION) + ): update.apply_async( queue=queue, args=(vm, create_agent_tar())).get(timeout=10) @@ -91,7 +103,10 @@ def agent_started(vm, version=None): measure_boot_time(instance) send_init_commands(instance, act, vm) - with act.sub_activity('start_access_server'): + with act.sub_activity( + 'start_access_server', + readable_name=ugettext_noop('start access server') + ): start_access_server.apply_async(queue=queue, args=(vm, )) @@ -122,5 +137,5 @@ def agent_stopped(vm): qs = InstanceActivity.objects.filter(instance=instance, activity_code='vm.Instance.agent') act = qs.latest('id') - with act.sub_activity('stopping'): + with act.sub_activity('stopping', readable_name=ugettext_noop('stopping')): pass diff --git a/circle/vm/tests/test_models.py b/circle/vm/tests/test_models.py index e7e7e44..1898102 100644 --- a/circle/vm/tests/test_models.py +++ b/circle/vm/tests/test_models.py @@ -210,7 +210,7 @@ class NodeTestCase(TestCase): node.enabled = True node.STATES = Node.STATES self.assertEqual(Node.get_state(node), "ONLINE") - assert isinstance(Node.get_status_display(node), _("").__class__) + assert isinstance(Node.get_status_display(node), _("x").__class__) class InstanceActivityTestCase(TestCase): diff --git a/requirements/base.txt b/requirements/base.txt index d67650c..6b6dceb 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -21,7 +21,6 @@ kombu==3.0.15 logutils==0.3.3 MarkupSafe==0.21 netaddr==0.7.11 -nose==1.3.1 pip-tools==0.3.4 psycopg2==2.5.2 Pygments==1.6 diff --git a/requirements/test.txt b/requirements/test.txt index 2837c40..c1d9641 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -3,3 +3,5 @@ coverage==3.7.1 factory-boy==2.3.1 mock==1.0.1 +django-nose==1.2 +nose==1.3.3