Commit 6efe1ef5 by Őry Máté

one: add save as functionality

parent e9fe67c6
...@@ -26,12 +26,14 @@ urlpatterns = patterns('', ...@@ -26,12 +26,14 @@ urlpatterns = patterns('',
url(r'^vm/restart/(?P<iid>\d+)/$', 'one.views.vm_restart', name='vm_restart'), url(r'^vm/restart/(?P<iid>\d+)/$', 'one.views.vm_restart', name='vm_restart'),
url(r'^vm/port_add/(?P<iid>\d+)/$', 'one.views.vm_port_add', name='vm_port_add'), url(r'^vm/port_add/(?P<iid>\d+)/$', 'one.views.vm_port_add', name='vm_port_add'),
url(r'^vm/port_del/(?P<iid>\d+)/(?P<proto>tcp|udp)/(?P<public>\d+)/$', 'one.views.vm_port_del', name='vm_port_del'), url(r'^vm/port_del/(?P<iid>\d+)/(?P<proto>tcp|udp)/(?P<public>\d+)/$', 'one.views.vm_port_del', name='vm_port_del'),
url(r'^vm/saveas/(?P<vmid>\d+)$', 'one.views.vm_saveas', name='vm_saveas'),
url(r'^reload/$', 'firewall.views.reload_firewall', name='reload_firewall'), url(r'^reload/$', 'firewall.views.reload_firewall', name='reload_firewall'),
url(r'^fwapi/$', 'firewall.views.firewall_api', name='firewall_api'), url(r'^fwapi/$', 'firewall.views.firewall_api', name='firewall_api'),
url(r'^store/$', 'store.views.index', name='store_index'), url(r'^store/$', 'store.views.index', name='store_index'),
url(r'^store/gui/$', 'store.views.gui', name='store_gui'), url(r'^store/gui/$', 'store.views.gui', name='store_gui'),
url(r'^store/top/$', 'store.views.toplist', name='store_top'), url(r'^store/top/$', 'store.views.toplist', name='store_top'),
url(r'^ajax/templateWizard$', 'one.views.ajax_template_wizard', name='ajax_template_wizard'), url(r'^ajax/templateWizard$', 'one.views.ajax_template_wizard', name='ajax_template_wizard'),
url(r'^ajax/template_name_unique/(?P<name>.*)$', 'one.views.ajax_template_name_unique', name='ajax_template_name_unique'),
url(r'^ajax/store/list$', 'store.views.ajax_listfolder', name='store_ajax_listfolder'), url(r'^ajax/store/list$', 'store.views.ajax_listfolder', name='store_ajax_listfolder'),
url(r'^ajax/store/download$', 'store.views.ajax_download', name='store_ajax_download'), url(r'^ajax/store/download$', 'store.views.ajax_download', name='store_ajax_download'),
url(r'^ajax/store/upload$', 'store.views.ajax_upload', name='store_ajax_upload'), url(r'^ajax/store/upload$', 'store.views.ajax_upload', name='store_ajax_upload'),
......
...@@ -47,6 +47,7 @@ submit_vm.short_description = _('Submit VM') ...@@ -47,6 +47,7 @@ submit_vm.short_description = _('Submit VM')
class TemplateAdmin(contrib.admin.ModelAdmin): class TemplateAdmin(contrib.admin.ModelAdmin):
model=models.Template model=models.Template
list_display = ('name', 'state', 'owner', 'system')
class InstanceAdmin(contrib.admin.ModelAdmin): class InstanceAdmin(contrib.admin.ModelAdmin):
model=models.Instance model=models.Instance
......
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'InstanceType.credit'
db.add_column('one_instancetype', 'credit',
self.gf('django.db.models.fields.IntegerField')(default=1),
keep_default=False)
def backwards(self, orm):
# Deleting field 'InstanceType.credit'
db.delete_column('one_instancetype', 'credit')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'firewall.domain': {
'Meta': {'object_name': 'Domain'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'ttl': ('django.db.models.fields.IntegerField', [], {'default': '600'})
},
'firewall.group': {
'Meta': {'object_name': 'Group'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
'firewall.host': {
'Meta': {'object_name': 'Host'},
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['firewall.Group']", 'null': 'True', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ipv4': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
'ipv6': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'location': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'mac': ('firewall.fields.MACAddressField', [], {'unique': 'True', 'max_length': '17'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'pub_ipv4': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
'reverse': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
'shared_ip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['firewall.Vlan']"})
},
'firewall.vlan': {
'Meta': {'object_name': 'Vlan'},
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'dhcp_pool': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['firewall.Domain']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'interface': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
'ipv4': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
'ipv6': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
'net4': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
'net6': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'prefix4': ('django.db.models.fields.IntegerField', [], {'default': '16'}),
'prefix6': ('django.db.models.fields.IntegerField', [], {'default': '80'}),
'reverse_domain': ('django.db.models.fields.TextField', [], {}),
'snat_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
'snat_to': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['firewall.Vlan']", 'null': 'True', 'blank': 'True'}),
'vid': ('django.db.models.fields.IntegerField', [], {'unique': 'True'})
},
'one.disk': {
'Meta': {'ordering': "['name']", 'object_name': 'Disk'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
},
'one.instance': {
'Meta': {'object_name': 'Instance'},
'active_since': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'firewall_host': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['firewall.Host']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
'one_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'pw': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'share': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Share']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.CharField', [], {'default': "'DEPLOYABLE'", 'max_length': '20'}),
'template': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Template']"}),
'time_of_delete': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'time_of_suspend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'})
},
'one.instancetype': {
'CPU': ('django.db.models.fields.IntegerField', [], {}),
'Meta': {'object_name': 'InstanceType'},
'RAM': ('django.db.models.fields.IntegerField', [], {}),
'credit': ('django.db.models.fields.IntegerField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
},
'one.network': {
'Meta': {'ordering': "['name']", 'object_name': 'Network'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'nat': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'one.share': {
'Meta': {'object_name': 'Share'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['school.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance_limit': ('django.db.models.fields.IntegerField', [], {}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'per_user_limit': ('django.db.models.fields.IntegerField', [], {}),
'template': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Template']"}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
},
'one.sshkey': {
'Meta': {'object_name': 'SshKey'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'key': ('django.db.models.fields.CharField', [], {'max_length': '2000'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'one.template': {
'Meta': {'object_name': 'Template'},
'access_type': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'disk': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Disk']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.InstanceType']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'network': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Network']"}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'state': ('django.db.models.fields.CharField', [], {'default': "'NEW'", 'max_length': '10'}),
'system': ('django.db.models.fields.TextField', [], {'blank': 'True'})
},
'one.userclouddetails': {
'Meta': {'object_name': 'UserCloudDetails'},
'disk_quota': ('django.db.models.fields.IntegerField', [], {'default': '2048'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance_quota': ('django.db.models.fields.IntegerField', [], {'default': '20'}),
'share_quota': ('django.db.models.fields.IntegerField', [], {'default': '100'}),
'smb_password': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'ssh_key': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.SshKey']", 'null': 'True'}),
'ssh_private_key': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'school.course': {
'Meta': {'object_name': 'Course'},
'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_group_of'", 'null': 'True', 'to': "orm['school.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'null': 'True', 'blank': 'True'}),
'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['school.Person']", 'null': 'True', 'blank': 'True'}),
'short_name': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
},
'school.group': {
'Meta': {'unique_together': "(('name', 'course', 'semester'),)", 'object_name': 'Group'},
'course': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['school.Course']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'course_groups'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['school.Person']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'owners': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'owned_groups'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['school.Person']"}),
'semester': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['school.Semester']"})
},
'school.person': {
'Meta': {'object_name': 'Person'},
'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'hu'", 'max_length': '10'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
},
'school.semester': {
'Meta': {'object_name': 'Semester'},
'end': ('django.db.models.fields.DateField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
'start': ('django.db.models.fields.DateField', [], {})
}
}
complete_apps = ['one']
\ No newline at end of file
...@@ -122,10 +122,11 @@ class SshKey(models.Model): ...@@ -122,10 +122,11 @@ class SshKey(models.Model):
TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')), ("SAVE", _('saving')), ("READY", _('ready'))) TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')), ("SAVE", _('saving')), ("READY", _('ready')))
TYPES = {"LAB": {"verbose_name": _('lab'), "suspend": td(hours=5), "delete": td(days=15)}, TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB", "suspend": td(hours=5), "delete": td(days=15), "help_text": _('For lab or home work with short life time.')},
"PROJECT": {"verbose_name": _('project'), "suspend": td(weeks=5), "delete": td(days=366/2)}, "PROJECT": {"verbose_name": _('project'), "id": "PROJECT", "suspend": td(weeks=5), "delete": td(days=366/2), "help_text": _('For project work.')},
"SERVER": {"verbose_name": _('server'), "suspend": td(days=365), "delete": None}, "SERVER": {"verbose_name": _('server'), "id": "SERVER", "suspend": td(days=365), "delete": None, "help_text": _('For long term server use.')},
} }
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()])
class Share(models.Model): class Share(models.Model):
name = models.CharField(max_length=100, unique=True, verbose_name=_('name')) name = models.CharField(max_length=100, unique=True, verbose_name=_('name'))
...@@ -222,10 +223,14 @@ class InstanceType(models.Model): ...@@ -222,10 +223,14 @@ class InstanceType(models.Model):
verbose_name=_('name')) verbose_name=_('name'))
CPU = models.IntegerField(help_text=_('CPU cores.')) CPU = models.IntegerField(help_text=_('CPU cores.'))
RAM = models.IntegerField(help_text=_('Mebibytes of memory.')) RAM = models.IntegerField(help_text=_('Mebibytes of memory.'))
credit = models.IntegerField(verbose_name=_('credits'),
help_text=_('Price of instance.'))
def __unicode__(self): def __unicode__(self):
return u"%s" % self.name return u"%s" % self.name
class Meta:
ordering = ['credit']
TEMPLATE_STATES = (('NEW', _('new')), ('PREPARING', _('preparing')), TEMPLATE_STATES = (('NEW', _('new')),
('SAVING', _('saving')), ('READY', _('ready')), ) ('SAVING', _('saving')), ('READY', _('ready')), )
""" """
Virtual machine template specifying OS, disk, type and network. Virtual machine template specifying OS, disk, type and network.
...@@ -455,6 +460,7 @@ class Instance(models.Model): ...@@ -455,6 +460,7 @@ class Instance(models.Model):
proc = subprocess.Popen(["/opt/occi.sh", "compute", proc = subprocess.Popen(["/opt/occi.sh", "compute",
"delete", "%d"%self.one_id], stdout=subprocess.PIPE) "delete", "%d"%self.one_id], stdout=subprocess.PIPE)
(out, err) = proc.communicate() (out, err) = proc.communicate()
if self.firewall_host:
self.firewall_host.delete() self.firewall_host.delete()
reload_firewall_lock() reload_firewall_lock()
...@@ -499,7 +505,10 @@ class Instance(models.Model): ...@@ -499,7 +505,10 @@ class Instance(models.Model):
imgname = "template-%d-%d" % (self.template.id, self.id) imgname = "template-%d-%d" % (self.template.id, self.id)
self._update_vm('<DISK id="0"><SAVE_AS name="%s"/></DISK>' % imgname) self._update_vm('<DISK id="0"><SAVE_AS name="%s"/></DISK>' % imgname)
self._change_state("SHUTDOWN") self._change_state("SHUTDOWN")
def is_save_as_done(self): t = self.template
t.state = 'SAVING'
t.save()
def check_if_is_save_as_done(self):
self.update_state() self.update_state()
if self.state != 'DONE': if self.state != 'DONE':
return false return false
...@@ -509,9 +518,9 @@ class Instance(models.Model): ...@@ -509,9 +518,9 @@ class Instance(models.Model):
if len(disks) != 1: if len(disks) != 1:
return false return false
self.template.disk_id = disks[0].id self.template.disk_id = disks[0].id
self.template.status = 'READY' self.template.state = 'READY'
self.template.save() self.template.save()
self.host.delete() self.firewall_host.delete()
return True return True
...@@ -523,7 +532,7 @@ def delete_instance(sender, instance, using, **kwargs): ...@@ -523,7 +532,7 @@ def delete_instance(sender, instance, using, **kwargs):
if instance.state != "DONE": if instance.state != "DONE":
instance.one_delete() instance.one_delete()
try: try:
instance.host.delete() instance.firewall_host.delete()
except: except:
pass pass
post_delete.connect(delete_instance, sender=Instance, dispatch_uid="delete_instance") post_delete.connect(delete_instance, sender=Instance, dispatch_uid="delete_instance")
......
...@@ -43,6 +43,21 @@ body ...@@ -43,6 +43,21 @@ body
margin-top:0; margin-top:0;
padding:10px; padding:10px;
} }
&.wide {
margin-right: 30px;
}
&.note {
background-color: #ffc;
}
ol {
margin-left: 2em;
&.done {
text-color: #aaa;
}
}
}
.big {
font-size: 2em;
} }
.wm-list{ .wm-list{
list-style: none; list-style: none;
...@@ -75,6 +90,14 @@ ul.messagelist li.error ...@@ -75,6 +90,14 @@ ul.messagelist li.error
{ {
background-image:url(admin/img/icon_error.gif); background-image:url(admin/img/icon_error.gif);
} }
input.validated {
padding-right: 15px;
&.error
{
background:#fcc url(admin/img/icon_error.gif) right center no-repeat;
padding-right: 15px;
}
}
.errornote .errornote
{ {
...@@ -187,3 +210,6 @@ textarea { ...@@ -187,3 +210,6 @@ textarea {
box-shadow: inset 0 0 10px rgba(0,0,0,0.2), 0 0 10px rgba(255,255,0,0.8); box-shadow: inset 0 0 10px rgba(0,0,0,0.2), 0 0 10px rgba(255,255,0,0.8);
} }
} }
.hilight {
background-color: #ff6;
}
...@@ -282,6 +282,27 @@ ...@@ -282,6 +282,27 @@
background-image: url(icons/computer--plus.png); background-image: url(icons/computer--plus.png);
} }
} }
#template-wizard {
.size-summary {
font-size: .8em;
text-align: right;
span {
background-position: left center;
background-repeat: no-repeat;
padding-left: 18px;
}
.cpu{
background-image: url(icons/processor.png)
}
.memory{
background-image: url(icons/memory.png)
}
.credit{
background-image: url(icons/documents-stack.png) /* TODO */
}
}
}
.file-list{ .file-list{
list-style: none; list-style: none;
......
...@@ -73,6 +73,9 @@ ...@@ -73,6 +73,9 @@
<div class="contentblock" id="template"> <div class="contentblock" id="template">
<h2>{% trans "My templates" %}</h2> <h2>{% trans "My templates" %}</h2>
<ul class="wm-list"> <ul class="wm-list">
{% for t in mytemplates %}
<li class="wm">{{t.name}}</li>
{% endfor %}
<li class="wm"> <li class="wm">
<div class="summary"> <div class="summary">
<div class="quota"> <div class="quota">
......
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
<form action="/ajax/templateWizard" method="post" id="template-wizard">{% csrf_token %}
<div id="new-template-step-1" class="wizard">
<div class="progress">
<div class="bar-container">
<div class="bar" style="width: 33%"></div>
</div>
<h3>{% blocktrans with step=1 all=3 %}{{step}}/{{all}}{% endblocktrans %}</h3>
</div>
<h2>{% blocktrans with step=1 %}Step {{step}}{% endblocktrans %}</h2>
<p class="help">{% trans "Please choose the base system you want to customize." %}</p>
<div class="container">
<ul class="tpl-list modal">
{% if not templates %}
<p>{% trans "There are no available templates." %}</p>
{% endif %}
{% for m in templates %}
<li class="tpl">
<div class="summary">
<div class="name tpl os-{{m.os_type}}" title="{{m.description}}"><label><input type="radio" name="base" value="{{m.id}}" /> {{m.name}}</label></div>
<div class="clear"></div>
</div>
</li>
{% endfor %}
</ul>
</div>
<nav>
<a href="#" class="prev">{% trans "&laquo; Cancel" %}</a>
<input type="submit" class="next" value="{% trans "Next &raquo;" %}" />
<div class="clear"></div>
</nav>
<script type="text/javascript">
$(function(){
$('#template-wizard nav .prev').click(function(){
$('#modal').hide();
})
$('#template-wizard').unbind('submit').submit(function(e){
if ($("#template-wizard input[type=radio]:checked").length==0) {
$("#template-wizard .help").addClass('hilight');
}
else {
$.ajax({
'type': 'POST',
'url': '/ajax/templateWizard',
'data': $('#template-wizard').serialize(),
'success': function(data) {
$('#modal-container').html(data);
}
});
}
e.preventDefault();
e.stopPropagation();
return false;
})
})
</script>
</div>
</div>
</form>
{% load i18n %} {% load i18n %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<form action="/" method="post" id="template-wizard"> <form action="/vm/new/{{base.id}}/" method="post" id="template-wizard">{% csrf_token %}
<div id="new-template-step-1" class="wizard"> <div id="new-template-step-2" class="wizard">
<div class="progress">
<div class="bar-container">
<div class="bar" style="width: 33%"></div>
</div>
<h3>{% blocktrans with step=1 all=3 %}{{step}}/{{all}}{% endblocktrans %}</h3>
</div>
<h2>{% blocktrans with step=1 %}Step {{step}}{% endblocktrans %}</h2>
<p>Leírás, mit is kéne itt ezen az ablakon csinálni, és miért jó, ha azt csinálja, amit.</p>
<div class="container">
<ul class="wm-list modal">
{% for m in templates %}
<li class="wm">
<form method="POST" action="/vm/new/{{m.pk}}/">{% csrf_token %}
<div class="summary">
<div class="name wm-on">{{m.name}}</div>
<div class="status">
<input type="submit" value="Indítás"/>
</div>
<div class="clear"></div>
</div>
<div class="details">
<h3>
{% trans "Details" %}
</h3>
<ul>
<li class="name">{% trans "System" %}: <span class="value">{{m.disk.name}}</span></li>
<li class="type">{% trans "Size" %}: <span class="value">{{m.instance_type.name}}</span></li>
<li class="memory">{% trans "Memory" %}: <span class="value">{{m.instance_type.RAM}} MiB</span></li>
<li class="cpu">{% trans "CPU cores" %}: <span class="value">{{m.instance_type.CPU}}</span></li>
</ul>
</div>
</form>
</li>
{% endfor %}
</ul>
</div>
<nav>
<a href="#" class="prev">{% trans "&laquo; Cancel" %}</a>
<a href="#" class="next">{% trans "Next &raquo;" %}</a>
<div class="clear"></div>
</nav>
<script type="text/javascript">
$(function(){
console.log('foo');
$('#modal .wm .summary').each(function(){
$(this).next('.details').show();
console.log($(this).next('.details').css('display'), $(this).next('.details').css('height'));
//this.originalHeight=parseInt($(this).next('.details').css('height'));
})
$('#modal .wm .summary').click(function(){
if($(this).next('.details').is(':hidden')){
$(this).next('.details')
.slideDown(700);
$(this).parent('.wm').addClass('opened');
} else {
var that=this;
$(this).next('.details')
.removeClass('opened')
.slideUp(700);
}
});
$('#new-template-step-1 nav .prev').click(function(){
$('#new-template-step-1').hide();
$('#new-template-step-1').show();
})
$('#new-template-step-1 nav .next').click(function(){
$('#new-template-step-1').hide();
$('#new-template-step-3').show();
$.ajax({
'type': 'POST',
'url': '/ajax/templateWizard',
'data': $('#template-wizard').serialize()
})
.done(function(){ console.log('ok')});
})
})
</script>
</div>
<div id="new-template-step-2" class="wizard" style="display: none">
<div class="progress"> <div class="progress">
<div class="bar-container"> <div class="bar-container">
<div class="bar" style="width: 66%"></div> <div class="bar" style="width: 66%"></div>
...@@ -90,69 +9,77 @@ ...@@ -90,69 +9,77 @@
<h3>{% blocktrans with step=2 all=3 %}{{step}}/{{all}}{% endblocktrans %}</h3> <h3>{% blocktrans with step=2 all=3 %}{{step}}/{{all}}{% endblocktrans %}</h3>
</div> </div>
<h2>{% blocktrans with step=2 %}Step {{step}}{% endblocktrans %}</h2> <h2>{% blocktrans with step=2 %}Step {{step}}{% endblocktrans %}</h2>
<p>Leírás, mit is kéne itt ezen az ablakon csinálni, és miért jó, ha azt csinálja, amit.</p> <p>{% trans "Change the parameters as needed." %}</p>
<ul> <ul>
<li> <li>
<label for="new-template-name">{% trans "Name" %}</label> <label for="new-template-name">{% trans "Name" %}</label>
<input type="text" placeholder="{% trans "Short name of template" %}" name="name" id="new-template-name" /> <input type="text" name="name" id="new-template-name" value="{{base.name}}"
<div class="clear"></div> class="error validated" title="{% trans "Please choose a different name." %}" />
</li>
<li>
<label for="new-template-type">{% trans "Type" %}</label>
<ul class="radio">
<li>
<input type="radio" name="type" value="labor" id="new-template-type-labor" />
<label for="new-template-type-labor">{% trans "Lab" %}</label>
</li>
<li>
<input type="radio" name="type" value="project" id="new-template-type-project" />
<label for="new-template-type-project">{% trans "Project" %}</label>
</li>
</ul>
<div class="clear"></div> <div class="clear"></div>
</li> </li>
<li> <li class="new-tpl-size">
<label for="new-template-size">{% trans "Size" %}</label> <label for="new-template-size">{% trans "Size" %}</label>
<ul class="radio"> <ul class="radio">
{% for s in sizes %}
<li> <li>
<input type="radio" name="size" value="small" id="new-template-size-small" /> <label>
<label for="new-template-size-small">Kicsi</label> <input type="radio" name="size" value="{{s.id}}" id="new-template-size-{{s.id}}"
</li> {% if s == base.instance_type %}checked="checked"{% endif %} />
<li> {{s.name}}
<input type="radio" name="size" value="medium" id="new-template-size-medium" /> </label>
<label for="new-template-size-medium">Közepes</label>
</li>
<li>
<input type="radio" name="size" value="large" id="new-template-size-large" />
<label for="new-template-size-large">Nagy</label>
</li> </li>
{% endfor %}
</ul> </ul>
{% 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">{% 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>
</p>
{% endfor %}
<div class="clear"></div> <div class="clear"></div>
</li> </li>
<li style="border: none"> <li style="border: none">
<label for="new-template-description">{% trans "Description" %}</label> <label for="new-template-description">{% trans "Description" %}</label>
<textarea name="description" id="new-template-description" placeholder="{% trans "Comments about the template" %}"></textarea> <textarea name="description" id="new-template-description" style="text-align: left">{{base.description}}</textarea>
<div class="clear"></div> <div class="clear"></div>
</li> </li>
</ul> </ul>
<nav> <nav>
<a href="#" class="prev">{% trans "&laquo; Cancel" %}</a> <a href="#" class="prev">{% trans "&laquo; Cancel" %}</a>
<a href="#" class="next">{% trans "Next &raquo;" %}</a> <input type="submit" value="{% trans "Launch" %}" />
<div class="clear"></div> <div class="clear"></div>
</nav> </nav>
<script type="text/javascript"> <script type="text/javascript">
$(function(){ $(function(){
$('#new-template-step-1 nav .next').click(function(){ $('#new-template-step-2 nav .prev').click(function(){
$('#new-template-step-1').hide();
$('#new-template-step-2').show();
})
$('#new-template-step-1 nav .prev').click(function(){
$('#modal').hide(); $('#modal').hide();
}) })
$(".new-tpl-size input[name='size']").click(function(e){
var v = $(".new-tpl-size input[name='size']:checked").val();
$("p.size-summary").hide();
$("#new-template-size-summary-" + v).show();
});
$("#new-template-name").change(function(e){
var s = $(this).val();
$.ajax({
'type': 'GET',
'url': '/ajax/template_name_unique/' + s,
'success': function(data, b, c) {
if (s != $("#new-template-name").val()) {
return True;
}
if (data == "True") {
$('#new-template-name').removeClass("error");
}
else {
$('#new-template-name').addClass("error");
}
}
});
});
}) })
</script> </script>
</div> </div>
<div id="new-template-step-3" class="wizard" style="display: none">
</div>
</form> </form>
...@@ -21,6 +21,29 @@ ...@@ -21,6 +21,29 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if i.template.state != "READY" %}
<div class="contentblock wide note big">
<p>{% blocktrans %}This is a master image for your new template.{% endblocktrans %}</p>
<form action="{% url one.views.vm_saveas id %}" method="POST">{% csrf_token %}
{% if i.template.state == "NEW" %}
<p style="float: right; margin-top:2em;margin-right:1em;">
<input type="submit" value="{% trans "Save" %}" class="big" style="background-color:rgba(102, 255, 0, 0.4)" />
</p>
{% endif %}
</form>
<ol>
<li{% if i.template.state == "SAVING" %} class="done"{%endif%}>{% blocktrans %}Connect to the machine.{% endblocktrans %}</li>
<li{% if i.template.state == "SAVING" %} class="done"{%endif%}>{% blocktrans %}Do all the needed installation/customization.{% endblocktrans %}</li>
<li{% if i.template.state == "SAVING" %} class="done"{%endif%}>{% blocktrans %}Log off (keep the machine running).{% endblocktrans %}</li>
<li{% if i.template.state == "SAVING" %} class="done"{%endif%}>{% blocktrans %}Click on the "save" button on the right.{% endblocktrans %}</li>
<li>{% blocktrans %}The machine will be shut down and its disk saved.{% endblocktrans %}</li>
<li>{% blocktrans %}You can share the template with your groups.{% endblocktrans %}</li>
</ol>
</div>
{% endif %}
<div class="boxes"> <div class="boxes">
<div class="contentblock" id="state"> <div class="contentblock" id="state">
<h2>{{name}}</h2> <h2>{{name}}</h2>
......
...@@ -10,7 +10,6 @@ from django.core.mail import mail_managers, send_mail ...@@ -10,7 +10,6 @@ from django.core.mail import mail_managers, send_mail
from django.db import transaction from django.db import transaction
from django.forms import ModelForm, Textarea from django.forms import ModelForm, Textarea
from django.http import Http404 from django.http import Http404
#from django_shibboleth.forms import BaseRegisterForm
from django.shortcuts import render, render_to_response, get_object_or_404, redirect from django.shortcuts import render, render_to_response, get_object_or_404, redirect
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import render_to_string from django.template.loader import render_to_string
...@@ -19,11 +18,14 @@ from django.utils.translation import get_language as lang ...@@ -19,11 +18,14 @@ 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 _
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 one.models import * from one.models import *
from school.models import * from school.models import *
import django.contrib.auth as auth import django.contrib.auth as auth
from firewall.tasks import *
import json import json
import logging
logger = logging.getLogger(__name__)
class LoginView(View): class LoginView(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
...@@ -32,7 +34,7 @@ class LoginView(View): ...@@ -32,7 +34,7 @@ class LoginView(View):
nex = request.GET['next'] nex = request.GET['next']
except: except:
pass pass
return render_to_response("login.html", RequestContext(request,{'next': nex})) return render_to_response("login.html", RequestContext(request, {'next': nex}))
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if request.POST['pw'] != 'ezmiez': if request.POST['pw'] != 'ezmiez':
return redirect('/') return redirect('/')
...@@ -70,29 +72,66 @@ def _list_instances(request): ...@@ -70,29 +72,66 @@ def _list_instances(request):
@require_GET @require_GET
@login_required @login_required
def home(request): def home(request):
return render_to_response("home.html", RequestContext(request,{ return render_to_response("home.html", RequestContext(request, {
'templates': Template.objects.all(), 'templates': Template.objects.filter(state='READY'),
'mytemplates': Template.objects.filter(owner=request.user),
'instances': _list_instances(request), 'instances': _list_instances(request),
'groups': request.user.person_set.all()[0].owned_groups.all(), 'groups': request.user.person_set.all()[0].owned_groups.all(),
'semesters': Semester.objects.all() 'semesters': Semester.objects.all()
})) }))
@require_GET def ajax_template_name_unique(request, name):
@login_required s = "True"
def ajax_template_wizard(request): if Template.objects.filter(name=name).exists():
return render_to_response('new-template-flow.html', RequestContext(request,{ s = "False"
'templates': Template.objects.all(), return HttpResponse(s)
class AjaxTemplateWizard(View):
def get(self, request, *args, **kwargs):
return render_to_response('new-template-flow-1.html', RequestContext(request,{
'templates': Template.objects.filter(public=True)
# + Template.objects.filter(owner=request.user),
})) }))
def post(self, request, *args, **kwargs):
base = get_object_or_404(Template, id=request.POST['base'])
if base.owner != request.user and not base.public and not request.user.is_superuser:
raise PermissionDenied()
return render_to_response('new-template-flow.html', RequestContext(request, {
'sizes': InstanceType.objects.all(),
'base': base,
}))
ajax_template_wizard = login_required(AjaxTemplateWizard.as_view())
@require_POST
@login_required
def vm_saveas(request, vmid):
inst = get_object_or_404(Instance, pk=vmid)
if inst.owner != request.user and not request.user.is_superuser:
raise PermissionDenied()
inst.save_as()
messages.success(request, _("Template is being saved..."))
return redirect(inst)
@require_POST @require_POST
@login_required @login_required
def vm_new(request, template): def vm_new(request, template):
m = get_object_or_404(Template, pk=template) base = get_object_or_404(Template, pk=template)
if "name" in request.POST:
if base.owner != request.user and not base.public and not request.user.is_superuser:
raise PermissionDenied()
name = request.POST['name']
t = Template.objects.create(name=name, disk=base.disk, instance_type_id=request.POST['size'], network=base.network, owner=request.user)
t.access_type = base.access_type
t.description = request.POST['description']
t.system = base.system
t.save()
base = t
try: try:
i = Instance.submit(m, request.user) i = Instance.submit(base, request.user, extra="<RECONTEXT>YES</RECONTEXT>")
return redirect(i) return redirect(i)
except: except Exception as e:
raise logger.error('Failed to create virtual machine.' + unicode(e))
messages.error(request, _('Failed to create virtual machine.')) messages.error(request, _('Failed to create virtual machine.'))
return redirect('/') return redirect('/')
...@@ -111,6 +150,8 @@ vm_list = login_required(VmListView.as_view()) ...@@ -111,6 +150,8 @@ vm_list = login_required(VmListView.as_view())
def vm_show(request, iid): def vm_show(request, iid):
inst = get_object_or_404(Instance, id=iid, owner=request.user) inst = get_object_or_404(Instance, id=iid, owner=request.user)
inst.update_state() inst.update_state()
if inst.template.state == "SAVING":
inst.check_if_is_save_as_done()
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,
...@@ -188,7 +229,7 @@ class VmDeleteView(View): ...@@ -188,7 +229,7 @@ class VmDeleteView(View):
def get(self, request, iid, *args, **kwargs): def get(self, request, iid, *args, **kwargs):
i = get_object_or_404(Instance, id=iid, owner=request.user) i = get_object_or_404(Instance, id=iid, owner=request.user)
return render_to_response("confirm_delete.html", RequestContext(request,{ return render_to_response("confirm_delete.html", RequestContext(request, {
'i': i})) 'i': i}))
vm_delete = login_required(VmDeleteView.as_view()) vm_delete = login_required(VmDeleteView.as_view())
......
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