Commit e84c01cf by Őry Máté

Merge branch 'release-13.04.1' into releases

Conflicts:
	one/locale/hu/LC_MESSAGES/django.po
	one/locale/hu/LC_MESSAGES/djangojs.po
	school/locale/hu/LC_MESSAGES/django.po
parents f6fdeffe 3c786fcb
......@@ -14,7 +14,15 @@
<a id="master"></a>
<section>
<h3 id="release-13.03.2"><a href="#13.03.2">13.03.2 (2013. március 21.)</a></h3>
<h3 id="release-13.04.1"><a href="#release-13.04.1">13.04.1 (2013. április 4.)</a></h3>
<ul>
<li>Hálózati beállítások új felületen.</li>
<li>Rövidebb IPv6-os gépnév.</li>
<li>Hibajavítások.</li>
</ul>
</section>
<section>
<h3 id="release-13.03.2"><a href="#release-13.03.2">13.03.2 (2013. március 21.)</a></h3>
<ul>
<li>Súgó.</li>
<li>Változáslista.</li>
......@@ -26,7 +34,7 @@
</section>
<section>
<h3 id="release-13.03.1"><a href="#13.03.1">13.03.1 (2013. március 7.)</a></h3>
<h3 id="release-13.03.1"><a href="#release-13.03.1">13.03.1 (2013. március 7.)</a></h3>
<ul>
<li>Határidős felfüggesztés élesítve.</li>
<li>Csatlakozási adatoknál IP cím helyett DNS név jelenik meg.</li>
......@@ -50,7 +58,7 @@
</section>
<section>
<h3 id="release-13.02.2"><a href="#13.02.2">13.02.2 (2013. február 21.)</a></h3>
<h3 id="release-13.02.2"><a href="#release-13.02.2">13.02.2 (2013. február 21.)</a></h3>
<ul>
<li>Felhasználói kvóták megvalósítása.</li>
<li>Publikus kulcsok kezelése.</li>
......
......@@ -37,7 +37,7 @@ urlpatterns = patterns('',
url(r'^vm/renew/(?P<which>(suspend|delete))/(?P<iid>\d+)/$',
'one.views.vm_renew', ),
url(r'^vm/port_add/(?P<iid>\d+)/$', 'one.views.vm_port_add', ),
url(r'^vm/port_del/(?P<iid>\d+)/(?P<proto>tcp|udp)/(?P<public>\d+)/$',
url(r'^vm/port_del/(?P<iid>\d+)/(?P<proto>tcp|udp)/(?P<private>\d+)/$',
'one.views.vm_port_del', ),
url(r'^ajax/shareEdit/(?P<id>\d+)/$', 'one.views.ajax_share_edit_wizard',
name='ajax_share_edit_wizard'),
......
......@@ -10,6 +10,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator
from cloud.settings import firewall_settings as settings
from django.db.models.signals import post_save
import re
import random
class Rule(models.Model):
CHOICES_type = (('host', 'host'), ('firewall', 'firewall'),
......@@ -174,15 +175,30 @@ class Host(models.Model):
def enable_net(self):
self.groups.add(Group.objects.get(name="netezhet"))
def add_port(self, proto, public, private = 0):
def add_port(self, proto, public=None, private=None):
proto = "tcp" if proto == "tcp" else "udp"
if self.shared_ip:
used_ports = Rule.objects.filter(host__pub_ipv4=self.pub_ipv4,
nat=True, proto=proto).values_list('dport', flat=True)
if public is None:
public = random.randint(1024, 21000)
if public in used_ports:
for i in range(1024, 21000) + range(24000, 65535):
if i not in used_ports:
public = i
break
else:
raise ValidationError(_("Port %s %s is already in use.") %
(proto, public))
else:
if public < 1024:
raise ValidationError(_("Only ports above 1024 can be used."))
for host in Host.objects.filter(pub_ipv4=self.pub_ipv4):
if host.rules.filter(nat=True, proto=proto, dport=public):
if public in used_ports:
raise ValidationError(_("Port %s %s is already in use.") %
(proto, public))
rule = Rule(direction='1', owner=self.owner, dport=public,
proto=proto, nat=True, accept=True, r_type="host",
nat_dport=private, host=self, foreign_network=VlanGroup.
......@@ -199,15 +215,58 @@ class Host(models.Model):
rule.full_clean()
rule.save()
def del_port(self, proto, public):
def del_port(self, proto, private):
if self.shared_ip:
self.rules.filter(owner=self.owner, proto=proto, host=self,
nat_dport=private).delete()
else:
self.rules.filter(owner=self.owner, proto=proto, host=self,
dport=public).delete()
dport=private).delete()
def get_hostname(self, proto):
try:
if proto == 'ipv6':
res = self.record_set.filter(type='AAAA')
elif proto == 'ipv4':
if self.shared_ip:
res = Record.objects.filter(type='A',
address=self.pub_ipv4)
else:
res = self.record_set.filter(type='A')
return unicode(res[0].get_data()['name'])
except:
raise
if self.shared_ip:
return self.pub_ipv4
else:
return self.ipv4
def list_ports(self):
return [{'proto': rule.proto,
'public': rule.dport,
'private': rule.nat_dport} for rule in
self.rules.filter(owner=self.owner)]
retval = []
for rule in self.rules.filter(owner=self.owner):
private = rule.nat_dport if self.shared_ip else rule.dport
forward = {
'proto': rule.proto,
'private': private,
}
if self.shared_ip:
public4 = rule.dport
public6 = rule.nat_dport
else:
public4 = public6 = rule.dport
if True: # ipv4
forward['ipv4'] = {
'host': self.get_hostname(proto='ipv4'),
'port': public4,
}
if self.ipv6: # ipv6
forward['ipv6'] = {
'host': self.get_hostname(proto='ipv6'),
'port': public6,
}
retval.append(forward)
return retval
def get_fqdn(self):
return self.hostname + u'.' + unicode(self.vlan.domain)
......
description "IK Cloud Django Development Server"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
respawn limit 30 30
exec sudo -u cloud /opt/webadmin/cloud/manage.py celery worker --loglevel=info -c 1 -Q local
......@@ -183,6 +183,7 @@ class Browser:
def load_committed_cb(self,web_view, frame):
uri = frame.get_uri()
print uri
try:
self.webview.execute_script('document.getElementsByTagName("a")[0].target="";')
except:
......@@ -193,7 +194,7 @@ class Browser:
### JS
self.post_key(self.public_key_b64)
### Parse values and do mounting ###
elif uri.startswith("https://cloud.ik.bme.hu/?"):
elif uri.startswith("https://cloud.ik.bme.hu/home/?"):
if self.mounted != True:
try:
uri, params = uri.split('?', 1)
......
@inproceedings{younge2011analysis,
title={Analysis of virtualization technologies for high performance computing environments},
author={Younge, Andrew J and Henschel, Robert and Brown, James T and von Laszewski, Gregor and Qiu, Judy and Fox, Geoffrey C},
booktitle={Cloud Computing (CLOUD), 2011 IEEE International Conference on},
pages={9--16},
year={2011},
organization={IEEE}
}
@article{creasy1981origin,
title={The origin of the VM/370 time-sharing system},
author={Creasy, Robert J.},
journal={IBM Journal of Research and Development},
volume={25},
number={5},
pages={483--490},
year={1981},
publisher={IBM}
}
@article{duatonew,
title={A New Approach to rCUDA},
author={Duato, Jos{\'e} and Pena, Antonio J and Silla, Federico and Fern{\'a}ndez, Juan C and Mayo, Rafael and Quintana-Ort{\i}, Enrique S}
}
@book{holovaty2009definitive,
title={The Definitive Guide to Django: Web Development Done Right},
author={Holovaty, Adrian and Kaplan-Moss, Jacob},
year={2009},
publisher={Apress}
}
@techreport{pinzari2003introduction,
title={Introduction to NX technology},
author={Pinzari, Gian Filippo},
year={2003},
institution={NoMachine Technical Report 309}
}
@article{victoria2009creating,
title={Creating and Controlling KVM Guests using libvirt},
author={Victoria, B},
journal={University of Victoria},
year={2009}
}
@inproceedings{bolte2010non,
title={Non-intrusive virtualization management using libvirt},
author={Bolte, Matthias and Sievers, Michael and Birkenheuer, Georg and Nieh{\"o}rster, Oliver and Brinkmann, Andr{\'e}},
booktitle={Proceedings of the Conference on Design, Automation and Test in Europe},
pages={574--579},
year={2010},
organization={European Design and Automation Association}
}
@article{pfaff2009extending,
title={Extending networking into the virtualization layer},
author={Pfaff, Ben and Pettit, Justin and Koponen, Teemu and Amidon, Keith and Casado, Martin and Shenker, Scott},
journal={Proc. HotNets (October 2009)},
year={2009}
}
@article{hoskins2006sshfs,
title={Sshfs: super easy file access over ssh},
author={Hoskins, Matthew E},
journal={Linux Journal},
volume={2006},
number={146},
pages={4},
year={2006},
publisher={Belltown Media}
}
@article{szeredi2010fuse,
title={FUSE: Filesystem in userspace},
author={M. Szeredi},
journal={Accessed on},
year={2010}
}
@inproceedings{yang2012implementation,
title={On implementation of GPU virtualization using PCI pass-through},
author={Yang, Chao-Tung and Wang, Hsien-Yi and Ou, Wei-Shen and Liu, Yu-Tso and Hsu, Ching-Hsien},
booktitle={Cloud Computing Technology and Science (CloudCom), 2012 IEEE 4th International Conference on},
pages={711--716},
year={2012},
organization={IEEE}
}
@inproceedings{duato2011enabling,
title={Enabling CUDA acceleration within virtual machines using rCUDA},
author={Duato, Jos{\'e} and Pena, Antonio J and Silla, Federico and Fern{\'a}ndez, Juan C and Mayo, Rafael and Quintana-Orti, ES},
booktitle={High Performance Computing (HiPC), 2011 18th International Conference on},
pages={1--10},
year={2011},
organization={IEEE}
}
@inproceedings{callaghan2002nfs,
title={Nfs over rdma},
author={Callaghan, Brent and Lingutla-Raj, Theresa and Chiu, Alex and Staubach, Peter and Asad, Omer},
booktitle={Proceedings of ACM SIGCOMM Summer 2003 NICELI Workshop},
year={2002}
}
@article{vinoski2006advanced,
title={Advanced message queuing protocol},
author={Vinoski, Steve},
journal={Internet Computing, IEEE},
volume={10},
number={6},
pages={87--89},
year={2006},
publisher={IEEE}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
#!/usr/bin/python
import xmltodict
import xml.dom.minidom as minidom
import sys
import json
......@@ -23,6 +22,7 @@ mem_max = 0
running_vms = 0
for host in hosts:
if host.getElementsByTagName("STATE")[0].childNodes[0].data == "2":
share = host.getElementsByTagName("HOST_SHARE")[0]
cpu_max += int(share.getElementsByTagName("MAX_CPU")[0].childNodes[0].data)
used_cpu += int(share.getElementsByTagName("USED_CPU")[0].childNodes[0].data)
......
......@@ -98,8 +98,19 @@ class InstanceAdmin(contrib.admin.ModelAdmin):
class DiskAdmin(contrib.admin.ModelAdmin):
model=models.Disk
list_display = ('name', 'used_by')
def used_by(self, obj):
try:
return ", ".join(obj.template_set.all())
except:
return None
used_by.verbose_name = _('used by')
class NetworkAdmin(contrib.admin.ModelAdmin):
model=models.Network
list_display = ('name', 'nat', 'public', 'get_vlan')
class ShareAdmin(contrib.admin.ModelAdmin):
model=models.Network
list_filter = ('group', 'template', )
......
......@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-22 10:43+0100\n"
"PO-Revision-Date: 2013-03-07 17:02+0100\n"
"POT-Creation-Date: 2013-04-04 18:33+0200\n"
"PO-Revision-Date: 2013-04-04 18:03+0200\n"
"Last-Translator: \n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n"
"Language: hu\n"
......@@ -17,17 +17,18 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"X-Generator: Lokalize 1.4\n"
#: static/script/cloud.js:24
#: static/script/cloud.js:41 static/script/cloud.min.js:1
msgid "Are you sure deleting key?"
msgstr "Biztosan törli a kulcsot?"
#: static/script/cloud.js:24 static/script/cloud.js.c:301
#: static/script/cloud.js:373 static/script/cloud.js.c:600
#: static/script/store.js:288
#: static/script/cloud.js:41 static/script/cloud.js.c:338
#: static/script/cloud.js:416 static/script/cloud.js.c:644
#: static/script/cloud.min.js:1 static/script/store.js:288
#: static/script/store.min.js:1
msgid "Delete"
msgstr "Törlés"
#: static/script/cloud.js:36
#: static/script/cloud.js:53 static/script/cloud.min.js:1
msgid ""
"Are you sure about reseting store credentials?<br /> You will lose your "
"access to your store account on your existing virtual machines!"
......@@ -35,92 +36,114 @@ msgstr ""
"Biztosan újragenerálja az adattár-kulcsait?<br /> El fogja veszteni az "
"adattár-hozzáférést a már futó virtuális gépekből!"
#: static/script/cloud.js:36
#: static/script/cloud.js:53 static/script/cloud.min.js:1
msgid "Reset"
msgstr "Újragenerálás"
#: static/script/cloud.js:76 static/script/store.js:323
#: static/script/cloud.js:95 static/script/cloud.min.js:1
#: static/script/store.js:323 static/script/store.min.js:1
msgid "Rename"
msgstr "Átnevezés"
#: static/script/cloud.js:276 static/script/store.js:288
#: static/script/cloud.js:311 static/script/cloud.min.js:1
#: static/script/store.js:288 static/script/store.min.js:1
msgid "Cancel"
msgstr "Mégsem"
#: static/script/cloud.js:290
#: static/script/cloud.js:327 static/script/cloud.min.js:1
#, c-format
msgid "Are you sure stopping %s?"
msgstr "Biztosan felfüggeszti a következőt: %s?"
#: static/script/cloud.js:291
#: static/script/cloud.js:328 static/script/cloud.min.js:1
msgid "Stop"
msgstr "Felfüggesztés"
#: static/script/cloud.js:300
#: static/script/cloud.js:337 static/script/cloud.min.js:1
#, c-format
msgid "Are you sure deleting %s?"
msgstr "Biztosan törli a következőt: %s?"
#: static/script/cloud.js:310
#: static/script/cloud.js:347 static/script/cloud.min.js:1
#, c-format
msgid "Are you sure restarting %s?"
msgstr "Biztosan újraindítja a következőt: %s?"
#: static/script/cloud.js:311
#: static/script/cloud.js:348 static/script/cloud.min.js:1
msgid "Restart"
msgstr "Újraindítás"
#: static/script/cloud.js:372
#: static/script/cloud.js:415 static/script/cloud.min.js:1
#, c-format
msgid "Are you sure deleting this %s template?"
msgstr "Biztosan törli a következő sablont: %s?"
#: static/script/cloud.js:551 static/script/cloud.js.c:554
#: static/script/cloud.js:436 static/script/cloud.min.js:1
msgid "Template deletion successful!"
msgstr "Sablon törlése sikeres."
#: static/script/cloud.js:595 static/script/cloud.js.c:598
#: static/script/cloud.min.js:1
msgid "Add owner"
msgstr "Tulajdonos hozzáadása"
#: static/script/cloud.js:554
#: static/script/cloud.js:598 static/script/cloud.min.js:1
msgid "Unknown"
msgstr "Ismeretlen"
#: static/script/cloud.js:600
#: static/script/cloud.js:644 static/script/cloud.min.js:1
#, c-format
msgid "Are you sure deleting <strong>%s</strong>"
msgstr "Törli a következő fájlt: <strong>%s</strong>"
#: static/script/store.js:52 static/script/store.js.c:61
#: static/script/store.js:70 static/script/store.js.c:220
#: static/script/store.js:282
#: static/script/store.js:282 static/script/store.min.js:1
msgid "file"
msgstr "fájl"
#: static/script/store.js:125
#: static/script/store.js:125 static/script/store.min.js:1
msgid "Toplist"
msgstr "Legújabb fájlok"
#: static/script/store.js:127
#: static/script/store.js:127 static/script/store.min.js:1
msgid "Back to the root folder"
msgstr "Vissza a gyökérmappába"
#: static/script/store.js:283
#: static/script/store.js:283 static/script/store.min.js:1
#, c-format
msgid "You are removing the file <strong>%s</strong>."
msgstr "Törli a következő fájlt: <strong>%s</strong>."
#: static/script/store.js:285
#: static/script/store.js:285 static/script/store.min.js:1
#, c-format
msgid "You are removing the folder <strong>%s</strong> (and its content)."
msgstr "Törli a következő könyvtárat és tartalmát: <strong>%s</strong>."
#: static/script/store.js:288
#: static/script/store.js:288 static/script/store.min.js:1
msgid "Are you sure?"
msgstr "Biztos benne?"
#: static/script/store.js:446 static/script/store.js.c:448
#: static/script/store.js:396 static/script/store.min.js:1
#, c-format
msgid "File %s is already present! Click OK to override it."
msgstr "%s már létezik. Felülírja?"
#: static/script/store.js:408 static/script/store.min.js:1
#, c-format
msgid ""
"%s seems to be a directory. Uploading directories is currently not "
"supported. If you're sure that %s is a file, click OK to upload it."
msgstr ""
"%s egy mappának tűnik. Mappák feltöltésére nincs lehetőség. Ha biztos benne, "
"hogy %s egy fájl, kattintson az OK-ra a feltöltéshez."
#: static/script/store.js:463 static/script/store.js.c:465
#: static/script/store.min.js:1
msgid "Upload"
msgstr "Feltöltés"
#: static/script/store.js:448
#: static/script/store.js:465 static/script/store.min.js:1
msgid "done, processing..."
msgstr "kész, feldolgozás..."
......
......@@ -158,7 +158,6 @@ class SshKey(models.Model):
TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')),
("SAVE", _('saving')), ("READY", _('ready')))
TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB",
"suspend": td(hours=5), "delete": td(days=15),
"help_text": _('For lab or homework with short lifetime.')},
......@@ -169,6 +168,7 @@ TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB",
"suspend": td(days=365), "delete": None,
"help_text": _('For long-term server use.')},
}
DEFAULT_TYPE = TYPES['LAB']
TYPES_L = sorted(TYPES.values(), key=lambda m: m["suspend"])
TYPES_C = tuple([(i[0], i[1]["verbose_name"]) for i in TYPES.items()])
......@@ -188,13 +188,23 @@ class Share(models.Model):
'user.'))
owner = models.ForeignKey(User, null=True, blank=True, related_name='share_set')
def get_type(self):
t = TYPES[self.type]
class Meta:
ordering = ['group', 'template', 'owner', ]
verbose_name = _('share')
verbose_name_plural = _('shares')
@classmethod
def extend_type(cls, t):
t['deletex'] = (datetime.now() + td(seconds=1) + t['delete']
if t['delete'] else None)
t['suspendx'] = (datetime.now() + td(seconds=1) + t['suspend']
if t['suspend'] else None)
return t
def get_type(self):
t = TYPES[self.type]
return self.extend_type(t)
def get_running_or_stopped(self, user=None):
running = (Instance.objects.all().exclude(state='DONE')
.filter(share=self))
......@@ -210,9 +220,14 @@ class Share(models.Model):
return running.filter(owner=user).count()
else:
return running.count()
def get_instance_pc(self):
return float(self.get_running()) / self.instance_limit * 100
def __unicode__(self):
return u"%(group)s: %(tpl)s %(owner)s" % {
'group': self.group, 'tpl': self.template, 'owner': self.owner}
class Disk(models.Model):
"""Virtual disks automatically synchronized with OpenNebula."""
name = models.CharField(max_length=100, unique=True,
......@@ -220,6 +235,8 @@ class Disk(models.Model):
class Meta:
ordering = ['name']
verbose_name = _('disk')
verbose_name_plural = _('disks')
def __unicode__(self):
return u"%s (#%d)" % (self.name, self.id)
......@@ -259,6 +276,9 @@ class Network(models.Model):
class Meta:
ordering = ['name']
verbose_name = _('network')
verbose_name_plural = _('networks')
def __unicode__(self):
return self.name
......@@ -285,6 +305,8 @@ class Network(models.Model):
Network(id=id, name=name).save()
l.append(id)
Network.objects.exclude(id__in=l).delete()
def get_vlan(self):
return Vlan.objects.get(vid=self.id)
class InstanceType(models.Model):
......@@ -298,6 +320,8 @@ class InstanceType(models.Model):
class Meta:
ordering = ['credit']
verbose_name = _('instance type')
verbose_name_plural = _('instance types')
def __unicode__(self):
return u"%s" % self.name
......@@ -332,6 +356,8 @@ class Template(models.Model):
class Meta:
verbose_name = _('template')
verbose_name_plural = _('templates')
ordering = ['name', ]
def __unicode__(self):
return self.name
......@@ -357,7 +383,7 @@ class Template(models.Model):
class Instance(models.Model):
"""Virtual machine instance."""
name = models.CharField(max_length=100, unique=True,
name = models.CharField(max_length=100,
verbose_name=_('name'), blank=True)
ip = models.IPAddressField(blank=True, null=True,
verbose_name=_('IP address'))
......@@ -396,6 +422,7 @@ class Instance(models.Model):
class Meta:
verbose_name = _('instance')
verbose_name_plural = _('instances')
ordering = ['pk', ]
def __unicode__(self):
return self.name
......@@ -407,28 +434,17 @@ class Instance(models.Model):
def get_port(self, use_ipv6=False):
"""Get public port number for default access method."""
proto = self.template.access_type
if self.template.network.nat and not use_ipv6:
if self.nat and not use_ipv6:
return {"rdp": 23000, "nx": 22000, "ssh": 22000}[proto] + int(self.ip.split('.')[2]) * 256 + int(self.ip.split('.')[3])
else:
return {"rdp": 3389, "nx": 22, "ssh": 22}[proto]
def get_connect_host(self, use_ipv6=False):
"""Get public hostname."""
if self.firewall_host is None:
return _('None')
try:
if use_ipv6:
return self.firewall_host.record_set.filter(type='AAAA')[0].get_data()['name']
else:
if self.template.network.nat:
ip = self.firewall_host.pub_ipv4
return Record.objects.filter(type='A', address=ip)[0].get_data()['name']
else:
return self.firewall_host.record_set.filter(type='A')[0].get_data()['name']
except:
if self.template.network.nat:
return self.firewall_host.pub_ipv4
else:
return self.firewall_host.ipv4
proto = 'ipv6' if use_ipv6 else 'ipv4'
return self.firewall_host.get_hostname(proto=proto)
def get_connect_uri(self, use_ipv6=False):
"""Get access parameters in URI format."""
......@@ -472,6 +488,15 @@ class Instance(models.Model):
self.check_if_is_save_as_done()
return x
@property
def nat(self):
if self.firewall_host is not None:
return self.firewall_host.shared_ip
elif self.template is not None:
return self.template.network.nat
else:
return False
def get_age(self):
"""Get age of VM in seconds."""
from datetime import datetime
......@@ -491,7 +516,7 @@ class Instance(models.Model):
inst = Instance(pw=pwgen(), template=template, owner=owner,
share=share, state='PENDING')
inst.save()
hostname = u"cloud-%d" % (inst.id, )
hostname = u"%d" % (inst.id, )
with tempfile.NamedTemporaryFile(delete=False) as f:
os.chmod(f.name, stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)
token = signing.dumps(inst.id, salt='activate')
......@@ -638,13 +663,20 @@ class Instance(models.Model):
def renew(self, which='both'):
if which in ['suspend', 'both']:
self.time_of_suspend = self.share.get_type()['suspendx']
self.time_of_suspend = self.share_type['suspendx']
if which in ['delete', 'both']:
self.time_of_delete = self.share.get_type()['deletex']
self.time_of_delete = self.share_type['deletex']
if not (which in ['suspend', 'delete', 'both']):
raise ValueError('No such expiration type.')
self.save()
@property
def share_type(self):
if self.share:
return self.share.get_type()
else:
return Share.extend_type(DEFAULT_TYPE)
def save_as(self):
"""Save image and shut down."""
imgname = "template-%d-%d" % (self.template.id, self.id)
......
......@@ -9,7 +9,24 @@ $(function() {
$(this).next('.details').slideDown(700);
}
})
$('a').click(function(e) {
$('a[href=#]').click(function(e) {
e.preventDefault();
});
$('.host-toggle').click(function(e){
e.preventDefault();
if($(this).find('.v4').is(':hidden')){
$(this).find('.v4').show();
$(this).find('.v6').hide();
$(this).parent().next().find('.host').show();
$(this).parent().next().find('.ipv4host').hide();
} else {
$(this).find('.v6').show();
$(this).find('.v4').hide();
$(this).parent().next().find('.host').hide();
$(this).parent().next().find('.ipv4host').show();
}
});
$('a[href!=#]').click(function(e) {
e.stopPropagation();
});
$('.delete-template').click(function(e) {
......@@ -64,12 +81,14 @@ $(function() {
var content = $('#vm-' + id + '-name').html();
var self=this;
var url = $(this).data('url');
$('#vm-'+id).addClass('editing');
$(this).unbind('click').click(function(e){
e.preventDefault();
e.stopPropagation();
$(this).unbind('click').click(handler);
$('#vm-' + id + '-name-details').show();
$('#vm-' + id + '-name').html(content);
$('#vm-'+id).removeClass('editing');
})
$('#vm-' + id + '-name-details').hide();
$('#vm-' + id + '-name').html('<input type="text" value="' + oldName + '" />\
......@@ -91,6 +110,8 @@ $(function() {
$('#vm-' + id + '-name-details').removeAttr('style');
$('#vm-' + id + '-name').text(data.name);
$(self).click(handler);
$(self).data('name', newName);
$('#vm-'+id).removeClass('editing');
}
});
})
......@@ -120,7 +141,7 @@ $(function() {
e.stopPropagation();
restart_vm($(this).data('id'), $(this).data('name'));
});
$('.renew-suspend-vm').click(function(e) {
$('.entry').on('click', '.renew-suspend-vm', function(e) {
e.preventDefault();
e.stopPropagation();
renew_suspend_vm($(this).data('id'))
......@@ -255,6 +276,20 @@ $(function() {
function get_vm_details(id) {
$.get('/vm/credentials/' + id, function(data) {
$('#modal-container').html(data);
$('#modal-container .host-toggle').click(function(e){
e.preventDefault();
if($(this).find('.v4').is(':hidden')){
$(this).find('.v4').show();
$(this).find('.v6').hide();
$(this).parent().next().find('.host').show();
$(this).parent().next().find('.ipv4host').hide();
} else {
$(this).find('.v6').show();
$(this).find('.v4').hide();
$(this).parent().next().find('.host').hide();
$(this).parent().next().find('.ipv4host').show();
}
});
$('#modal-container .hidden-password').click(function() {
if ($(this).attr('type') == 'password') {
$(this).attr('type', 'text');
......@@ -282,6 +317,8 @@ $(function() {
$('#modal').hide();
});
}
cloud.confirm=vm_confirm_popup;
/**
* Manage VM State (STOP)
*/
......@@ -324,7 +361,11 @@ $(function() {
*/
function renew_suspend_vm(id) {
manage_vm(id, "renew/suspend")
manage_vm(id, "renew/suspend", function(data) {
//workaround for some strange jquery parse error :o
var foo=$('<div />').append(data);
$('#vm-'+id+' .details-container').replaceWith(foo.find('.details-container'));
});
}
/**
* Renew vm deletion time.
......@@ -337,12 +378,14 @@ $(function() {
* Manage VM State generic
*/
function manage_vm(id, state) {
function manage_vm(id, state, f) {
$.ajax({
type: 'POST',
url: '/vm/' + state + '/' + id + '/',
success: function(data, b, c) {
if (state == "resume") {
if(f) {
f(data);
} else if (state == "resume") {
window.location.href = '/vm/show/' + id + "/";
} else {
window.location.reload();
......@@ -371,7 +414,7 @@ $(function() {
function delete_template_confirm(url, id, name) {
confirm_message = interpolate(gettext("Are you sure deleting this %s template?"), ["<strong>" + name + "</strong>"])
vm_confirm_popup(confirm_message, gettext("Delete"), function() {
delete_template(id)
delete_template(url, id)
})
}
/**
......@@ -389,7 +432,8 @@ $(function() {
alert(data['responseText']);
},
200: function(data) {
$("#t" + id).remove()
$("#t" + id).remove();
alert(gettext('Template deletion successful!'));
},
}
......
......@@ -343,7 +343,7 @@ var cloud = (function(cloud) {
/**
* Requests a new upload link from the store server
*/
self.getUploadURL = function() {
self.getUploadURL = function(f) {
uploadURLRequestInProgress = true;
$.ajax({
type: 'POST',
......@@ -353,6 +353,7 @@ var cloud = (function(cloud) {
success: function(data) {
self.uploadURL(data.url);
uploadURLRequestInProgress = false;
if (typeof f == 'function') f();
}
})
}
......@@ -387,8 +388,31 @@ var cloud = (function(cloud) {
* Uploads the specified file(s)
*/
var readfiles = cloud.delayUntil(function(file, next) {
console.log('read', file, next)
//1 GB file limit
for (var i in self.files()) {
var f = self.files()[i];
if (file.name == f.originalName) {
if (!confirm(
interpolate(
gettext('File %s is already present! Click OK to override it.'),
[file.name]
)
)
) {
return next();
}
}
}
if (file.name.indexOf('.') == -1 && file.size % 4096 == 0) {
if (!confirm(
interpolate(
gettext('%s seems to be a directory. Uploading directories is currently not supported. If you\'re sure that %s is a file, click OK to upload it.'),
[file.name, file.name]
)
)
) {
return next();
}
}
if (file.size > 1024 * 1024 * 1024) {
$('#upload-zone').hide();
$('#upload-error').show();
......@@ -407,18 +431,11 @@ var cloud = (function(cloud) {
var start = new Date().getTime();
xhr.open('POST', self.uploadURL());
xhr.onload = xhr.onerror = function() {
console.log('onload');
self.uploadProgress('0%');
self.uploadURL('/');
if (next) {
console.log('complete, next')
console.log('complete, next');
next();
} else {
$('.file-upload').removeClass('opened');
$('.file-upload .details').slideUp(700);
$('#upload-zone').show();
$('#upload-progress-text').hide();
loadFolder(self.currentPath());
}
}
if (tests.progress) {
$('#upload-zone').hide();
......@@ -466,22 +483,25 @@ var cloud = (function(cloud) {
var files = e.dataTransfer.files;
console.log(files);
console.log(e.dataTransfer.files);
var i = 1;
readfiles(e.dataTransfer.files[0], function() {
console.log('next', i);
next = arguments.callee;
return function() {
console.log('readnext', i, len);
if (i >= len - 2) {
console.log('end', i, len);
self.getUploadURL();
readfiles(files[i++], null);
var i = 0;
(function next() {
if (i >= files.length) {
$('.file-upload').removeClass('opened');
$('.file-upload .details').slideUp(700);
$('#upload-zone').show();
$('#upload-progress-text').hide();
loadFolder(self.currentPath());
return;
}
self.getUploadURL();
readfiles(files[i++], next());
console.log('reading file ' + i);
if (self.uploadURL() == '/') {
self.getUploadURL(function() {
readfiles(files[i++], next);
});
} else {
readfiles(files[i++], next);
}
}());
})();
return false;
});
document.addEventListener('dragover', function(e) {
......
......@@ -419,6 +419,15 @@ body > footer {
}
}
.host-toggle {
.v6 {
display: none;
}
}
.ipv4host {
display: none;
}
@media (max-width: 900px) {
#content {
width: 100%;
......@@ -438,3 +447,10 @@ body > footer {
font-size: 1.1em;
}
}
.vendor (@prop, @val) {
-foo: ~"bar; -webkit-@{prop}: @{val}; -moz-@{prop}: @{val}; -ms-@{prop}: @{val}; -o-@{prop}: @{val}; @{prop}: @{val};";
}
.vendorVal (@prop, @val, @arg) {
-foo: ~"bar; @{prop}: -webkit-@{val}(@{arg}); @{prop}: -moz-@{val}(@{arg}); @{prop}: -ms-@{val}(@{arg}); @{prop}: -o-@{val}(@{arg}); @{prop}: @{val}(@{arg});";
}
......@@ -67,10 +67,37 @@
list-style-type: none;
}
.entry {
&.editing {
.summary {
&:hover .name > span {
width: 100%;
max-width: 100%;
}
.name > span {
width: 100%;
max-width: 100%;
}
}
}
&.opened {
&.editing {
.summary {
.name > span {
width: 100%;
max-width: 100%;
}
}
}
.actions {
display: block !important;
}
.summary .name {
.vendorVal(width, calc, "100% - 200px");
span {
.vendorVal(width, calc, "100% - 110px");
.vendorVal(max-width, calc, "100% - 95px");
}
}
.summary .name .details {
display: inline;
border: none;
......@@ -81,11 +108,15 @@
cursor: default;
&:hover {
background-color: #c1c1c1;
.name {
width: 90%;
}
}
.name {
background: none !important;
text-align: center;
float: none;
width: 90%;
}
}
&.small-row .summary{
......@@ -168,11 +199,18 @@
.actions {
display: block;
}
.name .details {
display: inline;
.name {
.vendorVal(width, calc, "100% - 200px");
span {
.vendorVal(width, calc, "100% - 110px");
.vendorVal(max-width, calc, "100% - 95px");
}
.details {
display: block;
border: none;
}
}
}
.id {
float: right;
......@@ -186,11 +224,20 @@
z-index: 2;
position: relative;
height: 24px;
.vendorVal(width, calc, "100% - 90px");
span {
float: left;
text-overflow: ellipsis;
display: block;
white-space: nowrap;
overflow: hidden;
}
&.filetype-new-folder {
float: left;
}
.details {
display: none;
float: right;
}
}
.status {
......@@ -321,7 +368,7 @@
font-size: .8em;
text-align: right;
}
span {
.entry span {
background-position: left center;
background-repeat: no-repeat;
padding-left: 18px;
......@@ -513,6 +560,10 @@
.content {
padding: 15px;
}
.faded {
color: #666;
font-size: 0.8em;
}
}
table {
......@@ -537,7 +588,17 @@ table {
.summary {
.name {
background-image: url(/static/icons/users.png);
.vendorVal(width, calc, "100% - 70px");
}
}
li.owner {
background-image: url(/static/icons/user-worker-boss.png);
}
li.course {
background-image: url(/static/icons/book-open.png);
}
li.members {
background-image: url(/static/icons/users.png);
}
}
#new-group {
......
......@@ -84,7 +84,7 @@
<form action="{% url one.views.vm_unshare i.id %}" method="post">
<span title="{{i.name}}">{{i.name|truncatechars:20}}</span>
({{i.get_running}}/{{i.instance_limit}}) {{i.type}}
<a href="#" class="edit" data-id="{{i.id}}">
<a href="#" class="edit" data-url="{% url ajax_share_edit_wizard id=i.id %}">
<img src="{% static "icons/pencil.png" %}" alt="{% trans "Edit" %}" title="{% trans "Edit" %}" />
</a>
{% csrf_token %}
......
......@@ -84,9 +84,9 @@
{% trans "Size" %}:
<span class="value">
{{s.template.instance_type.name}}
<span class="cpu">{{s.template.instance_type.CPU}}</span>
<span class="memory">{{s.template.instance_type.RAM}}</span>
<span class="credit">{{s.template.instance_type.credit}}</span>
<span title="{% trans 'CPU cores' %}" class="cpu">{{s.template.instance_type.CPU}}</span>
<span title="{% trans 'RAM' %}" class="memory">{{s.template.instance_type.RAM}}</span>
<span title="{% trans 'Credit' %}" class="credit">{{s.template.instance_type.credit}}</span>
</span>
</li>
<li class="share-type">
......
......@@ -4,7 +4,7 @@
{% get_current_language as LANGUAGE_CODE %}
{% block content %}
<li class="entry {% if id == vm.id %}opened{% endif %}">
<li class="entry {% if id == vm.id %}opened{% endif %}" id="vm-{{vm.id}}">
{{block.super }}
</li>
{% endblock content %}
......@@ -22,17 +22,27 @@
</li>
<li class="os-{{vm.template.os_type}}">
{% trans "System" %}:
<span class="value">{{vm.template.system}}</span>
<span class="value">{{vm.template.system|default:"n/a"}}</span>
<div class="clear"></div>
</li>
<li class="template">
{% trans "Type" %}:
<span class="value">{% if vm.share %}{{vm.share.type}}{% else %}{% trans "Try" %}{% endif %}</span>
<span class="value">
{% if vm.share %}
{{vm.get_type}}
{% else %}
{% if vm.template.state != "READY" %}
{% trans "Master instance" %}
{% else %}
{% trans "Try" %}
{% endif %}
{% endif %}
</span>
<div class="clear"></div>
</li>
<li class="template">
{% trans "Share" %}:
<span class="value">{{vm.share.name}}</span>
<span class="value">{{vm.share.name|default:"n/a"}}</span>
<div class="clear"></div>
</li>
<li class="template">
......@@ -57,9 +67,9 @@
<li class="date">
{% trans "time of suspend"|capfirst %}:
<span class="value"> <abbr title="{{vm.time_of_suspend}}">{{vm.time_of_suspend|timeuntil}}</abbr>
{% if vm.share %}<a href="#" class="renew-vm renew-suspend-vm" data-id="{{ vm.id }}" title="{% trans "Renew suspend time" %}">
<a href="#" class="renew-vm renew-suspend-vm" data-id="{{ vm.id }}" title="{% trans "Renew suspend time" %}">
<img src="{% static "icons/control-double.png" %}" alt="{% trans "Renew suspend time" %}" />
</a>{% endif %}
</a>
</span>
</li>
{% endif %}
......@@ -67,9 +77,9 @@
<li class="date">
{% trans "time of delete"|capfirst %}:
<span class="value"> <abbr title="{{vm.time_of_delete}}">{{vm.time_of_delete|timeuntil}}</abbr>
{% if vm.share %}<a href="#" class="renew-vm renew-delete-vm" data-id="{{ vm.id }}" title="{% trans "Renew deletion time" %}">
<a href="#" class="renew-vm renew-delete-vm" data-id="{{ vm.id }}" title="{% trans "Renew deletion time" %}">
<img src="{% static "icons/control-double.png" %}" alt="{% trans "Renew deletion time" %}" />
</a>{% endif %}
</a>
</span>
</li>
{% endif %}
......
......@@ -11,7 +11,7 @@
{% block name %}
<div class="name {% if vm.state == 'ACTIVE' %}vm-on{% else %}vm-off{% endif %}">
<span id="vm-{{vm.id}}-name">{{vm.name|truncatechars:20}}</span>
<span id="vm-{{vm.id}}-name">{{vm.name}}</span>
<small id="vm-{{vm.id}}-name-details" class="details">
(<a href="{{ vm.get_absolute_url }}" title="{{vm.name}}">{% trans "More details" %}</a>)
</small>
......
......@@ -95,7 +95,7 @@
'type': 'GET',
'url': '{% url one.views.ajax_template_name_unique %}?name=' + $("#new-template-name").val(),
'success': function(data, b, c) {
if (data == "True" || s == original) {
if (data == "True" || $("#new-template-name").val() == original) {
$("#template-wizard").unbind('submit').submit()
}
else {
......
......@@ -37,11 +37,11 @@
{% for s in sizes %}
<p id="new-template-size-summary-{{s.id}}" class="size-summary clear"
{% if s != base.instance_type %}style="display:none"{% endif %}>
<span class="cpu">
<span title="{% trans 'CPU cores' %}" class="cpu">
{% blocktrans count n=s.CPU %}{{n}} core{% plural %}{{n}} cores{% endblocktrans %}
</span>
<span class="memory">{{s.RAM}} MiB</span>
<span class="credit">{{s.credit}}</span>
<span title="{% trans 'RAM' %}" class="memory">{{s.RAM}} MiB</span>
<span title="{% trans 'Credit' %}" class="credit">{{s.credit}}</span>
</p>
{% endfor %}
<div class="clear"></div>
......
......@@ -37,6 +37,10 @@
</script>
{% endblock %}
{% block title %}
{% blocktrans with name=i.name %}Details of {{name}}{% endblocktrans %}
- {{block.super}}
{% endblock %}
{% block content %}
{% if i.template.state != "READY" %}
......@@ -141,36 +145,63 @@
{% csrf_token %}
<table>
<tr>
<th>{% trans "Protocol" %}</th>
<th>{% trans "Public port" %}</th>
{% if i.template.network.nat %}<th colspan="2">{% trans "Private port" %}</th>{%endif%}
<th>{% trans "Port" %}</th>
<th colspan="2">{% trans "Access" %}</th>
</tr>
{% for port in ports %}
<tr>
<td>{{port.proto}}</td>
<td>{{port.public}}</td>
{% if i.template.network.nat %}<td>{{port.private}}</td>{%endif%}
<tr{% if i.is_ipv6 %} class="faded"{% endif %}>
<td>{{port.private}}/{{port.proto}}</td>
<td style="white-space: nowrap;">
{% if port.private == 80 or port.private == 8080 or port.private == 8000 %}
<a href="http://{{port.ipv4.host}}:{{port.ipv4.port}}">
{{port.ipv4.host}}:{{port.ipv4.port}}
</a>
{% elif port.private == 443 %}
<a href="https://{{port.ipv4.host}}:{{port.ipv4.port}}">{{port.ipv4.host}}:{{port.ipv4.port}}</a>
{% else %}
{{port.ipv4.host}}:{{port.ipv4.port}}
{% endif %}
</td>
{% if not i.is_ipv6 %}
<td>
<a href="{% url one.views.vm_port_del i.id port.proto port.private %}">{% trans "Delete" %}</a>
</td>
{% endif %}
</tr>
<tr{% if not i.is_ipv6 %} style="display: none"{% endif %}>
<td>{{port.private}}/{{port.proto}}6</td>
<td style="white-space: nowrap;">
{% if port.private == 80 or port.private == 8080 or port.private == 8000 %}
<a href="http://{{port.ipv6.host}}:{{port.ipv6.port}}">
{{port.ipv6.host}}:{{port.ipv6.port}}
</a>
{% elif port.private == 443 %}
<a href="https://{{port.ipv6.host}}:{{port.ipv6.port}}">
{{port.ipv6.host}}:{{port.ipv6.port}}
</a>
{% else %}
{{port.ipv6.host}}:{{port.ipv6.port}}
{% endif %}
</td>
{% if i.is_ipv6 %}
<td>
<a href="{% url one.views.vm_port_del i.id port.proto port.public %}">{% trans "Delete" %}</a>
<a href="{% url one.views.vm_port_del i.id port.proto port.private %}">{% trans "Delete" %}</a>
</td>
{% endif %}
</tr>
{% endfor %}
<tr>
<td>
<select style="min-width:50px;" name=proto>
<th colspan="3">{% trans "Port/Protocol" %}</th>
</tr>
<tr>
<td colspan="2">
<input style="min-width:50px;width:50px;" type="text" name="port"/>/
<select style="min-width:50px;" name="proto">
<option value="tcp">tcp</option>
<option value="udp">udp</option>
</select>
</td>
<td>
<input style="min-width:70px;width:70px;" type="text" name="public"/>
</td>
{% if i.template.network.nat %}
<td>
<input style="min-width:70px;width:70px;" type="text" name="private"/>
</td>
{% endif %}
<td>
<input type="submit" style="min-width:3em" value="{% trans "Add" %}" />
</td>
</tr>
......
......@@ -36,13 +36,13 @@
{{i.template.access_type|upper}}
</td>
</tr>
<tr>
<th>{% trans "IP" %}:</th>
<td>{{ i.hostname }}</td>
<tr{% if i.is_ipv6 %} class="faded"{% endif %}>
<th>{% trans "IPv4 Host" %}{% if i.is_ipv6 %} ({% trans "You are using IPv6" %}){% endif %}:</th>
<td>{{ i.hostname_v4 }}<strong>:{{ i.port_v4 }}</strong></td>
</tr>
<tr>
<th>{% trans "Port" %}:</th>
<td>{{ i.port}}</td>
<tr{% if not i.is_ipv6 %} style="display: none"{% endif %}>
<th>{% trans "IPv6 Host" %}{% if not i.is_ipv6 %} ({% trans "You are using IPv4" %}){% endif %}:</th>
<td>{{ i.hostname_v6 }}<strong>:{{ i.port_v6 }}</strong></td>
</tr>
<tr>
<th>{% trans "Username" %}:</th>
......
......@@ -16,7 +16,7 @@ from django.template import RequestContext
from django.template.loader import render_to_string
from django.utils.decorators import method_decorator
from django.utils.translation import get_language as lang
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext_lazy as _, ungettext_lazy
from django.views.decorators.http import *
from django.views.generic import *
from firewall.tasks import *
......@@ -104,9 +104,12 @@ def ajax_template_name_unique(request):
def vm_credentials(request, iid):
try:
vm = get_object_or_404(Instance, pk=iid, owner=request.user)
proto = len(request.META["REMOTE_ADDR"].split('.')) == 1
vm.hostname = vm.get_connect_host(use_ipv6=proto)
vm.port = vm.get_port(use_ipv6=proto)
is_ipv6 = len(request.META["REMOTE_ADDR"].split('.')) == 1
vm.hostname_v4 = vm.get_connect_host(use_ipv6=False)
vm.hostname_v6 = vm.get_connect_host(use_ipv6=True)
vm.is_ipv6 = is_ipv6
vm.port_v4 = vm.get_port(use_ipv6=False)
vm.port_v6 = vm.get_port(use_ipv6=True)
return render_to_response('vm-credentials.html', RequestContext(request, { 'i' : vm }))
except:
return HttpResponse(_("Could not get Virtual Machine credentials."), status=404)
......@@ -278,7 +281,7 @@ def _check_quota(request, template, share):
Returns if the given request is permitted to run the new vm.
"""
det = UserCloudDetails.objects.get(user=request.user)
if det.get_weighted_instance_count() + template.instance_type.credit >= det.instance_quota:
if det.get_weighted_instance_count() + template.instance_type.credit > det.instance_quota:
messages.error(request, _('You do not have any free quota. You can not launch this until you stop an other instance.'))
return False
if share:
......@@ -364,9 +367,12 @@ def vm_show(request, iid):
except UserCloudDetails.DoesNotExist:
details = UserCloudDetails(user=request.user)
details.save()
proto = len(request.META["REMOTE_ADDR"].split('.')) == 1
inst.hostname = inst.get_connect_host(use_ipv6=proto)
inst.port = inst.get_port(use_ipv6=proto)
is_ipv6 = len(request.META["REMOTE_ADDR"].split('.')) == 1
inst.is_ipv6 = is_ipv6
inst.hostname_v4 = inst.get_connect_host(use_ipv6=False)
inst.hostname_v6 = inst.get_connect_host(use_ipv6=True)
inst.port_v4 = inst.get_port(use_ipv6=False)
inst.port_v6 = inst.get_port(use_ipv6=True)
return render_to_response("show.html", RequestContext(request,{
'uri': inst.get_connect_uri(),
'state': inst.state,
......@@ -415,20 +421,19 @@ def boot_token(request, token):
class VmPortAddView(View):
def post(self, request, iid, *args, **kwargs):
try:
public = int(request.POST['public'])
if public >= 22000 and public < 24000:
raise ValidationError(_("Port number is in a restricted domain (22000 to 24000)."))
port = int(request.POST['port'])
inst = get_object_or_404(Instance, id=iid, owner=request.user)
if inst.template.network.nat:
private = private=int(request.POST['private'])
if inst.nat:
inst.firewall_host.add_port(proto=request.POST['proto'],
public=None, private=port)
else:
private = 0
inst.firewall_host.add_port(proto=request.POST['proto'], public=public, private=private)
messages.success(request, _(u"Port %d successfully added.") % public)
inst.firewall_host.add_port(proto=request.POST['proto'],
public=port, private=None)
except:
messages.error(request, _(u"Adding port failed."))
# raise
else:
messages.success(request, _(u"Port %d successfully added.") % port)
return redirect('/vm/show/%d/' % int(iid))
def get(self, request, iid, *args, **kwargs):
......@@ -439,11 +444,12 @@ vm_port_add = login_required(VmPortAddView.as_view())
@require_safe
@login_required
@require_GET
def vm_port_del(request, iid, proto, public):
def vm_port_del(request, iid, proto, private):
inst = get_object_or_404(Instance, id=iid, owner=request.user)
try:
inst.firewall_host.del_port(proto=proto, public=public)
messages.success(request, _(u"Port %s successfully removed.") % public)
inst.firewall_host.del_port(proto=proto, private=private)
messages.success(request, _(u"Port %s successfully removed.") %
private)
except:
messages.error(request, _(u"Removing port failed."))
return redirect('/vm/show/%d/' % int(iid))
......@@ -478,12 +484,21 @@ def vm_unshare(request, id, *args, **kwargs):
if not g.owners.filter(user=request.user).exists():
raise PermissionDenied()
try:
if s.get_running_or_stopped() > 0:
messages.error(request, _('There are machines running of this share.'))
n = s.get_running()
m = s.get_running_or_stopped()
if n > 0:
messages.error(request, ungettext_lazy('There is a machine running of this share.',
'There are %(n)d machines running of this share.', n) %
{'n' : n})
elif m > 0:
messages.error(request, ungettext_lazy('There is a suspended machine of this share.',
'There are %(m)d suspended machines of this share.', m) %
{'m' : m})
else:
s.delete()
messages.success(request, _('Share is successfully removed.'))
except:
except Exception as e:
print e
messages.error(request, _('Failed to remove share.'))
return redirect(g)
......@@ -516,11 +531,15 @@ def vm_resume(request, iid, *args, **kwargs):
@require_POST
def vm_renew(request, which, iid, *args, **kwargs):
try:
get_object_or_404(Instance, id=iid, owner=request.user).renew()
vm = get_object_or_404(Instance, id=iid, owner=request.user)
vm.renew()
messages.success(request, _('Virtual machine is successfully renewed.'))
except:
messages.error(request, _('Failed to renew virtual machine.'))
return redirect('/')
return render_to_response('box/vm/entry.html', RequestContext(request, {
'vm': vm
}))
@login_required
@require_POST
......
......@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-22 10:43+0100\n"
"PO-Revision-Date: 2013-03-07 17:48+0100\n"
"POT-Creation-Date: 2013-04-04 18:33+0200\n"
"PO-Revision-Date: 2013-04-04 18:03+0200\n"
"Last-Translator: \n"
"Language-Team: American English <cloud@ik.bme.hu>\n"
"Language: en_US\n"
......@@ -155,39 +155,44 @@ msgstr "Érvénytelen Neptun-kód."
msgid "Invalid NEPTUN code"
msgstr "Érvénytelen Neptun-kód"
#: templates/show-group.html:9
#: templates/show-group.html:7
#, python-format
msgid "Details of %(name)s"
msgstr "%(name)s részletei"
#: templates/show-group.html:15
msgid "Owners of"
msgstr "Tulajdonosok"
#: templates/show-group.html:23 templates/box/person/entry.html:23
#: templates/show-group.html:29 templates/box/person/entry.html:23
msgid "Remove"
msgstr "Eltávolítás"
#: templates/show-group.html:31 templates/box/person/entry.html:32
#: templates/show-group.html:37 templates/box/person/entry.html:32
msgid "This user never logged in, no data available"
msgstr "Ez a felhasználó még nem lépett be, nincs adat róla"
#: templates/show-group.html:35 templates/box/person/entry.html:36
#: templates/show-group.html:41 templates/box/person/entry.html:36
msgid "Name"
msgstr "Név"
#: templates/show-group.html:38 templates/box/person/entry.html:39
#: templates/show-group.html:44 templates/box/person/entry.html:39
msgid "NEPTUN"
msgstr "NEPTUN"
#: templates/show-group.html:51
#: templates/show-group.html:57
msgid "Add owner"
msgstr "Tulajdonos hozzáadása"
#: templates/show-group.html:56
#: templates/show-group.html:62
msgid "Owner name/NEPTUN"
msgstr "Tulajdonos neve/NEPTUN-kódja"
#: templates/show-group.html:69
#: templates/show-group.html:75
msgid "This group has no shared templates."
msgstr "Ennek a csoportnak egy sablon sincs megosztva."
#: templates/show-group.html:72
#: templates/show-group.html:78
msgid "Share one, and the group members can start their own virtual machine."
msgstr ""
"Osszon meg egyet, hogy a csoport tagjai is elindíthassák egy példányát."
......
......@@ -29,7 +29,7 @@
{% block details %}
<ul>
<li>
<li class="course">
{% trans "Course" %}:
<span class="value">
{% if group.course %}
......@@ -39,15 +39,15 @@
{% endif %}
</span>
</li>
<li>
<li class="date">
{% trans "Semester" %}:
<span class="value">{{group.semester.name}}</span>
</li>
<li>
<li class="owner">
{% trans "Owner(s)" %}:
<span class="value">{{group.owner_list}}</span>
</li>
<li>
<li class="members">
{% trans "Member count" %}:
<span class="value">{{group.member_count}}</span>
</li>
......
......@@ -2,6 +2,12 @@
{% load i18n %}
{% load staticfiles %}
{% get_current_language as LANGUAGE_CODE %}
{% block title %}
{% blocktrans with name=group.name %}Details of {{name}}{% endblocktrans %}
- {{block.super}}
{% endblock %}
{% block content %}
<div class="boxes">
{% include "box/person/box.html" %}
......
from django.test import TestCase
from models import create_user_profile, Person
from models import create_user_profile, Person, Course, Semester
from datetime import datetime, timedelta
class MockUser:
username = "testuser"
......@@ -50,3 +51,24 @@ class PersonTestCase(TestCase):
def test_unicode(self):
# TODO
self.testperson.__unicode__()
class CourseTestCase(TestCase):
def setUp(self):
now = datetime.now()
delta = timedelta(weeks=7)
self.testperson1 = Person.objects.create(code="testperson1")
self.testperson2 = Person.objects.create(code="testperson2")
self.testsemester = Semester.objects.create(name="testsemester",
start=now-delta, end=now+delta)
self.testcourse = Course.objects.create(code="testcode",
name="testname", short_name="tn")
self.testcourse.owners.add(self.testperson1, self.testperson2)
def test_get_or_create_default_group(self):
default_group = self.testcourse.get_or_create_default_group()
self.assertIsNotNone(default_group)
self.assertEquals(default_group, self.testcourse.default_group)
def test_owner_list(self):
owner_list = self.testcourse.owner_list()
self.assertIsNotNone(owner_list)
......@@ -198,7 +198,7 @@ def gui(request):
else:
return HttpResponse('Can not update authorization information!')
if StoreApi.updateauthorizationinfo(user, password, key_list):
return HttpResponse('https://cloud.ik.bme.hu/?neptun='+user+"&"+"host="+StoreApi.get_host())
return HttpResponse('https://cloud.ik.bme.hu/home/'+'?neptun='+user+'&'+'host='+StoreApi.get_host())
else:
return HttpResponse('Can not update authorization information!')
else:
......
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