Merge pull request #486 from tiwariav/task/3728/vm_teminate_animation

task/3728 vm termination animation
This commit is contained in:
Arvind Tiwari 2017-09-23 23:26:00 +05:30 committed by GitHub
commit ae1a317bf9
8 changed files with 263 additions and 89 deletions

View file

@ -561,6 +561,9 @@ msgstr "Aktueller Preis"
msgid "Your VM is" msgid "Your VM is"
msgstr "Deine VM ist" msgstr "Deine VM ist"
msgid "Terminating"
msgstr "Beenden"
msgid "Pending" msgid "Pending"
msgstr "In Vorbereitung" msgstr "In Vorbereitung"
@ -573,6 +576,11 @@ msgstr "Fehlgeschlagen"
msgid "Terminate VM" msgid "Terminate VM"
msgstr "VM Beenden" msgstr "VM Beenden"
msgid "Sorry, there was an unexpected error. Kindly retry."
msgstr ""
"Bitte entschuldige, es scheint ein unerwarteter Fehler aufgetreten zu sein. "
"Versuche es doch bitte noch einmal."
msgid "Something doesn't work?" msgid "Something doesn't work?"
msgstr "Etwas funktioniert nicht?" msgstr "Etwas funktioniert nicht?"
@ -591,6 +599,11 @@ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
msgid "OK" msgid "OK"
msgstr "" msgstr ""
#, python-format
msgid ""
"Your Virtual Machine %(virtual_machine.name)s is successfully terminated!"
msgstr "Deine Virtuelle Machine (VM) %(virtual_machine.name)s wurde erfolgreich beendet!"
msgid "Virtual Machines" msgid "Virtual Machines"
msgstr "Virtuelle Maschinen" msgstr "Virtuelle Maschinen"
@ -678,12 +691,20 @@ msgid ""
"contact Data Center Light Support." "contact Data Center Light Support."
msgstr "Kontaktiere den Data Center Light Support." msgstr "Kontaktiere den Data Center Light Support."
msgid "Terminated"
msgstr "Beendet"
msgid "Error terminating VM"
msgstr "Fehler beenden VM"
msgid "Virtual Machine Cancellation" msgid "Virtual Machine Cancellation"
msgstr "VM Kündigung" msgstr "VM Kündigung"
#, python-format #~ msgid "Close"
msgid "VM %(VM_ID)s terminated successfully" #~ msgstr "Schliessen"
msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ msgid "VM %(VM_ID)s terminated successfully"
#~ msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ msgid "days" #~ msgid "days"
#~ msgstr "tage" #~ msgstr "tage"
@ -760,9 +781,6 @@ msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ msgid "Ipv6" #~ msgid "Ipv6"
#~ msgstr "IPv6" #~ msgstr "IPv6"
#~ msgid "Close"
#~ msgstr "Schliessen"
#~ msgid "Cancel" #~ msgid "Cancel"
#~ msgstr "Beenden" #~ msgstr "Beenden"

View file

