Commit 6efe1ef5 by Őry Máté

one: add save as functionality

parent e9fe67c6
......@@ -26,12 +26,14 @@ urlpatterns = patterns('',
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_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'^fwapi/$', 'firewall.views.firewall_api', name='firewall_api'),
url(r'^store/$', 'store.views.index', name='store_index'),
url(r'^store/gui/$', 'store.views.gui', name='store_gui'),
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/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/download$', 'store.views.ajax_download', name='store_ajax_download'),
url(r'^ajax/store/upload$', 'store.views.ajax_upload', name='store_ajax_upload'),
......
......@@ -47,6 +47,7 @@ submit_vm.short_description = _('Submit VM')
class TemplateAdmin(contrib.admin.ModelAdmin):
model=models.Template
list_display = ('name', 'state', 'owner', 'system')
class InstanceAdmin(contrib.admin.ModelAdmin):
model=models.Instance
......
......@@ -122,10 +122,11 @@ class SshKey(models.Model):
TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')), ("SAVE", _('saving')), ("READY", _('ready')))
TYPES = {"LAB": {"verbose_name": _('lab'), "suspend": td(hours=5), "delete": td(days=15)},
"PROJECT": {"verbose_name": _('project'), "suspend": td(weeks=5), "delete": td(days=366/2)},
"SERVER": {"verbose_name": _('server'), "suspend": td(days=365), "delete": None},
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'), "id": "PROJECT", "suspend": td(weeks=5), "delete": td(days=366/2), "help_text": _('For project work.')},
"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()])
class Share(models.Model):
name = models.CharField(max_length=100, unique=True, verbose_name=_('name'))
......@@ -222,10 +223,14 @@ class InstanceType(models.Model):
verbose_name=_('name'))
CPU = models.IntegerField(help_text=_('CPU cores.'))
RAM = models.IntegerField(help_text=_('Mebibytes of memory.'))
credit = models.IntegerField(verbose_name=_('credits'),
help_text=_('Price of instance.'))
def __unicode__(self):
return u"%s" % self.name
class Meta:
ordering = ['credit']
TEMPLATE_STATES = (('NEW', _('new')), ('PREPARING', _('preparing')),
TEMPLATE_STATES = (('NEW', _('new')),
('SAVING', _('saving')), ('READY', _('ready')), )
"""
Virtual machine template specifying OS, disk, type and network.
......@@ -455,7 +460,8 @@ class Instance(models.Model):
proc = subprocess.Popen(["/opt/occi.sh", "compute",
"delete", "%d"%self.one_id], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
self.firewall_host.delete()
if self.firewall_host:
self.firewall_host.delete()
reload_firewall_lock()
def _update_vm(self, template):
......@@ -499,7 +505,10 @@ class Instance(models.Model):
imgname = "template-%d-%d" % (self.template.id, self.id)
self._update_vm('<DISK id="0"><SAVE_AS name="%s"/></DISK>' % imgname)
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()
if self.state != 'DONE':
return false
......@@ -509,9 +518,9 @@ class Instance(models.Model):
if len(disks) != 1:
return false
self.template.disk_id = disks[0].id
self.template.status = 'READY'
self.template.state = 'READY'
self.template.save()
self.host.delete()
self.firewall_host.delete()
return True
......@@ -523,7 +532,7 @@ def delete_instance(sender, instance, using, **kwargs):
if instance.state != "DONE":
instance.one_delete()
try:
instance.host.delete()
instance.firewall_host.delete()
except:
pass
post_delete.connect(delete_instance, sender=Instance, dispatch_uid="delete_instance")
......
......@@ -43,6 +43,21 @@ body
margin-top:0;
padding:10px;
}
&.wide {
margin-right: 30px;
}
&.note {
background-color: #ffc;
}
ol {
margin-left: 2em;
&.done {
text-color: #aaa;
}
}
}
.big {
font-size: 2em;
}
.wm-list{
list-style: none;
......@@ -75,6 +90,14 @@ ul.messagelist li.error
{
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
{
......@@ -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);
}
}
.hilight {
background-color: #ff6;
}
......@@ -282,6 +282,27 @@
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{
list-style: none;
......
......@@ -73,6 +73,9 @@
<div class="contentblock" id="template">
<h2>{% trans "My templates" %}</h2>
<ul class="wm-list">
{% for t in mytemplates %}
<li class="wm">{{t.name}}</li>
{% endfor %}
<li class="wm">
<div class="summary">
<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 @@
{% endblock %}
{% 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="contentblock" id="state">
<h2>{{name}}</h2>
......
......@@ -10,7 +10,6 @@ from django.core.mail import mail_managers, send_mail
from django.db import transaction
from django.forms import ModelForm, Textarea
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.template import RequestContext
from django.template.loader import render_to_string
......@@ -19,11 +18,14 @@ from django.utils.translation import get_language as lang
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.http import *
from django.views.generic import *
from firewall.tasks import *
from one.models import *
from school.models import *
import django.contrib.auth as auth
from firewall.tasks import *
import json
import logging
logger = logging.getLogger(__name__)
class LoginView(View):
def get(self, request, *args, **kwargs):
......@@ -32,12 +34,12 @@ class LoginView(View):
nex = request.GET['next']
except:
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):
if request.POST['pw'] != 'ezmiez':
return redirect('/')
p, created = User.objects.get_or_create(username=request.POST['neptun'])
if created:
if created:
p.set_unusable_password()
if not p.email:
p.email = "%s@nc.hszk.bme.hu" % p.username
......@@ -70,29 +72,66 @@ def _list_instances(request):
@require_GET
@login_required
def home(request):
return render_to_response("home.html", RequestContext(request,{
'templates': Template.objects.all(),
return render_to_response("home.html", RequestContext(request, {
'templates': Template.objects.filter(state='READY'),
'mytemplates': Template.objects.filter(owner=request.user),
'instances': _list_instances(request),
'groups': request.user.person_set.all()[0].owned_groups.all(),
'semesters': Semester.objects.all()
}))
@require_GET
def ajax_template_name_unique(request, name):
s = "True"
if Template.objects.filter(name=name).exists():
s = "False"
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 ajax_template_wizard(request):
return render_to_response('new-template-flow.html', RequestContext(request,{
'templates': Template.objects.all(),
}))
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
@login_required
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:
i = Instance.submit(m, request.user)
i = Instance.submit(base, request.user, extra="<RECONTEXT>YES</RECONTEXT>")
return redirect(i)
except:
raise
except Exception as e:
logger.error('Failed to create virtual machine.' + unicode(e))
messages.error(request, _('Failed to create virtual machine.'))
return redirect('/')
......@@ -111,6 +150,8 @@ vm_list = login_required(VmListView.as_view())
def vm_show(request, iid):
inst = get_object_or_404(Instance, id=iid, owner=request.user)
inst.update_state()
if inst.template.state == "SAVING":
inst.check_if_is_save_as_done()
return render_to_response("show.html", RequestContext(request,{
'uri': inst.get_connect_uri(),
'state': inst.state,
......@@ -188,7 +229,7 @@ class VmDeleteView(View):
def get(self, request, iid, *args, **kwargs):
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}))
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