Commit a65d9643 by Bach Dániel

Merge remote-tracking branch 'origin/master' into feature-django18

parents 3d1583e0 3122ce02
...@@ -213,6 +213,7 @@ PIPELINE_JS = { ...@@ -213,6 +213,7 @@ PIPELINE_JS = {
"dashboard/vm-common.js", "dashboard/vm-common.js",
"dashboard/vm-create.js", "dashboard/vm-create.js",
"dashboard/vm-list.js", "dashboard/vm-list.js",
"dashboard/help.js",
"js/host.js", "js/host.js",
"js/network.js", "js/network.js",
"js/switch-port.js", "js/switch-port.js",
......
...@@ -21,7 +21,8 @@ from logging import getLogger ...@@ -21,7 +21,8 @@ from logging import getLogger
from django.core.exceptions import PermissionDenied, ImproperlyConfigured from django.core.exceptions import PermissionDenied, ImproperlyConfigured
from django.utils.translation import ugettext_noop from django.utils.translation import ugettext_noop
from .models import activity_context, has_suffix, humanize_exception from .models import (activity_context, has_suffix, humanize_exception,
HumanReadableObject)
logger = getLogger(__name__) logger = getLogger(__name__)
...@@ -110,8 +111,12 @@ class Operation(object): ...@@ -110,8 +111,12 @@ class Operation(object):
arguments.update(auxargs) arguments.update(auxargs)
with activity_context(allargs['activity'], on_abort=self.on_abort, with activity_context(allargs['activity'], on_abort=self.on_abort,
on_commit=self.on_commit): on_commit=self.on_commit) as act:
return self._operation(**arguments) retval = self._operation(**arguments)
if (act.result is None and isinstance(
retval, (basestring, int, HumanReadableObject))):
act.result = retval
return retval
def _operation(self, **kwargs): def _operation(self, **kwargs):
"""This method is the operation's particular implementation. """This method is the operation's particular implementation.
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from datetime import timedelta
from itertools import chain from itertools import chain
from hashlib import md5 from hashlib import md5
from logging import getLogger from logging import getLogger
...@@ -31,6 +32,7 @@ from django.db.models import ( ...@@ -31,6 +32,7 @@ from django.db.models import (
) )
from django.db.models.signals import post_save, pre_delete, post_delete from django.db.models.signals import post_save, pre_delete, post_delete
from django.templatetags.static import static from django.templatetags.static import static
from django.utils import timezone
from django.utils.html import escape from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
...@@ -132,6 +134,17 @@ class Notification(TimeStampedModel): ...@@ -132,6 +134,17 @@ class Notification(TimeStampedModel):
def message(self, value): def message(self, value):
self.message_data = None if value is None else value.to_dict() self.message_data = None if value is None else value.to_dict()
@property
def has_valid_renew_url(self):
params = self.message_data['params']
return ('token' in params and 'suspend' in params and
self.modified > timezone.now() - timedelta(days=3))
@property
def renew_url(self):
return (settings.DJANGO_URL.rstrip("/") +
str(self.message_data['params'].get('token')))
class ConnectCommand(Model): class ConnectCommand(Model):
user = ForeignKey(User, related_name='command_set') user = ForeignKey(User, related_name='command_set')
......
...@@ -1320,15 +1320,155 @@ textarea[name="new_members"] { ...@@ -1320,15 +1320,155 @@ textarea[name="new_members"] {
} }
} }
/* help page */
.help-tabs li {
width: 50%;
text-align: center;
}
// note: padding-margin hack to skip banner on anchor
#help-tab-content {
h1, h2, h3, h4, h5 {
font-weight: bold;
padding-top: 55px; margin-top: -55px;
}
blockquote {
background-color:#dfe4e4;
}
}
#wrapper {
position: fixed;
padding-left: 0px;
background: #252525;
left: 0px;
top: 46px;
height: 100%;
width: 210px;
transition: all .4s ease 0s;
/* Firefox */
height: -moz-calc(100% - 130px);
/* WebKit */
height: -webkit-calc(100% - 120px);
/* Opera */
height: -o-calc(100% - 110px);
/* Standard */
height: calc(100% - 130px);
}
#sidebar-wrapper {
margin-left: 0px;
left: 0px;
top: inherit;
bottom: 30px;
width: inherit;
position: inherit;
z-index: 10000;
transition: all .4s ease 0s;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
}
#sidebar-wrapper ul {
display: block;
float: left;
list-style: none;
margin: 0;
padding: 0px;
}
#sidebar-wrapper a {
font-family: "proxima-nova", 'Helvetica Neue', 'Helvetica', 'sans-serif';
color: #ddd;
display: block;
float: left;
width: 200px;
text-decoration: none;
background: #252525;
border-top: 1px solid #333;
border-bottom: 1px solid #222;
-webkit-transition: background .5s;
-moz-transition: background .5s;
-o-transition: background .5s;
-ms-transition: background .5s;
transition: background .5s;
}
#sidebar-wrapper ul a{
padding-top: 10px;
padding-bottom: 10px;
padding-left: 10px;
font-size: larger;
}
#sidebar-wrapper li a{
padding-top: 6px;
padding-bottom: 6px;
padding-left: 20px;
padding-right: 6px;
font-size: smaller;
}
#sidebar-wrapper ul ul a{
padding-top: 10px;
padding-bottom: 10px;
padding-left: 16px;
font-size: larger;
}
#sidebar-wrapper li li a{
padding-top: 6px;
padding-bottom: 6px;
padding-left: 24px;
padding-right: 6px;
font-size: smaller;
}
#sidebar-wrapper li a:hover, #sidebar-wrapper ul a:hover{
color: #fff;
background: rgba(200,200,255,0.15);
text-decoration: none;
}
#page-content {
margin-left: 200px;
}
@media only screen and (max-width: 600px) {
#wrapper {
-webkit-transform: translateX(-250px);
-moz-transform: translateX(-250px);
-o-transform: translateX(-250px);
-ms-transform: translateX(-250px);
transform: translateX(-250px);
}
#page-content {
margin-left: 0px;
-webkit-transition: background .5s;
-moz-transition: background .5s;
-o-transition: background .5s;
-ms-transition: background .5s;
transition: background .5s;
}
}
.overview_href {
cursor: pointer;
}
#request-buttons { #request-buttons {
form { form {
display: inline; display: inline;
} }
textarea {
textarea {
resize: none; resize: none;
min-height: 80px; min-height: 80px;
} }
} }
.nowrap { .nowrap {
......
$(function() {
$('.crosslink').click(function(e) {
e.preventDefault();
var menu = $(this).data("menu");
$(menu).click();
window.location = this.href;
});
var hash = window.location.hash;
if(hash) {
var pane = $(hash).closest(".tab-pane").prop("class");
if (pane) {
if (pane.indexOf("overview") != -1) {
$("#overview_menu").click();
} else {
$("#faq_menu").click();
}
$("html, body").animate({scrollTop: $(hash).offset().top}, 500);
window.location.hash = hash;
}
}
});
<img src="{{ STATIC_URL}}dashboard/img/logo.png" alt="circle logo" style="height: 25px;"/> {% load staticfiles %}
<img src="{{ STATIC_URL}}local-logo.png" alt="local logo" style="padding-left: 2px; height: 25px;"/> <img src="{% static "dashboard/img/logo.png" %}" alt="circle logo" style="height: 25px;"/>
<img src="{% static "local-logo.png" %}" alt="local logo" style="padding-left: 2px; height: 25px;"/>
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
{% blocktrans count n=messages|length %}You have a new notification:{% plural %}You have {{n}} new notifications:{% endblocktrans %} {% blocktrans count n=messages|length %}You have a new notification:{% plural %}You have {{n}} new notifications:{% endblocktrans %}
{% for msg in messages %} * {{msg.subject}} {% for msg in messages %} * {{msg.subject}}{% if msg.has_valid_renew_url %}
{% trans "You can renew it without logging in:" %}
{{ msg.renew_url }}{% endif %}
{% endfor %} {% endfor %}
{% blocktrans with url=url count n=messages|length %}See it in detail on <{{url}}>.{% plural %} See them in detail on <{{url}}>.{% endblocktrans %} {% blocktrans with url=url count n=messages|length %}See it in detail on <{{url}}>.{% plural %} See them in detail on <{{url}}>.{% endblocktrans %}
......
{% load i18n %} {% load i18n %}
{% load staticfiles %} {% load staticfiles %}
{% if not perms.vm.vm_access_console %} {% if not perms.vm.access_console %}
<div class="alert alert-warning"> <div class="alert alert-warning">
{% trans "You are not authorized to access the VNC console." %} {% trans "You are not authorized to access the VNC console." %}
</div> </div>
......
...@@ -148,7 +148,11 @@ ...@@ -148,7 +148,11 @@
</div> </div>
<div class="alert alert-info"> <div class="alert alert-info">
You can filter the list by certain attributes (owner, name, status, tags) in the following way: "owner:John Doe name:my little server". If you don't specify any attribute names the filtering will be done by name. {% blocktrans %}
You can filter the list by certain attributes (owner, name, status, tags)
in the following way: "owner:John Doe name:my little server !name:test".
If you don't specify any attribute names the filtering will be done by name.
{% endblocktrans %}
</div> </div>
<div class="alert alert-info"> <div class="alert alert-info">
......
...@@ -5,5 +5,8 @@ ...@@ -5,5 +5,8 @@
<div><strong>{{ field.label }}</strong>: {{ field.errors|striptags }}</div> <div><strong>{{ field.label }}</strong>: {{ field.errors|striptags }}</div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% for error in form.non_field_errors %}
<div>{{ error|striptags }}</div>
{% endfor %}
</div> </div>
{% endif %} {% endif %}
...@@ -28,6 +28,7 @@ from braces.views import LoginRequiredMixin ...@@ -28,6 +28,7 @@ from braces.views import LoginRequiredMixin
from dashboard.models import GroupProfile from dashboard.models import GroupProfile
from vm.models import Instance, Node, InstanceTemplate from vm.models import Instance, Node, InstanceTemplate
from dashboard.views.vm import vm_ops
from ..store_api import Store from ..store_api import Store
...@@ -127,7 +128,10 @@ class HelpView(TemplateView): ...@@ -127,7 +128,10 @@ class HelpView(TemplateView):
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
ctx = super(HelpView, self).get_context_data(*args, **kwargs) ctx = super(HelpView, self).get_context_data(*args, **kwargs)
operations = [(o, Instance._ops[o.op])
for o in vm_ops.values() if o.show_in_toolbar]
ctx.update({"saml": hasattr(settings, "SAML_CONFIG"), ctx.update({"saml": hasattr(settings, "SAML_CONFIG"),
"operations": operations,
"store": settings.STORE_URL}) "store": settings.STORE_URL})
return ctx return ctx
......
...@@ -237,7 +237,8 @@ class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView): ...@@ -237,7 +237,8 @@ class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView):
self.create_fake_get() self.create_fake_get()
try: try:
qs = qs.filter(**self.get_queryset_filters()).distinct() filters, excludes = self.get_queryset_filters()
qs = qs.filter(**filters).exclude(**excludes).distinct()
except ValueError: except ValueError:
messages.error(self.request, _("Error during filtering.")) messages.error(self.request, _("Error during filtering."))
......
...@@ -79,14 +79,26 @@ class FilterMixin(object): ...@@ -79,14 +79,26 @@ class FilterMixin(object):
def get_queryset_filters(self): def get_queryset_filters(self):
filters = {} filters = {}
for item in self.allowed_filters: excludes = {}
if item in self.request.GET:
filters[self.allowed_filters[item]] = ( for key, value in self.request.GET.items():
self.request.GET[item].split(",") if not key:
if self.allowed_filters[item].endswith("__in") else continue
self.request.GET[item]) exclude = key.startswith('!')
key = key.lstrip('!')
if key not in self.allowed_filters:
continue
filter_field = self.allowed_filters[key]
value = (value.split(",")
if filter_field.endswith("__in") else
value)
if exclude:
excludes[filter_field] = value
else:
filters[filter_field] = value
return filters return filters, excludes
def get_queryset(self): def get_queryset(self):
return super(FilterMixin, return super(FilterMixin,
...@@ -118,6 +130,9 @@ class FilterMixin(object): ...@@ -118,6 +130,9 @@ class FilterMixin(object):
>>> o = f._parse_get({'s': "name:hello ws node:node 3 oh"}).items() >>> o = f._parse_get({'s': "name:hello ws node:node 3 oh"}).items()
>>> sorted(o) # doctest: +ELLIPSIS >>> sorted(o) # doctest: +ELLIPSIS
[(u'name', u'hello ws'), (u'node', u'node 3 oh'), (...)] [(u'name', u'hello ws'), (u'node', u'node 3 oh'), (...)]
>>> o = f._parse_get({'s': "!hello:szia"}).items()
>>> sorted(o) # doctest: +ELLIPSIS
[(u'!hello', u'szia'), (...)]
""" """
s = GET_dict.get("s") s = GET_dict.get("s")
fake = GET_dict.copy() fake = GET_dict.copy()
......
...@@ -1002,7 +1002,8 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView): ...@@ -1002,7 +1002,8 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
in [i.name for i in Instance._meta.fields] + ["pk"]): in [i.name for i in Instance._meta.fields] + ["pk"]):
queryset = queryset.order_by(sort) queryset = queryset.order_by(sort)
return queryset.filter(**self.get_queryset_filters()).prefetch_related( filters, excludes = self.get_queryset_filters()
return queryset.filter(**filters).exclude(**excludes).prefetch_related(
"owner", "node", "owner__profile", "interface_set", "lease", "owner", "node", "owner__profile", "interface_set", "lease",
"interface_set__host").distinct() "interface_set__host").distinct()
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.forms import ( from django.forms import (
ModelForm, ModelChoiceField, ChoiceField, Form, CharField, RadioSelect, ModelForm, ModelChoiceField, ChoiceField, Form, CharField, RadioSelect,
Textarea, Textarea, ValidationError
) )
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.template import RequestContext from django.template import RequestContext
...@@ -70,6 +70,14 @@ class InitialFromFileMixin(object): ...@@ -70,6 +70,14 @@ class InitialFromFileMixin(object):
RequestContext(request, {}), RequestContext(request, {}),
) )
def clean(self):
cleaned_data = super(InitialFromFileMixin, self).clean()
if cleaned_data['message'].strip() == self.initial['message'].strip():
raise ValidationError(
_("Fill in the message."),
code="invalid")
return cleaned_data
class TemplateRequestForm(InitialFromFileMixin, Form): class TemplateRequestForm(InitialFromFileMixin, Form):
template = ModelChoiceField(TemplateAccessType.objects.all(), template = ModelChoiceField(TemplateAccessType.objects.all(),
...@@ -92,3 +100,13 @@ class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm): ...@@ -92,3 +100,13 @@ class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm):
message = CharField(widget=Textarea) message = CharField(widget=Textarea)
initial_template = "request/initials/resources.html" initial_template = "request/initials/resources.html"
def clean(self):
cleaned_data = super(ResourceRequestForm, self).clean()
inst = self.instance
if (cleaned_data['ram_size'] == inst.ram_size and
cleaned_data['num_cores'] == inst.num_cores and
int(cleaned_data['priority']) == inst.priority):
raise ValidationError(
_("You haven't changed any of the resources."),
code="invalid")
{% load i18n %}
<br/>
<h2 id="faq" >{% trans "FAQ" %}</h2>
<br/>
<h3 id="how-can-i-create-and-share-a-template">{% trans "How can I create and share a template?" %}</h3>
<blockquote>
<ol>
<li>{% trans "Start a virtual machine." %}</li>
<li>{% trans "Customize this machine - install and remove softwares, etc." %}</li>
<li>{% trans 'Click the “Save as Template” button.' %}</li>
<li>{% trans 'On the dashboard select this template to go to the "Edit template page".' %}</li>
<li>
{% trans 'Use the "Manage access" box to add a user or user group with "user" access level.' %}
(<a class="crosslink" data-menu="#overview_menu" href="#how-can-i-create-groups">
{% trans "You can easily create groups if you need to" %}</a>)
</li>
</ol>
</blockquote>
<h3 id="how-can-i-create-a-vm-and-give-to-another-user">{% trans "How can I create a VM and give to another user?" %}</h3>
<blockquote>
<ol>
<li>{% trans "Start a virtual machine." %}</li>
<li>{% trans "On the machine's Access panel you can grant access for users and groups to the VM." %}</li>
</ol>
</blockquote>
<h3 id="how-can-i-portforward">{% trans "How can I open ports?" %}</h3>
<blockquote>
<ol>
<li>{% trans "On the VM’s detail page click on the Network panel." %}</li>
<li>{% trans "On the prefered interface type the port number, select type and click ‘Add’." %}</li>
<li>{% trans "You have to open this port in the firewall too. Opening port 80 examples: " %}<br>
<ul>
<li>Ubuntu, Debian: <code>sudo ufw allow 80</code></li>
<li>
CentOS, RHEL: {% trans "append to" %} <code>/etc/sysconfig/iptables</code>:<br/>
<code>iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT</code>
{% trans "and restart the service:" %} <code>sudo service iptables restart</code>
</li>
</ul>
</li>
<li>{% trans "Now you can connect to the machine with the generated port number." %}</li>
</ol>
</blockquote>
<h3 id="my-machines-lease-is-short-how-can-i-extend-it">{% trans "My machine's lease is too short. How can I extend it?" %}</h3>
<blockquote>
<p>
{% blocktrans %}You can send a request to the administrators. On the VM's home panel click on the ‘renew’ button and ‘send request’.
Please explain why you need a longer lease and choose the most suitable one.{% endblocktrans %}
</p>
</blockquote>
<h3 id="how-can-i-have-more-cpumemory">{% trans "How can I have more CPU/memory?" %}</h3>
<blockquote>
<p>
{% blocktrans %}
You can send a request to the administrators.
On the VM's resources panel click <strong>Request&nbsp;more&nbsp;resources</strong>, modify the values, explain your request and finally hit save.
{% endblocktrans %}
</p>
</blockquote>
<h3 id="how-can-i-get-access-to-a-template">{% trans "How can I get access to a template?" %}</h3>
<blockquote>
<p>
{% blocktrans %}
When you want to create a VM below the template list there is an option to send a request.
Select which template you want, explain why you need it and then submit the form.
{% endblocktrans %}
</p>
</blockquote>
{% load i18n %}
<ul><a href="#faq">{% trans "FAQ" %}</a>
<li><a href="#how-can-i-create-and-share-a-template">{% trans "How can I create and share a template?" %}</a></li>
<li><a href="#how-can-i-create-a-vm-and-give-to-another-user">{% trans "How can I create a VM and give to another user?" %}</a></li>
<li><a href="#how-can-i-portforward">{% trans "How can I portforward?" %}</a></li>
<li><a href="#my-machines-lease-is-short-how-can-i-extend-it">{% trans "My machine’s lease is short. How can I extend it?" %}</a></li>
<li><a href="#how-can-i-have-more-cpumemory">{% trans "How can I have more CPU/memory?" %}</a></li>
<li><a href="#how-can-i-get-access-to-a-template">{% trans "How can I get acces to a template?" %}</a></li>
</li>
</ul>
{% load i18n %}
<ul>
<a href="#overview">{% trans "Overview" %}</a>
<li><ul><a href="#introduction">{% trans "Introduction" %}</a></ul></li>
<li>
<ul><a href="#dashboard">{% trans "Dashboard" %}</a>
<li><a href="#virtual-machines-box">{% trans "Virtual Machines box" %}</a>
<ul>
<li><a href="#how-can-i-create-a-vm">{% trans "How can I create a VM?" %}</a></li>
<li><a href="#how-can-i-mark-frequently-used-vms">{% trans "How can I mark frequently used VMs?" %}</a></li>
<li><a href="#how-can-i-search-for-vms">{% trans "How can I search for VMs?" %}</a></li>
</ul>
</li>
<li><a href="#templates-box">{% trans "Templates box" %}</a></li>
</ul>
</li>
<li>
<ul><a href="#virtual-machines">{% trans "Virtual Machines" %}</a>
<li>
<ul><a href="#details">{% trans "Details" %}</a>
<li><a href="#how-can-i-connect-to-the-virtual-machine">{% trans "How can I connect to the virtual machine?" %}</a></li>
<li><a href="#how-can-i-change-the-vms-password">{% trans "How can I change the VM’s password?" %}</a></li>
</ul>
</li>
<li>
<ul><a href="#operations">{% trans "Operations" %}</a>
<li><a href="#what-kind-of-operations-are-allowed-to-do-with-my-vm">{% trans "What kind of operations are allowed to do with my VM?" %}</a></li>
<li>
</ul>
</li>
<li>
<ul><a href="#home">{% trans "Home" %}</a>
<li><a href="#expiration">{% trans "Expiration" %}</a></li>
<li><a href="#how-can-i-expand-the-vms-expiration-date">{% trans "How can I expand the VM’s expiration date?" %}</a></li>
<li><a href="#file-management">{% trans "File management" %}</a></li>
<li><a href="#how-can-i-share-previously-uploaded-files-with-the-vm">{% trans "How can I share previously uploaded files with the VM?" %}</a></li>
</ul>
</li>
<li><ul><a href="#resources">{% trans "Resources" %}</a></ul></li>
<li><a href="#console">{% trans "Console" %}</a></li>
<li><a href="#access">{% trans "Access" %}</a><ul>
<li><a href="#how-can-i-give-access-to-others">{% trans "How can I give access to others?" %}</a></li>
<li><a href="#what-kind-of-permissions-are-available">{% trans "What kind of permissions are available?" %}</a></li>
<li>
<ul><a href="#network">{% trans "Network" %}</a>
<li><a href="#how-can-i-add-a-network-interface">{% trans "How can I add a network interface?" %}</a></li>
</ul>
</li>
<li><a href="#activity">{% trans "Activity" %}</a></li>
<li><a href="#multiple-vm-operations">{% trans "Multiple VM operations" %}</a>
<li><a href="#how-can-i-show-shared-or-destroyed-vms">{% trans "How can I show shared or destroyed VMs?" %}</a></li>
</ul>
</li>
</ul>
</li>
<li>
<ul><a href="#templates">{% trans "Templates" %}</a>
<li><a href="#how-can-i-create-templates">{% trans "How can I create templates?" %}</a></li>
<li><a href="#what-kind-of-options-are-customizable-in-the-template">{% trans "What kind of options are customizable in the template?" %}</a></li>
<li><a href="#how-can-i-change-the-expiration-of-the-templates-vms">{% trans "How can I change the expiration of the tempalte's VMs" %}</a></li>
<li><a href="#how-can-i-give-the-template-to-other-user">{% trans "How can I give the template to other user" %}</a></li>
<li><a href="#how-can-i-give-access-to-users-or-groups-to-the-template">{% trans "How can I give access to users or groups to the template?"%}</a></li>
</ul>
</li>
<li>
<ul><a href="#groups">{% trans "Groups" %}</a>
<li><a href="#how-can-i-create-groups">{% trans "How can I create groups?" %}</a></li>
<li><a href="#how-can-i-manage-the-users-in-a-group">{% trans "How can I manage the users in a group?" %}</a></li>
<li><a href="#how-can-i-manage-privileges-with-the-group">{% trans "How can I manage privileges with the group?" %}</a></li>
</ul>
</li>
<li>
<ul><a href="#files">{% trans "Files" %}</a>
<li><a href="#how-can-i-share-my-files-with-a-vm">{% trans "How can I share my files with a VM?" %}</a></li>
</ul>
</li>
<li>
<ul><a href="#profile">{% trans "Profile" %}</a>
<li><a href="#how-can-i-change-my-password">{% trans "How can I change my password?" %}</a></li>
<li><a href="#how-can-i-store-public-keys-on-the-vms">{% trans "How can I store public keys on the VMs?" %}</a></li>
<li><a href="#how-can-i-change-connection-template">{% trans "How can I change connection template" %}</a></li>
</ul>
</li>
</ul>
...@@ -28,6 +28,7 @@ import time ...@@ -28,6 +28,7 @@ import time
from urlparse import urlsplit from urlparse import urlsplit
from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from django.conf import settings from django.conf import settings
...@@ -794,7 +795,10 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -794,7 +795,10 @@ class SaveAsTemplateOperation(InstanceOperation):
tmpl.delete() tmpl.delete()
raise raise
else: else:
return tmpl return create_readable(
ugettext_noop("New template: %(template)s"),
template=reverse('dashboard.views.template-detail',
kwargs={'pk': tmpl.pk}))
@register_operation @register_operation
...@@ -986,7 +990,7 @@ class RenewOperation(InstanceOperation): ...@@ -986,7 +990,7 @@ class RenewOperation(InstanceOperation):
if save: if save:
self.instance.lease = lease self.instance.lease = lease
self.instance.save() self.instance.save()
activity.result = create_readable(ugettext_noop( return create_readable(ugettext_noop(
"Renewed to suspend at %(suspend)s and destroy at %(delete)s."), "Renewed to suspend at %(suspend)s and destroy at %(delete)s."),
suspend=suspend, delete=delete) suspend=suspend, delete=delete)
...@@ -1357,7 +1361,7 @@ class ResourcesOperation(InstanceOperation): ...@@ -1357,7 +1361,7 @@ class ResourcesOperation(InstanceOperation):
self.instance.full_clean() self.instance.full_clean()
self.instance.save() self.instance.save()
activity.result = create_readable(ugettext_noop( return create_readable(ugettext_noop(
"Priority: %(priority)s, Num cores: %(num_cores)s, " "Priority: %(priority)s, Num cores: %(num_cores)s, "
"Ram size: %(ram_size)s"), priority=priority, num_cores=num_cores, "Ram size: %(ram_size)s"), priority=priority, num_cores=num_cores,
ram_size=ram_size ram_size=ram_size
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment