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 @@ ...@@ -14,7 +14,15 @@
<a id="master"></a> <a id="master"></a>
<section> <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> <ul>
<li>Súgó.</li> <li>Súgó.</li>
<li>Változáslista.</li> <li>Változáslista.</li>
...@@ -26,7 +34,7 @@ ...@@ -26,7 +34,7 @@
</section> </section>
<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> <ul>
<li>Határidős felfüggesztés élesítve.</li> <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> <li>Csatlakozási adatoknál IP cím helyett DNS név jelenik meg.</li>
...@@ -50,7 +58,7 @@ ...@@ -50,7 +58,7 @@
</section> </section>
<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> <ul>
<li>Felhasználói kvóták megvalósítása.</li> <li>Felhasználói kvóták megvalósítása.</li>
<li>Publikus kulcsok kezelése.</li> <li>Publikus kulcsok kezelése.</li>
......
...@@ -37,7 +37,7 @@ urlpatterns = patterns('', ...@@ -37,7 +37,7 @@ urlpatterns = patterns('',
url(r'^vm/renew/(?P<which>(suspend|delete))/(?P<iid>\d+)/$', url(r'^vm/renew/(?P<which>(suspend|delete))/(?P<iid>\d+)/$',
'one.views.vm_renew', ), 'one.views.vm_renew', ),
url(r'^vm/port_add/(?P<iid>\d+)/$', 'one.views.vm_port_add', ), 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', ), 'one.views.vm_port_del', ),
url(r'^ajax/shareEdit/(?P<id>\d+)/$', 'one.views.ajax_share_edit_wizard', url(r'^ajax/shareEdit/(?P<id>\d+)/$', 'one.views.ajax_share_edit_wizard',
name='ajax_share_edit_wizard'), name='ajax_share_edit_wizard'),
......
...@@ -10,6 +10,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator ...@@ -10,6 +10,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator
from cloud.settings import firewall_settings as settings from cloud.settings import firewall_settings as settings
from django.db.models.signals import post_save from django.db.models.signals import post_save
import re import re
import random
class Rule(models.Model): class Rule(models.Model):
CHOICES_type = (('host', 'host'), ('firewall', 'firewall'), CHOICES_type = (('host', 'host'), ('firewall', 'firewall'),
...@@ -174,15 +175,30 @@ class Host(models.Model): ...@@ -174,15 +175,30 @@ class Host(models.Model):
def enable_net(self): def enable_net(self):
self.groups.add(Group.objects.get(name="netezhet")) 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" proto = "tcp" if proto == "tcp" else "udp"
if self.shared_ip: 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: if public < 1024:
raise ValidationError(_("Only ports above 1024 can be used.")) raise ValidationError(_("Only ports above 1024 can be used."))
for host in Host.objects.filter(pub_ipv4=self.pub_ipv4): if public in used_ports:
if host.rules.filter(nat=True, proto=proto, dport=public):
raise ValidationError(_("Port %s %s is already in use.") % raise ValidationError(_("Port %s %s is already in use.") %
(proto, public)) (proto, public))
rule = Rule(direction='1', owner=self.owner, dport=public, rule = Rule(direction='1', owner=self.owner, dport=public,
proto=proto, nat=True, accept=True, r_type="host", proto=proto, nat=True, accept=True, r_type="host",
nat_dport=private, host=self, foreign_network=VlanGroup. nat_dport=private, host=self, foreign_network=VlanGroup.
...@@ -199,15 +215,58 @@ class Host(models.Model): ...@@ -199,15 +215,58 @@ class Host(models.Model):
rule.full_clean() rule.full_clean()
rule.save() 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, 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): def list_ports(self):
return [{'proto': rule.proto, retval = []
'public': rule.dport, for rule in self.rules.filter(owner=self.owner):
'private': rule.nat_dport} for rule in private = rule.nat_dport if self.shared_ip else rule.dport
self.rules.filter(owner=self.owner)] 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): def get_fqdn(self):
return self.hostname + u'.' + unicode(self.vlan.domain) 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: ...@@ -183,6 +183,7 @@ class Browser:
def load_committed_cb(self,web_view, frame): def load_committed_cb(self,web_view, frame):
uri = frame.get_uri() uri = frame.get_uri()
print uri
try: try:
self.webview.execute_script('document.getElementsByTagName("a")[0].target="";') self.webview.execute_script('document.getElementsByTagName("a")[0].target="";')
except: except:
...@@ -193,7 +194,7 @@ class Browser: ...@@ -193,7 +194,7 @@ class Browser:
### JS ### JS
self.post_key(self.public_key_b64) self.post_key(self.public_key_b64)
### Parse values and do mounting ### ### 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: if self.mounted != True:
try: try:
uri, params = uri.split('?', 1) 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 #!/usr/bin/python
import xmltodict
import xml.dom.minidom as minidom import xml.dom.minidom as minidom
import sys import sys
import json import json
...@@ -23,6 +22,7 @@ mem_max = 0 ...@@ -23,6 +22,7 @@ mem_max = 0
running_vms = 0 running_vms = 0
for host in hosts: for host in hosts:
if host.getElementsByTagName("STATE")[0].childNodes[0].data == "2":
share = host.getElementsByTagName("HOST_SHARE")[0] share = host.getElementsByTagName("HOST_SHARE")[0]
cpu_max += int(share.getElementsByTagName("MAX_CPU")[0].childNodes[0].data) cpu_max += int(share.getElementsByTagName("MAX_CPU")[0].childNodes[0].data)
used_cpu += int(share.getElementsByTagName("USED_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): ...@@ -98,8 +98,19 @@ class InstanceAdmin(contrib.admin.ModelAdmin):
class DiskAdmin(contrib.admin.ModelAdmin): class DiskAdmin(contrib.admin.ModelAdmin):
model=models.Disk 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): class NetworkAdmin(contrib.admin.ModelAdmin):
model=models.Network model=models.Network
list_display = ('name', 'nat', 'public', 'get_vlan')
class ShareAdmin(contrib.admin.ModelAdmin): class ShareAdmin(contrib.admin.ModelAdmin):
model=models.Network model=models.Network
list_filter = ('group', 'template', ) list_filter = ('group', 'template', )
......
...@@ -6,8 +6,8 @@ msgid "" ...@@ -6,8 +6,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-22 10:43+0100\n" "POT-Creation-Date: 2013-04-04 18:33+0200\n"
"PO-Revision-Date: 2013-03-07 17:02+0100\n" "PO-Revision-Date: 2013-04-04 18:03+0200\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n" "Language-Team: Hungarian <cloud@ik.bme.hu>\n"
"Language: hu\n" "Language: hu\n"
...@@ -17,17 +17,18 @@ msgstr "" ...@@ -17,17 +17,18 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n"
"X-Generator: Lokalize 1.4\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?" msgid "Are you sure deleting key?"
msgstr "Biztosan törli a kulcsot?" msgstr "Biztosan törli a kulcsot?"
#: static/script/cloud.js:24 static/script/cloud.js.c:301 #: static/script/cloud.js:41 static/script/cloud.js.c:338
#: static/script/cloud.js:373 static/script/cloud.js.c:600 #: static/script/cloud.js:416 static/script/cloud.js.c:644
#: static/script/store.js:288 #: static/script/cloud.min.js:1 static/script/store.js:288
#: static/script/store.min.js:1
msgid "Delete" msgid "Delete"
msgstr "Törlés" msgstr "Törlés"
#: static/script/cloud.js:36 #: static/script/cloud.js:53 static/script/cloud.min.js:1
msgid "" msgid ""
"Are you sure about reseting store credentials?<br /> You will lose your " "Are you sure about reseting store credentials?<br /> You will lose your "
"access to your store account on your existing virtual machines!" "access to your store account on your existing virtual machines!"
...@@ -35,92 +36,114 @@ msgstr "" ...@@ -35,92 +36,114 @@ msgstr ""
"Biztosan újragenerálja az adattár-kulcsait?<br /> El fogja veszteni az " "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!" "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" msgid "Reset"
msgstr "Újragenerálás" 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" msgid "Rename"
msgstr "Átnevezés" 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" msgid "Cancel"
msgstr "Mégsem" msgstr "Mégsem"
#: static/script/cloud.js:290 #: static/script/cloud.js:327 static/script/cloud.min.js:1
#, c-format #, c-format
msgid "Are you sure stopping %s?" msgid "Are you sure stopping %s?"
msgstr "Biztosan felfüggeszti a következőt: %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" msgid "Stop"
msgstr "Felfüggesztés" msgstr "Felfüggesztés"
#: static/script/cloud.js:300 #: static/script/cloud.js:337 static/script/cloud.min.js:1
#, c-format #, c-format
msgid "Are you sure deleting %s?" msgid "Are you sure deleting %s?"
msgstr "Biztosan törli a következőt: %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 #, c-format
msgid "Are you sure restarting %s?" msgid "Are you sure restarting %s?"
msgstr "Biztosan újraindítja a következőt: %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" msgid "Restart"
msgstr "Újraindítás" msgstr "Újraindítás"
#: static/script/cloud.js:372 #: static/script/cloud.js:415 static/script/cloud.min.js:1
#, c-format #, c-format
msgid "Are you sure deleting this %s template?" msgid "Are you sure deleting this %s template?"
msgstr "Biztosan törli a következő sablont: %s?" 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" msgid "Add owner"
msgstr "Tulajdonos hozzáadása" msgstr "Tulajdonos hozzáadása"
#: static/script/cloud.js:554 #: static/script/cloud.js:598 static/script/cloud.min.js:1
msgid "Unknown" msgid "Unknown"
msgstr "Ismeretlen" msgstr "Ismeretlen"
#: static/script/cloud.js:600 #: static/script/cloud.js:644 static/script/cloud.min.js:1
#, c-format #, c-format
msgid "Are you sure deleting <strong>%s</strong>" msgid "Are you sure deleting <strong>%s</strong>"
msgstr "Törli a következő fájlt: <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:52 static/script/store.js.c:61
#: static/script/store.js:70 static/script/store.js.c:220 #: 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" msgid "file"
msgstr "fájl" msgstr "fájl"
#: static/script/store.js:125 #: static/script/store.js:125 static/script/store.min.js:1
msgid "Toplist" msgid "Toplist"
msgstr "Legújabb fájlok" 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" msgid "Back to the root folder"
msgstr "Vissza a gyökérmappába" 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 #, c-format
msgid "You are removing the file <strong>%s</strong>." msgid "You are removing the file <strong>%s</strong>."
msgstr "Törli a következő fájlt: <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 #, c-format
msgid "You are removing the folder <strong>%s</strong> (and its content)." 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>." 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?" msgid "Are you sure?"
msgstr "Biztos benne?" 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" msgid "Upload"
msgstr "Feltöltés" msgstr "Feltöltés"
#: static/script/store.js:448 #: static/script/store.js:465 static/script/store.min.js:1
msgid "done, processing..." msgid "done, processing..."
msgstr "kész, feldolgozás..." msgstr "kész, feldolgozás..."
......
...@@ -158,7 +158,6 @@ class SshKey(models.Model): ...@@ -158,7 +158,6 @@ class SshKey(models.Model):
TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')), TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')),
("SAVE", _('saving')), ("READY", _('ready'))) ("SAVE", _('saving')), ("READY", _('ready')))
TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB", TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB",
"suspend": td(hours=5), "delete": td(days=15), "suspend": td(hours=5), "delete": td(days=15),
"help_text": _('For lab or homework with short lifetime.')}, "help_text": _('For lab or homework with short lifetime.')},
...@@ -169,6 +168,7 @@ TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB", ...@@ -169,6 +168,7 @@ TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB",
"suspend": td(days=365), "delete": None, "suspend": td(days=365), "delete": None,
"help_text": _('For long-term server use.')}, "help_text": _('For long-term server use.')},
} }
DEFAULT_TYPE = TYPES['LAB']
TYPES_L = sorted(TYPES.values(), key=lambda m: m["suspend"]) TYPES_L = sorted(TYPES.values(), key=lambda m: m["suspend"])
TYPES_C = tuple([(i[0], i[1]["verbose_name"]) for i in TYPES.items()]) TYPES_C = tuple([(i[0], i[1]["verbose_name"]) for i in TYPES.items()])
...@@ -188,13 +188,23 @@ class Share(models.Model): ...@@ -188,13 +188,23 @@ class Share(models.Model):
'user.')) 'user.'))
owner = models.ForeignKey(User, null=True, blank=True, related_name='share_set') owner = models.ForeignKey(User, null=True, blank=True, related_name='share_set')
def get_type(self): class Meta:
t = TYPES[self.type] 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'] t['deletex'] = (datetime.now() + td(seconds=1) + t['delete']
if t['delete'] else None) if t['delete'] else None)
t['suspendx'] = (datetime.now() + td(seconds=1) + t['suspend'] t['suspendx'] = (datetime.now() + td(seconds=1) + t['suspend']
if t['suspend'] else None) if t['suspend'] else None)
return t return t
def get_type(self):
t = TYPES[self.type]
return self.extend_type(t)
def get_running_or_stopped(self, user=None): def get_running_or_stopped(self, user=None):
running = (Instance.objects.all().exclude(state='DONE') running = (Instance.objects.all().exclude(state='DONE')
.filter(share=self)) .filter(share=self))
...@@ -210,9 +220,14 @@ class Share(models.Model): ...@@ -210,9 +220,14 @@ class Share(models.Model):
return running.filter(owner=user).count() return running.filter(owner=user).count()
else: else:
return running.count() return running.count()
def get_instance_pc(self): def get_instance_pc(self):
return float(self.get_running()) / self.instance_limit * 100 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): class Disk(models.Model):
"""Virtual disks automatically synchronized with OpenNebula.""" """Virtual disks automatically synchronized with OpenNebula."""
name = models.CharField(max_length=100, unique=True, name = models.CharField(max_length=100, unique=True,
...@@ -220,6 +235,8 @@ class Disk(models.Model): ...@@ -220,6 +235,8 @@ class Disk(models.Model):
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
verbose_name = _('disk')
verbose_name_plural = _('disks')
def __unicode__(self): def __unicode__(self):
return u"%s (#%d)" % (self.name, self.id) return u"%s (#%d)" % (self.name, self.id)
...@@ -259,6 +276,9 @@ class Network(models.Model): ...@@ -259,6 +276,9 @@ class Network(models.Model):
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
verbose_name = _('network')
verbose_name_plural = _('networks')
def __unicode__(self): def __unicode__(self):
return self.name return self.name
...@@ -285,6 +305,8 @@ class Network(models.Model): ...@@ -285,6 +305,8 @@ class Network(models.Model):
Network(id=id, name=name).save() Network(id=id, name=name).save()
l.append(id) l.append(id)
Network.objects.exclude(id__in=l).delete() Network.objects.exclude(id__in=l).delete()
def get_vlan(self):
return Vlan.objects.get(vid=self.id)
class InstanceType(models.Model): class InstanceType(models.Model):
...@@ -298,6 +320,8 @@ class InstanceType(models.Model): ...@@ -298,6 +320,8 @@ class InstanceType(models.Model):
class Meta: class Meta:
ordering = ['credit'] ordering = ['credit']
verbose_name = _('instance type')
verbose_name_plural = _('instance types')
def __unicode__(self): def __unicode__(self):
return u"%s" % self.name return u"%s" % self.name
...@@ -332,6 +356,8 @@ class Template(models.Model): ...@@ -332,6 +356,8 @@ class Template(models.Model):
class Meta: class Meta:
verbose_name = _('template') verbose_name = _('template')
verbose_name_plural = _('templates') verbose_name_plural = _('templates')
ordering = ['name', ]
def __unicode__(self): def __unicode__(self):
return self.name return self.name
...@@ -357,7 +383,7 @@ class Template(models.Model): ...@@ -357,7 +383,7 @@ class Template(models.Model):
class Instance(models.Model): class Instance(models.Model):
"""Virtual machine instance.""" """Virtual machine instance."""
name = models.CharField(max_length=100, unique=True, name = models.CharField(max_length=100,
verbose_name=_('name'), blank=True) verbose_name=_('name'), blank=True)
ip = models.IPAddressField(blank=True, null=True, ip = models.IPAddressField(blank=True, null=True,
verbose_name=_('IP address')) verbose_name=_('IP address'))
...@@ -396,6 +422,7 @@ class Instance(models.Model): ...@@ -396,6 +422,7 @@ class Instance(models.Model):
class Meta: class Meta:
verbose_name = _('instance') verbose_name = _('instance')
verbose_name_plural = _('instances') verbose_name_plural = _('instances')
ordering = ['pk', ]
def __unicode__(self): def __unicode__(self):
return self.name return self.name
...@@ -407,28 +434,17 @@ class Instance(models.Model): ...@@ -407,28 +434,17 @@ class Instance(models.Model):
def get_port(self, use_ipv6=False): def get_port(self, use_ipv6=False):
"""Get public port number for default access method.""" """Get public port number for default access method."""
proto = self.template.access_type 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]) return {"rdp": 23000, "nx": 22000, "ssh": 22000}[proto] + int(self.ip.split('.')[2]) * 256 + int(self.ip.split('.')[3])
else: else:
return {"rdp": 3389, "nx": 22, "ssh": 22}[proto] return {"rdp": 3389, "nx": 22, "ssh": 22}[proto]
def get_connect_host(self, use_ipv6=False): def get_connect_host(self, use_ipv6=False):
"""Get public hostname.""" """Get public hostname."""
if self.firewall_host is None: if self.firewall_host is None:
return _('None') return _('None')
try: proto = 'ipv6' if use_ipv6 else 'ipv4'
if use_ipv6: return self.firewall_host.get_hostname(proto=proto)
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
def get_connect_uri(self, use_ipv6=False): def get_connect_uri(self, use_ipv6=False):
"""Get access parameters in URI format.""" """Get access parameters in URI format."""
...@@ -472,6 +488,15 @@ class Instance(models.Model): ...@@ -472,6 +488,15 @@ class Instance(models.Model):
self.check_if_is_save_as_done() self.check_if_is_save_as_done()
return x 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): def get_age(self):
"""Get age of VM in seconds.""" """Get age of VM in seconds."""
from datetime import datetime from datetime import datetime
...@@ -491,7 +516,7 @@ class Instance(models.Model): ...@@ -491,7 +516,7 @@ class Instance(models.Model):
inst = Instance(pw=pwgen(), template=template, owner=owner, inst = Instance(pw=pwgen(), template=template, owner=owner,
share=share, state='PENDING') share=share, state='PENDING')
inst.save() inst.save()
hostname = u"cloud-%d" % (inst.id, ) hostname = u"%d" % (inst.id, )
with tempfile.NamedTemporaryFile(delete=False) as f: with tempfile.NamedTemporaryFile(delete=False) as f:
os.chmod(f.name, stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH) os.chmod(f.name, stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)
token = signing.dumps(inst.id, salt='activate') token = signing.dumps(inst.id, salt='activate')
...@@ -638,13 +663,20 @@ class Instance(models.Model): ...@@ -638,13 +663,20 @@ class Instance(models.Model):
def renew(self, which='both'): def renew(self, which='both'):
if which in ['suspend', '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']: 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']): if not (which in ['suspend', 'delete', 'both']):
raise ValueError('No such expiration type.') raise ValueError('No such expiration type.')
self.save() 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): def save_as(self):
"""Save image and shut down.""" """Save image and shut down."""
imgname = "template-%d-%d" % (self.template.id, self.id) imgname = "template-%d-%d" % (self.template.id, self.id)
......
...@@ -9,7 +9,24 @@ $(function() { ...@@ -9,7 +9,24 @@ $(function() {
$(this).next('.details').slideDown(700); $(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(); e.stopPropagation();
}); });
$('.delete-template').click(function(e) { $('.delete-template').click(function(e) {
...@@ -64,12 +81,14 @@ $(function() { ...@@ -64,12 +81,14 @@ $(function() {
var content = $('#vm-' + id + '-name').html(); var content = $('#vm-' + id + '-name').html();
var self=this; var self=this;
var url = $(this).data('url'); var url = $(this).data('url');
$('#vm-'+id).addClass('editing');
$(this).unbind('click').click(function(e){ $(this).unbind('click').click(function(e){
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
$(this).unbind('click').click(handler); $(this).unbind('click').click(handler);
$('#vm-' + id + '-name-details').show(); $('#vm-' + id + '-name-details').show();
$('#vm-' + id + '-name').html(content); $('#vm-' + id + '-name').html(content);
$('#vm-'+id).removeClass('editing');
}) })
$('#vm-' + id + '-name-details').hide(); $('#vm-' + id + '-name-details').hide();
$('#vm-' + id + '-name').html('<input type="text" value="' + oldName + '" />\ $('#vm-' + id + '-name').html('<input type="text" value="' + oldName + '" />\
...@@ -91,6 +110,8 @@ $(function() { ...@@ -91,6 +110,8 @@ $(function() {
$('#vm-' + id + '-name-details').removeAttr('style'); $('#vm-' + id + '-name-details').removeAttr('style');
$('#vm-' + id + '-name').text(data.name); $('#vm-' + id + '-name').text(data.name);
$(self).click(handler); $(self).click(handler);
$(self).data('name', newName);
$('#vm-'+id).removeClass('editing');
} }
}); });
}) })
...@@ -120,7 +141,7 @@ $(function() { ...@@ -120,7 +141,7 @@ $(function() {
e.stopPropagation(); e.stopPropagation();
restart_vm($(this).data('id'), $(this).data('name')); 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.preventDefault();
e.stopPropagation(); e.stopPropagation();
renew_suspend_vm($(this).data('id')) renew_suspend_vm($(this).data('id'))
...@@ -255,6 +276,20 @@ $(function() { ...@@ -255,6 +276,20 @@ $(function() {
function get_vm_details(id) { function get_vm_details(id) {
$.get('/vm/credentials/' + id, function(data) { $.get('/vm/credentials/' + id, function(data) {
$('#modal-container').html(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() { $('#modal-container .hidden-password').click(function() {
if ($(this).attr('type') == 'password') { if ($(this).attr('type') == 'password') {
$(this).attr('type', 'text'); $(this).attr('type', 'text');
...@@ -282,6 +317,8 @@ $(function() { ...@@ -282,6 +317,8 @@ $(function() {
$('#modal').hide(); $('#modal').hide();
}); });
} }
cloud.confirm=vm_confirm_popup;
/** /**
* Manage VM State (STOP) * Manage VM State (STOP)
*/ */
...@@ -324,7 +361,11 @@ $(function() { ...@@ -324,7 +361,11 @@ $(function() {
*/ */
function renew_suspend_vm(id) { 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. * Renew vm deletion time.
...@@ -337,12 +378,14 @@ $(function() { ...@@ -337,12 +378,14 @@ $(function() {
* Manage VM State generic * Manage VM State generic
*/ */
function manage_vm(id, state) { function manage_vm(id, state, f) {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/vm/' + state + '/' + id + '/', url: '/vm/' + state + '/' + id + '/',
success: function(data, b, c) { success: function(data, b, c) {
if (state == "resume") { if(f) {
f(data);
} else if (state == "resume") {
window.location.href = '/vm/show/' + id + "/"; window.location.href = '/vm/show/' + id + "/";
} else { } else {
window.location.reload(); window.location.reload();
...@@ -371,7 +414,7 @@ $(function() { ...@@ -371,7 +414,7 @@ $(function() {
function delete_template_confirm(url, id, name) { function delete_template_confirm(url, id, name) {
confirm_message = interpolate(gettext("Are you sure deleting this %s template?"), ["<strong>" + name + "</strong>"]) confirm_message = interpolate(gettext("Are you sure deleting this %s template?"), ["<strong>" + name + "</strong>"])
vm_confirm_popup(confirm_message, gettext("Delete"), function() { vm_confirm_popup(confirm_message, gettext("Delete"), function() {
delete_template(id) delete_template(url, id)
}) })
} }
/** /**
...@@ -389,7 +432,8 @@ $(function() { ...@@ -389,7 +432,8 @@ $(function() {
alert(data['responseText']); alert(data['responseText']);
}, },
200: function(data) { 200: function(data) {
$("#t" + id).remove() $("#t" + id).remove();
alert(gettext('Template deletion successful!'));
}, },
} }
......
...@@ -343,7 +343,7 @@ var cloud = (function(cloud) { ...@@ -343,7 +343,7 @@ var cloud = (function(cloud) {
/** /**
* Requests a new upload link from the store server * Requests a new upload link from the store server
*/ */
self.getUploadURL = function() { self.getUploadURL = function(f) {
uploadURLRequestInProgress = true; uploadURLRequestInProgress = true;
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
...@@ -353,6 +353,7 @@ var cloud = (function(cloud) { ...@@ -353,6 +353,7 @@ var cloud = (function(cloud) {
success: function(data) { success: function(data) {
self.uploadURL(data.url); self.uploadURL(data.url);
uploadURLRequestInProgress = false; uploadURLRequestInProgress = false;
if (typeof f == 'function') f();
} }
}) })
} }
...@@ -387,8 +388,31 @@ var cloud = (function(cloud) { ...@@ -387,8 +388,31 @@ var cloud = (function(cloud) {
* Uploads the specified file(s) * Uploads the specified file(s)
*/ */
var readfiles = cloud.delayUntil(function(file, next) { var readfiles = cloud.delayUntil(function(file, next) {
console.log('read', file, next) for (var i in self.files()) {
//1 GB file limit 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) { if (file.size > 1024 * 1024 * 1024) {
$('#upload-zone').hide(); $('#upload-zone').hide();
$('#upload-error').show(); $('#upload-error').show();
...@@ -407,18 +431,11 @@ var cloud = (function(cloud) { ...@@ -407,18 +431,11 @@ var cloud = (function(cloud) {
var start = new Date().getTime(); var start = new Date().getTime();
xhr.open('POST', self.uploadURL()); xhr.open('POST', self.uploadURL());
xhr.onload = xhr.onerror = function() { xhr.onload = xhr.onerror = function() {
console.log('onload');
self.uploadProgress('0%'); self.uploadProgress('0%');
self.uploadURL('/'); self.uploadURL('/');
if (next) { console.log('complete, next');
console.log('complete, next')
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) { if (tests.progress) {
$('#upload-zone').hide(); $('#upload-zone').hide();
...@@ -466,22 +483,25 @@ var cloud = (function(cloud) { ...@@ -466,22 +483,25 @@ var cloud = (function(cloud) {
var files = e.dataTransfer.files; var files = e.dataTransfer.files;
console.log(files); console.log(files);
console.log(e.dataTransfer.files); console.log(e.dataTransfer.files);
var i = 1; var i = 0;
readfiles(e.dataTransfer.files[0], function() { (function next() {
console.log('next', i); if (i >= files.length) {
next = arguments.callee; $('.file-upload').removeClass('opened');
return function() { $('.file-upload .details').slideUp(700);
console.log('readnext', i, len); $('#upload-zone').show();
if (i >= len - 2) { $('#upload-progress-text').hide();
console.log('end', i, len); loadFolder(self.currentPath());
self.getUploadURL();
readfiles(files[i++], null);
return; return;
} }
self.getUploadURL(); console.log('reading file ' + i);
readfiles(files[i++], next()); if (self.uploadURL() == '/') {
self.getUploadURL(function() {
readfiles(files[i++], next);
});
} else {
readfiles(files[i++], next);
} }
}()); })();
return false; return false;
}); });
document.addEventListener('dragover', function(e) { document.addEventListener('dragover', function(e) {
......
...@@ -419,6 +419,15 @@ body > footer { ...@@ -419,6 +419,15 @@ body > footer {
} }
} }
.host-toggle {
.v6 {
display: none;
}
}
.ipv4host {
display: none;
}
@media (max-width: 900px) { @media (max-width: 900px) {
#content { #content {
width: 100%; width: 100%;
...@@ -438,3 +447,10 @@ body > footer { ...@@ -438,3 +447,10 @@ body > footer {
font-size: 1.1em; 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 @@ ...@@ -67,10 +67,37 @@
list-style-type: none; list-style-type: none;
} }
.entry { .entry {
&.editing {
.summary {
&:hover .name > span {
width: 100%;
max-width: 100%;
}
.name > span {
width: 100%;
max-width: 100%;
}
}
}
&.opened { &.opened {
&.editing {
.summary {
.name > span {
width: 100%;
max-width: 100%;
}
}
}
.actions { .actions {
display: block !important; 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 { .summary .name .details {
display: inline; display: inline;
border: none; border: none;
...@@ -81,11 +108,15 @@ ...@@ -81,11 +108,15 @@
cursor: default; cursor: default;
&:hover { &:hover {
background-color: #c1c1c1; background-color: #c1c1c1;
.name {
width: 90%;
}
} }
.name { .name {
background: none !important; background: none !important;
text-align: center; text-align: center;
float: none; float: none;
width: 90%;
} }
} }
&.small-row .summary{ &.small-row .summary{
...@@ -168,11 +199,18 @@ ...@@ -168,11 +199,18 @@
.actions { .actions {
display: block; display: block;
} }
.name .details { .name {
display: inline; .vendorVal(width, calc, "100% - 200px");
span {
.vendorVal(width, calc, "100% - 110px");
.vendorVal(max-width, calc, "100% - 95px");
}
.details {
display: block;
border: none; border: none;
} }
} }
}
.id { .id {
float: right; float: right;
...@@ -186,11 +224,20 @@ ...@@ -186,11 +224,20 @@
z-index: 2; z-index: 2;
position: relative; position: relative;
height: 24px; height: 24px;
.vendorVal(width, calc, "100% - 90px");
span {
float: left;
text-overflow: ellipsis;
display: block;
white-space: nowrap;
overflow: hidden;
}
&.filetype-new-folder { &.filetype-new-folder {
float: left; float: left;
} }
.details { .details {
display: none; display: none;
float: right;
} }
} }
.status { .status {
...@@ -321,7 +368,7 @@ ...@@ -321,7 +368,7 @@
font-size: .8em; font-size: .8em;
text-align: right; text-align: right;
} }
span { .entry span {
background-position: left center; background-position: left center;
background-repeat: no-repeat; background-repeat: no-repeat;
padding-left: 18px; padding-left: 18px;
...@@ -513,6 +560,10 @@ ...@@ -513,6 +560,10 @@
.content { .content {
padding: 15px; padding: 15px;
} }
.faded {
color: #666;
font-size: 0.8em;
}
} }
table { table {
...@@ -537,7 +588,17 @@ table { ...@@ -537,7 +588,17 @@ table {
.summary { .summary {
.name { .name {
background-image: url(/static/icons/users.png); 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 { #new-group {
......
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
<form action="{% url one.views.vm_unshare i.id %}" method="post"> <form action="{% url one.views.vm_unshare i.id %}" method="post">
<span title="{{i.name}}">{{i.name|truncatechars:20}}</span> <span title="{{i.name}}">{{i.name|truncatechars:20}}</span>
({{i.get_running}}/{{i.instance_limit}}) {{i.type}} ({{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" %}" /> <img src="{% static "icons/pencil.png" %}" alt="{% trans "Edit" %}" title="{% trans "Edit" %}" />
</a> </a>
{% csrf_token %} {% csrf_token %}
......
...@@ -84,9 +84,9 @@ ...@@ -84,9 +84,9 @@
{% trans "Size" %}: {% trans "Size" %}:
<span class="value"> <span class="value">
{{s.template.instance_type.name}} {{s.template.instance_type.name}}
<span class="cpu">{{s.template.instance_type.CPU}}</span> <span title="{% trans 'CPU cores' %}" class="cpu">{{s.template.instance_type.CPU}}</span>
<span class="memory">{{s.template.instance_type.RAM}}</span> <span title="{% trans 'RAM' %}" class="memory">{{s.template.instance_type.RAM}}</span>
<span class="credit">{{s.template.instance_type.credit}}</span> <span title="{% trans 'Credit' %}" class="credit">{{s.template.instance_type.credit}}</span>
</span> </span>
</li> </li>
<li class="share-type"> <li class="share-type">
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% block content %} {% 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 }} {{block.super }}
</li> </li>
{% endblock content %} {% endblock content %}
...@@ -22,17 +22,27 @@ ...@@ -22,17 +22,27 @@
</li> </li>
<li class="os-{{vm.template.os_type}}"> <li class="os-{{vm.template.os_type}}">
{% trans "System" %}: {% trans "System" %}:
<span class="value">{{vm.template.system}}</span> <span class="value">{{vm.template.system|default:"n/a"}}</span>
<div class="clear"></div> <div class="clear"></div>
</li> </li>
<li class="template"> <li class="template">
{% trans "Type" %}: {% 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> <div class="clear"></div>
</li> </li>
<li class="template"> <li class="template">
{% trans "Share" %}: {% trans "Share" %}:
<span class="value">{{vm.share.name}}</span> <span class="value">{{vm.share.name|default:"n/a"}}</span>
<div class="clear"></div> <div class="clear"></div>
</li> </li>
<li class="template"> <li class="template">
...@@ -57,9 +67,9 @@ ...@@ -57,9 +67,9 @@
<li class="date"> <li class="date">
{% trans "time of suspend"|capfirst %}: {% trans "time of suspend"|capfirst %}:
<span class="value"> <abbr title="{{vm.time_of_suspend}}">{{vm.time_of_suspend|timeuntil}}</abbr> <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" %}" /> <img src="{% static "icons/control-double.png" %}" alt="{% trans "Renew suspend time" %}" />
</a>{% endif %} </a>
</span> </span>
</li> </li>
{% endif %} {% endif %}
...@@ -67,9 +77,9 @@ ...@@ -67,9 +77,9 @@
<li class="date"> <li class="date">
{% trans "time of delete"|capfirst %}: {% trans "time of delete"|capfirst %}:
<span class="value"> <abbr title="{{vm.time_of_delete}}">{{vm.time_of_delete|timeuntil}}</abbr> <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" %}" /> <img src="{% static "icons/control-double.png" %}" alt="{% trans "Renew deletion time" %}" />
</a>{% endif %} </a>
</span> </span>
</li> </li>
{% endif %} {% endif %}
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
{% block name %} {% block name %}
<div class="name {% if vm.state == 'ACTIVE' %}vm-on{% else %}vm-off{% endif %}"> <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"> <small id="vm-{{vm.id}}-name-details" class="details">
(<a href="{{ vm.get_absolute_url }}" title="{{vm.name}}">{% trans "More details" %}</a>) (<a href="{{ vm.get_absolute_url }}" title="{{vm.name}}">{% trans "More details" %}</a>)
</small> </small>
......
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
'type': 'GET', 'type': 'GET',
'url': '{% url one.views.ajax_template_name_unique %}?name=' + $("#new-template-name").val(), 'url': '{% url one.views.ajax_template_name_unique %}?name=' + $("#new-template-name").val(),
'success': function(data, b, c) { 'success': function(data, b, c) {
if (data == "True" || s == original) { if (data == "True" || $("#new-template-name").val() == original) {
$("#template-wizard").unbind('submit').submit() $("#template-wizard").unbind('submit').submit()
} }
else { else {
......
...@@ -37,11 +37,11 @@ ...@@ -37,11 +37,11 @@
{% for s in sizes %} {% for s in sizes %}
<p id="new-template-size-summary-{{s.id}}" class="size-summary clear" <p id="new-template-size-summary-{{s.id}}" class="size-summary clear"
{% if s != base.instance_type %}style="display:none"{% endif %}> {% 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 %} {% blocktrans count n=s.CPU %}{{n}} core{% plural %}{{n}} cores{% endblocktrans %}
</span> </span>
<span class="memory">{{s.RAM}} MiB</span> <span title="{% trans 'RAM' %}" class="memory">{{s.RAM}} MiB</span>
<span class="credit">{{s.credit}}</span> <span title="{% trans 'Credit' %}" class="credit">{{s.credit}}</span>
</p> </p>
{% endfor %} {% endfor %}
<div class="clear"></div> <div class="clear"></div>
......
...@@ -37,6 +37,10 @@ ...@@ -37,6 +37,10 @@
</script> </script>
{% endblock %} {% endblock %}
{% block title %}
{% blocktrans with name=i.name %}Details of {{name}}{% endblocktrans %}
- {{block.super}}
{% endblock %}
{% block content %} {% block content %}
{% if i.template.state != "READY" %} {% if i.template.state != "READY" %}
...@@ -141,36 +145,63 @@ ...@@ -141,36 +145,63 @@
{% csrf_token %} {% csrf_token %}
<table> <table>
<tr> <tr>
<th>{% trans "Protocol" %}</th> <th>{% trans "Port" %}</th>
<th>{% trans "Public port" %}</th> <th colspan="2">{% trans "Access" %}</th>
{% if i.template.network.nat %}<th colspan="2">{% trans "Private port" %}</th>{%endif%}
</tr> </tr>
{% for port in ports %} {% for port in ports %}
<tr> <tr{% if i.is_ipv6 %} class="faded"{% endif %}>
<td>{{port.proto}}</td> <td>{{port.private}}/{{port.proto}}</td>
<td>{{port.public}}</td> <td style="white-space: nowrap;">
{% if i.template.network.nat %}<td>{{port.private}}</td>{%endif%} {% 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> <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> </td>
{% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
<tr> <tr>
<td> <th colspan="3">{% trans "Port/Protocol" %}</th>
<select style="min-width:50px;" name=proto> </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="tcp">tcp</option>
<option value="udp">udp</option> <option value="udp">udp</option>
</select> </select>
</td> </td>
<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" %}" /> <input type="submit" style="min-width:3em" value="{% trans "Add" %}" />
</td> </td>
</tr> </tr>
......
...@@ -36,13 +36,13 @@ ...@@ -36,13 +36,13 @@
{{i.template.access_type|upper}} {{i.template.access_type|upper}}
</td> </td>
</tr> </tr>
<tr> <tr{% if i.is_ipv6 %} class="faded"{% endif %}>
<th>{% trans "IP" %}:</th> <th>{% trans "IPv4 Host" %}{% if i.is_ipv6 %} ({% trans "You are using IPv6" %}){% endif %}:</th>
<td>{{ i.hostname }}</td> <td>{{ i.hostname_v4 }}<strong>:{{ i.port_v4 }}</strong></td>
</tr> </tr>
<tr> <tr{% if not i.is_ipv6 %} style="display: none"{% endif %}>
<th>{% trans "Port" %}:</th> <th>{% trans "IPv6 Host" %}{% if not i.is_ipv6 %} ({% trans "You are using IPv4" %}){% endif %}:</th>
<td>{{ i.port}}</td> <td>{{ i.hostname_v6 }}<strong>:{{ i.port_v6 }}</strong></td>
</tr> </tr>
<tr> <tr>
<th>{% trans "Username" %}:</th> <th>{% trans "Username" %}:</th>
......
...@@ -16,7 +16,7 @@ from django.template import RequestContext ...@@ -16,7 +16,7 @@ from django.template import RequestContext
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import get_language as lang 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.decorators.http import *
from django.views.generic import * from django.views.generic import *
from firewall.tasks import * from firewall.tasks import *
...@@ -104,9 +104,12 @@ def ajax_template_name_unique(request): ...@@ -104,9 +104,12 @@ def ajax_template_name_unique(request):
def vm_credentials(request, iid): def vm_credentials(request, iid):
try: try:
vm = get_object_or_404(Instance, pk=iid, owner=request.user) vm = get_object_or_404(Instance, pk=iid, owner=request.user)
proto = len(request.META["REMOTE_ADDR"].split('.')) == 1 is_ipv6 = len(request.META["REMOTE_ADDR"].split('.')) == 1
vm.hostname = vm.get_connect_host(use_ipv6=proto) vm.hostname_v4 = vm.get_connect_host(use_ipv6=False)
vm.port = vm.get_port(use_ipv6=proto) 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 })) return render_to_response('vm-credentials.html', RequestContext(request, { 'i' : vm }))
except: except:
return HttpResponse(_("Could not get Virtual Machine credentials."), status=404) return HttpResponse(_("Could not get Virtual Machine credentials."), status=404)
...@@ -278,7 +281,7 @@ def _check_quota(request, template, share): ...@@ -278,7 +281,7 @@ def _check_quota(request, template, share):
Returns if the given request is permitted to run the new vm. Returns if the given request is permitted to run the new vm.
""" """
det = UserCloudDetails.objects.get(user=request.user) 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.')) messages.error(request, _('You do not have any free quota. You can not launch this until you stop an other instance.'))
return False return False
if share: if share:
...@@ -364,9 +367,12 @@ def vm_show(request, iid): ...@@ -364,9 +367,12 @@ def vm_show(request, iid):
except UserCloudDetails.DoesNotExist: except UserCloudDetails.DoesNotExist:
details = UserCloudDetails(user=request.user) details = UserCloudDetails(user=request.user)
details.save() details.save()
proto = len(request.META["REMOTE_ADDR"].split('.')) == 1 is_ipv6 = len(request.META["REMOTE_ADDR"].split('.')) == 1
inst.hostname = inst.get_connect_host(use_ipv6=proto) inst.is_ipv6 = is_ipv6
inst.port = inst.get_port(use_ipv6=proto) 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,{ return render_to_response("show.html", RequestContext(request,{
'uri': inst.get_connect_uri(), 'uri': inst.get_connect_uri(),
'state': inst.state, 'state': inst.state,
...@@ -415,20 +421,19 @@ def boot_token(request, token): ...@@ -415,20 +421,19 @@ def boot_token(request, token):
class VmPortAddView(View): class VmPortAddView(View):
def post(self, request, iid, *args, **kwargs): def post(self, request, iid, *args, **kwargs):
try: try:
public = int(request.POST['public']) port = int(request.POST['port'])
if public >= 22000 and public < 24000:
raise ValidationError(_("Port number is in a restricted domain (22000 to 24000)."))
inst = get_object_or_404(Instance, id=iid, owner=request.user) inst = get_object_or_404(Instance, id=iid, owner=request.user)
if inst.template.network.nat: if inst.nat:
private = private=int(request.POST['private']) inst.firewall_host.add_port(proto=request.POST['proto'],
public=None, private=port)
else: else:
private = 0 inst.firewall_host.add_port(proto=request.POST['proto'],
inst.firewall_host.add_port(proto=request.POST['proto'], public=public, private=private) public=port, private=None)
messages.success(request, _(u"Port %d successfully added.") % public)
except: except:
messages.error(request, _(u"Adding port failed.")) 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)) return redirect('/vm/show/%d/' % int(iid))
def get(self, request, iid, *args, **kwargs): def get(self, request, iid, *args, **kwargs):
...@@ -439,11 +444,12 @@ vm_port_add = login_required(VmPortAddView.as_view()) ...@@ -439,11 +444,12 @@ vm_port_add = login_required(VmPortAddView.as_view())
@require_safe @require_safe
@login_required @login_required
@require_GET @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) inst = get_object_or_404(Instance, id=iid, owner=request.user)
try: try:
inst.firewall_host.del_port(proto=proto, public=public) inst.firewall_host.del_port(proto=proto, private=private)
messages.success(request, _(u"Port %s successfully removed.") % public) messages.success(request, _(u"Port %s successfully removed.") %
private)
except: except:
messages.error(request, _(u"Removing port failed.")) messages.error(request, _(u"Removing port failed."))
return redirect('/vm/show/%d/' % int(iid)) return redirect('/vm/show/%d/' % int(iid))
...@@ -478,12 +484,21 @@ def vm_unshare(request, id, *args, **kwargs): ...@@ -478,12 +484,21 @@ def vm_unshare(request, id, *args, **kwargs):
if not g.owners.filter(user=request.user).exists(): if not g.owners.filter(user=request.user).exists():
raise PermissionDenied() raise PermissionDenied()
try: try:
if s.get_running_or_stopped() > 0: n = s.get_running()
messages.error(request, _('There are machines running of this share.')) 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: else:
s.delete() s.delete()
messages.success(request, _('Share is successfully removed.')) messages.success(request, _('Share is successfully removed.'))
except: except Exception as e:
print e
messages.error(request, _('Failed to remove share.')) messages.error(request, _('Failed to remove share.'))
return redirect(g) return redirect(g)
...@@ -516,11 +531,15 @@ def vm_resume(request, iid, *args, **kwargs): ...@@ -516,11 +531,15 @@ def vm_resume(request, iid, *args, **kwargs):
@require_POST @require_POST
def vm_renew(request, which, iid, *args, **kwargs): def vm_renew(request, which, iid, *args, **kwargs):
try: 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.')) messages.success(request, _('Virtual machine is successfully renewed.'))
except: except:
messages.error(request, _('Failed to renew virtual machine.')) messages.error(request, _('Failed to renew virtual machine.'))
return redirect('/') return redirect('/')
return render_to_response('box/vm/entry.html', RequestContext(request, {
'vm': vm
}))
@login_required @login_required
@require_POST @require_POST
......
...@@ -6,8 +6,8 @@ msgid "" ...@@ -6,8 +6,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-22 10:43+0100\n" "POT-Creation-Date: 2013-04-04 18:33+0200\n"
"PO-Revision-Date: 2013-03-07 17:48+0100\n" "PO-Revision-Date: 2013-04-04 18:03+0200\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: American English <cloud@ik.bme.hu>\n" "Language-Team: American English <cloud@ik.bme.hu>\n"
"Language: en_US\n" "Language: en_US\n"
...@@ -155,39 +155,44 @@ msgstr "Érvénytelen Neptun-kód." ...@@ -155,39 +155,44 @@ msgstr "Érvénytelen Neptun-kód."
msgid "Invalid NEPTUN code" msgid "Invalid NEPTUN code"
msgstr "Érvénytelen Neptun-kód" 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" msgid "Owners of"
msgstr "Tulajdonosok" 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" msgid "Remove"
msgstr "Eltávolítás" 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" msgid "This user never logged in, no data available"
msgstr "Ez a felhasználó még nem lépett be, nincs adat róla" 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" msgid "Name"
msgstr "Név" 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" msgid "NEPTUN"
msgstr "NEPTUN" msgstr "NEPTUN"
#: templates/show-group.html:51 #: templates/show-group.html:57
msgid "Add owner" msgid "Add owner"
msgstr "Tulajdonos hozzáadása" msgstr "Tulajdonos hozzáadása"
#: templates/show-group.html:56 #: templates/show-group.html:62
msgid "Owner name/NEPTUN" msgid "Owner name/NEPTUN"
msgstr "Tulajdonos neve/NEPTUN-kódja" msgstr "Tulajdonos neve/NEPTUN-kódja"
#: templates/show-group.html:69 #: templates/show-group.html:75
msgid "This group has no shared templates." msgid "This group has no shared templates."
msgstr "Ennek a csoportnak egy sablon sincs megosztva." 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." msgid "Share one, and the group members can start their own virtual machine."
msgstr "" msgstr ""
"Osszon meg egyet, hogy a csoport tagjai is elindíthassák egy példányát." "Osszon meg egyet, hogy a csoport tagjai is elindíthassák egy példányát."
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
{% block details %} {% block details %}
<ul> <ul>
<li> <li class="course">
{% trans "Course" %}: {% trans "Course" %}:
<span class="value"> <span class="value">
{% if group.course %} {% if group.course %}
...@@ -39,15 +39,15 @@ ...@@ -39,15 +39,15 @@
{% endif %} {% endif %}
</span> </span>
</li> </li>
<li> <li class="date">
{% trans "Semester" %}: {% trans "Semester" %}:
<span class="value">{{group.semester.name}}</span> <span class="value">{{group.semester.name}}</span>
</li> </li>
<li> <li class="owner">
{% trans "Owner(s)" %}: {% trans "Owner(s)" %}:
<span class="value">{{group.owner_list}}</span> <span class="value">{{group.owner_list}}</span>
</li> </li>
<li> <li class="members">
{% trans "Member count" %}: {% trans "Member count" %}:
<span class="value">{{group.member_count}}</span> <span class="value">{{group.member_count}}</span>
</li> </li>
......
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
{% load i18n %} {% load i18n %}
{% load staticfiles %} {% load staticfiles %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% block title %}
{% blocktrans with name=group.name %}Details of {{name}}{% endblocktrans %}
- {{block.super}}
{% endblock %}
{% block content %} {% block content %}
<div class="boxes"> <div class="boxes">
{% include "box/person/box.html" %} {% include "box/person/box.html" %}
......
from django.test import TestCase 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: class MockUser:
username = "testuser" username = "testuser"
...@@ -50,3 +51,24 @@ class PersonTestCase(TestCase): ...@@ -50,3 +51,24 @@ class PersonTestCase(TestCase):
def test_unicode(self): def test_unicode(self):
# TODO # TODO
self.testperson.__unicode__() 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): ...@@ -198,7 +198,7 @@ def gui(request):
else: else:
return HttpResponse('Can not update authorization information!') return HttpResponse('Can not update authorization information!')
if StoreApi.updateauthorizationinfo(user, password, key_list): 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: else:
return HttpResponse('Can not update authorization information!') return HttpResponse('Can not update authorization information!')
else: 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