@ -139,6 +139,10 @@
.modal-text p:not(:last-of-type){ .modal-text p:not(:last-of-type){
margin-bottom: 5px; margin-bottom: 5px;
} }
.modal-title + .modal-footer {
margin-top: 5px;
}
.modal-footer { .modal-footer {
border-top: 0px solid #e5e5e5; border-top: 0px solid #e5e5e5;
width: 100%; width: 100%;

View file

@ -853,6 +853,9 @@ a.list-group-item-danger:focus,
.panel-danger > .panel-heading { .panel-danger > .panel-heading {
color: #eb4d5c; color: #eb4d5c;
} }
.alert-danger{
background: rgba(235, 204, 209, 0.2);
}
.has-error .form-control, .has-error .form-control,
.has-error .input-group-addon { .has-error .input-group-addon {
color: #eb4d5c; color: #eb4d5c;

View file

@ -290,6 +290,11 @@
text-align: center; text-align: center;
} }
.vm-vmid .alert {
margin-top: 15px;
margin-bottom: -60px;
}
.vm-item-lg { .vm-item-lg {
font-size: 22px; font-size: 22px;
margin-top: 5px; margin-top: 5px;
@ -305,6 +310,10 @@
color: #e47f2f; color: #e47f2f;
} }
.vm-color-failed {
color: #eb4d5c;
}
.vm-detail-item .value{ .vm-detail-item .value{
font-weight: 400; font-weight: 400;
} }
@ -512,7 +521,7 @@
color: #87B6EA; color: #87B6EA;
} }
.vm-status, .vm-status-active, .vm-status-failed { .vm-status, .vm-status-active, .vm-status-failed, .vm-status-pending {
font-weight: 600; font-weight: 600;
} }
.vm-status-active { .vm-status-active {
@ -521,6 +530,9 @@
.vm-status-failed { .vm-status-failed {
color: #eb4d5c; color: #eb4d5c;
} }
.vm-status-pending {
color: #e47f2f;
}
@media (min-width:768px) { @media (min-width:768px) {
.dashboard-subtitle { .dashboard-subtitle {
@ -628,3 +640,27 @@
right: 8px; right: 8px;
} }
} }
.processing > .btn {
position: relative;
border-color: #eee;
}
.processing > .btn:hover,
.processing > .btn:focus,
.processing > .btn:active {
border-color: #eee;
}
.processing > .btn:after {
content: ' ';
display: block;
position: absolute;
background-image: url('/static/hosting/img/ajax-loader.gif');
background-repeat: no-repeat;
background-position: center;
background-color: #eee;
width: 100%;
top: 0;
height: 100%;
left: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

View file

@ -1,4 +1,84 @@
$(document).ready(function () { function VMTerminateStatus($container, url) {
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
success: function(data) {
VMTerminateSuccess($container, data);
},
error: function() {
setTimeout(function(){
VMTerminateStatus($container, url);
}, 5000);
}
});
}
function VMTerminateActive($container, altText) {
$container.find('.alert-danger').addClass('hide');
$container.addClass('processing')
.find('.vm-item-lg').attr('class', '')
.addClass('vm-item-lg vm-color-failed')
.text(altText);
$container.find('.btn').prop('disabled', true);
$('#confirm-cancel').modal('hide');
}
function VMTerminateSuccess($container, data) {
$container.addClass('terminate-success')
.find('.vm-item-lg').text(data.text);
$container.find('.btn').remove();
$('#terminate-success').modal('show');
}
function VMTerminateFail($container, data, text) {
$container.addClass('terminate-fail')
.find('.vm-item-lg').text(text);
$container.find('.btn').prop('disabled', false);
$container.find('.alert-danger').text(data.text).removeClass('hide');
$container.removeClass('processing');
}
$(document).ready(function() {
$('#confirm-cancel').on('click', '.btn-ok', function(e) {
var url = $('#virtual_machine_cancel_form').attr('action');
var $container = $('#terminate-VM');
var text = $container.find('.vm-item-lg').text();
var altText = $container.attr('data-alt');
VMTerminateActive($container, altText);
$.post(url)
.done(function(data) {
if (data.status == true) {
VMTerminateSuccess($container, data);
} else {
if ('text' in data) {
VMTerminateFail($container, data, text);
} else {
VMTerminateStatus($container, url);
}
}
})
.fail(function(data) {
if (data.status==504) {
VMTerminateStatus($container, url);
} else {
VMTerminateFail($container, data, text);
}
})
});
var hash = window.location.hash;
hash && $('ul.nav a[href="' + hash + '"]').tab('show');
$('.nav-tabs a').click(function(e) {
$(this).tab('show');
var scrollmem = $('body').scrollTop() || $('html').scrollTop();
window.location.hash = this.hash;
$('html,body').scrollTop(scrollmem);
});
$('.modal-text').removeClass('hide'); $('.modal-text').removeClass('hide');
var create_vm_form = $('#virtual_machine_create_form'); var create_vm_form = $('#virtual_machine_create_form');
create_vm_form.submit(function () { create_vm_form.submit(function () {
@ -32,19 +112,4 @@ $(document).ready(function () {
}); });
return false; return false;
}); });
$('#confirm-cancel').on('click', '.btn-ok', function (e) {
$('#virtual_machine_cancel_form').trigger('submit');
});
var hash = window.location.hash;
hash && $('ul.nav a[href="' + hash + '"]').tab('show');
$('.nav-tabs a').click(function (e) {
$(this).tab('show');
var scrollmem = $('body').scrollTop() || $('html').scrollTop();
window.location.hash = this.hash;
$('html,body').scrollTop(scrollmem);
});
}); });

