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
......
...@@ -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>
...@@ -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