View file

@ -53,19 +53,26 @@
<h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2> <h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2>
<div class="vm-vmid"> <div class="vm-vmid">
<div class="vm-item-subtitle">{% trans "Your VM is" %}</div> <div class="vm-item-subtitle">{% trans "Your VM is" %}</div>
{% if virtual_machine.state == 'PENDING' %} <div id="terminate-VM" data-alt="{% trans 'Terminating' %}">
<div class="vm-item-lg vm-color-pending">{% trans "Pending" %}</div> {% if virtual_machine.state == 'PENDING' %}
{% elif virtual_machine.state == 'ACTIVE' %} <div class="vm-item-lg vm-color-pending">{% trans "Pending" %}</div>
<div class="vm-item-lg vm-color-online">{% trans "Online" %}</div> {% elif virtual_machine.state == 'ACTIVE' %}
{% elif virtual_machine.state == 'FAILED'%} <div class="vm-item-lg vm-color-online">{% trans "Online" %}</div>
<div class="vm-item-lg vm-color-failed">{% trans "Failed" %}</div> {% elif virtual_machine.state == 'FAILED'%}
{% endif %} <div class="vm-item-lg vm-color-failed">{% trans "Failed" %}</div>
{% if not virtual_machine.status == 'canceled' %} {% else %}
<form method="POST" id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}"> <div class="vm-item-lg"></div>
{% csrf_token %} {% endif %}
</form> {% if not virtual_machine.status == 'canceled' %}
<button data-href="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-vm-term">{% trans "Terminate VM" %}</button> <form method="POST" id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}">
{% endif %} {% csrf_token %}
</form>
<button data-toggle="modal" data-target="#confirm-cancel" class="btn btn-vm-term">{% trans "Terminate VM" %}</button>
<div class="alert alert-danger hide">
{% trans "Sorry, there was an unexpected error. Kindly retry." %}
</div>
{% endif %}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -89,24 +96,41 @@
</div> </div>
<!-- Cancel Modal --> <!-- Cancel Modal -->
<div class="modal fade" id="confirm-cancel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal fade" id="confirm-cancel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Confirm"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="Confirm"><span aria-hidden="true">&times;</span></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div> <div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine"%}</h4> <h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine" %}</h4>
<div class="modal-text"> <div class="modal-text">
<p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p> <p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p>
<p><strong>{{virtual_machine.name}}</strong></p> <p><strong>{{virtual_machine.name}}</strong></p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a class="btn btn-danger btn-ok btn-wide">{% trans "OK" %}</a> <a class="btn btn-danger btn-ok btn-wide">{% trans "OK" %}</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- / Cancel Modal -->
<!-- Success Modal -->
<div class="modal fade" id="terminate-success" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
</div>
<div class="modal-body">
<div class="modal-icon"><i class="fa fa-check" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine <strong>{{machine_name}}</strong> is successfully terminated!{% endblocktrans %}</h4>
<div class="modal-footer">
<a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a>
</div>
</div>
</div>
</div>
</div> </div>
<!-- / Cancel Modal --> <!-- / Cancel Modal -->
{%endblock%} {%endblock%}

View file

@ -1,6 +1,7 @@
import json import json
import logging import logging
import uuid import uuid
from time import sleep
from django import forms from django import forms
from django.conf import settings from django.conf import settings
@ -10,16 +11,17 @@ from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.http import Http404
from django.http import HttpResponseRedirect, HttpResponse from django.http import Http404, HttpResponseRedirect, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect, render
from django.shortcuts import render
from django.utils.http import urlsafe_base64_decode from django.utils.http import urlsafe_base64_decode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import View, CreateView, FormView, ListView, \ from django.utils.translation import ugettext
DetailView, \ from django.views.generic import (
DeleteView, TemplateView, UpdateView View, CreateView, FormView, ListView, DetailView, DeleteView,
TemplateView, UpdateView
)
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from oca.pool import WrongIdError from oca.pool import WrongIdError
from stored_messages.api import mark_read from stored_messages.api import mark_read
@ -35,8 +37,9 @@ from utils.forms import BillingAddressForm, PasswordResetRequestForm, \
UserBillingAddressForm UserBillingAddressForm
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, \ from utils.views import (
LoginViewMixin PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
)
from .forms import HostingUserSignupForm, HostingUserLoginForm, \ from .forms import HostingUserSignupForm, HostingUserLoginForm, \
UserHostingKeyForm, generate_ssh_key_name UserHostingKeyForm, generate_ssh_key_name
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
@ -985,7 +988,19 @@ class VirtualMachineView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
vm = self.get_object() vm = self.get_object()
if vm is None: if vm is None:
return redirect(reverse('hosting:virtual_machines')) if self.request.is_ajax():
storage = messages.get_messages(request)
for m in storage:
pass
storage.used = True
return HttpResponse(
json.dumps({'text': ugettext('Terminated')}),
content_type="application/json"
)
else:
return redirect(reverse('hosting:virtual_machines'))
elif self.request.is_ajax():
return HttpResponse()
try: try:
serializer = VirtualMachineSerializer(vm) serializer = VirtualMachineSerializer(vm)
context = { context = {
@ -1000,6 +1015,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
response = {'status': False}
owner = self.request.user owner = self.request.user
vm = self.get_object() vm = self.get_object()
@ -1009,42 +1025,50 @@ class VirtualMachineView(LoginRequiredMixin, View):
email=owner.email, email=owner.email,
password=owner.password password=owner.password
) )
vm_data = VirtualMachineSerializer(manager.get_vm(vm.id)).data
terminated = manager.delete_vm( try:
vm.id vm_data = VirtualMachineSerializer(manager.get_vm(vm.id)).data
) except WrongIdError:
return redirect(reverse('hosting:virtual_machines'))
terminated = manager.delete_vm(vm.id)
if not terminated: if not terminated:
messages.error( response['text'] = ugettext(
request, 'Error terminating VM') + opennebula_vm_id
'Error terminating VM %s' % (opennebula_vm_id) else:
) for t in range(15):
return HttpResponseRedirect(self.get_success_url()) try:
context = { manager.get_vm(opennebula_vm_id)
'vm': vm_data, except WrongIdError:
'base_url': "{0}://{1}".format(self.request.scheme, response['status'] = True
self.request.get_host()), response['text'] = ugettext('Terminated')
'page_header': _('Virtual Machine Cancellation') break
} except BaseException:
email_data = { break
'subject': context['page_header'], else:
'to': self.request.user.email, sleep(2)
'context': context, context = {
'template_name': 'vm_canceled', 'vm': vm_data,
'template_path': 'hosting/emails/', 'base_url': "{0}://{1}".format(self.request.scheme,
'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, self.request.get_host()),
} 'page_header': _('Virtual Machine Cancellation')
email = BaseEmail(**email_data) }
email.send() email_data = {
'subject': context['page_header'],
messages.error( 'to': self.request.user.email,
request, 'context': context,
_('VM %(VM_ID)s terminated successfully') % { 'template_name': 'vm_canceled',
'VM_ID': opennebula_vm_id} 'template_path': 'hosting/emails/',
'from_address': settings.DCL_SUPPORT_FROM_ADDRESS,
}
email = BaseEmail(**email_data)
email.send()
return HttpResponse(
json.dumps(response),
content_type="application/json"
) )
return HttpResponseRedirect(self.get_success_url())
class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin, class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin,
ListView): ListView):