diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po index 16babbfa..b273dbcf 100644 --- a/hosting/locale/de/LC_MESSAGES/django.po +++ b/hosting/locale/de/LC_MESSAGES/django.po @@ -561,6 +561,9 @@ msgstr "Aktueller Preis" msgid "Your VM is" msgstr "Deine VM ist" +msgid "Terminating" +msgstr "Beenden" + msgid "Pending" msgstr "In Vorbereitung" @@ -573,6 +576,11 @@ msgstr "Fehlgeschlagen" msgid "Terminate VM" 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?" msgstr "Etwas funktioniert nicht?" @@ -591,6 +599,11 @@ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst" msgid "OK" 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" msgstr "Virtuelle Maschinen" @@ -678,12 +691,20 @@ msgid "" "contact 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" msgstr "VM Kündigung" -#, python-format -msgid "VM %(VM_ID)s terminated successfully" -msgstr "VM %(VM_ID)s erfolgreich beendet" +#~ msgid "Close" +#~ msgstr "Schliessen" + +#~ msgid "VM %(VM_ID)s terminated successfully" +#~ msgstr "VM %(VM_ID)s erfolgreich beendet" #~ msgid "days" #~ msgstr "tage" @@ -760,9 +781,6 @@ msgstr "VM %(VM_ID)s erfolgreich beendet" #~ msgid "Ipv6" #~ msgstr "IPv6" -#~ msgid "Close" -#~ msgstr "Schliessen" - #~ msgid "Cancel" #~ msgstr "Beenden" diff --git a/hosting/static/hosting/css/commons.css b/hosting/static/hosting/css/commons.css index c70051b6..317caabc 100644 --- a/hosting/static/hosting/css/commons.css +++ b/hosting/static/hosting/css/commons.css @@ -139,6 +139,10 @@ .modal-text p:not(:last-of-type){ margin-bottom: 5px; } + +.modal-title + .modal-footer { + margin-top: 5px; +} .modal-footer { border-top: 0px solid #e5e5e5; width: 100%; diff --git a/hosting/static/hosting/css/landing-page.css b/hosting/static/hosting/css/landing-page.css index 5c71b7ca..ed8fb310 100644 --- a/hosting/static/hosting/css/landing-page.css +++ b/hosting/static/hosting/css/landing-page.css @@ -853,6 +853,9 @@ a.list-group-item-danger:focus, .panel-danger > .panel-heading { color: #eb4d5c; } +.alert-danger{ + background: rgba(235, 204, 209, 0.2); +} .has-error .form-control, .has-error .input-group-addon { color: #eb4d5c; diff --git a/hosting/static/hosting/css/virtual-machine.css b/hosting/static/hosting/css/virtual-machine.css index b4916da0..a14abc31 100644 --- a/hosting/static/hosting/css/virtual-machine.css +++ b/hosting/static/hosting/css/virtual-machine.css @@ -290,6 +290,11 @@ text-align: center; } +.vm-vmid .alert { + margin-top: 15px; + margin-bottom: -60px; +} + .vm-item-lg { font-size: 22px; margin-top: 5px; @@ -305,6 +310,10 @@ color: #e47f2f; } +.vm-color-failed { + color: #eb4d5c; +} + .vm-detail-item .value{ font-weight: 400; } @@ -512,7 +521,7 @@ color: #87B6EA; } -.vm-status, .vm-status-active, .vm-status-failed { +.vm-status, .vm-status-active, .vm-status-failed, .vm-status-pending { font-weight: 600; } .vm-status-active { @@ -521,6 +530,9 @@ .vm-status-failed { color: #eb4d5c; } +.vm-status-pending { + color: #e47f2f; +} @media (min-width:768px) { .dashboard-subtitle { @@ -627,4 +639,28 @@ left: auto; 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; } \ No newline at end of file diff --git a/hosting/static/hosting/img/ajax-loader.gif b/hosting/static/hosting/img/ajax-loader.gif new file mode 100644 index 00000000..a7c3f2ba Binary files /dev/null and b/hosting/static/hosting/img/ajax-loader.gif differ diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 7d66afaf..db2621c1 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -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'); var create_vm_form = $('#virtual_machine_create_form'); create_vm_form.submit(function () { @@ -32,19 +112,4 @@ $(document).ready(function () { }); 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); - }); - }); diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index 6243c309..06d86032 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -53,19 +53,26 @@

{% trans "Status" %}

{% trans "Your VM is" %}
- {% if virtual_machine.state == 'PENDING' %} -
{% trans "Pending" %}
- {% elif virtual_machine.state == 'ACTIVE' %} -
{% trans "Online" %}
- {% elif virtual_machine.state == 'FAILED'%} -
{% trans "Failed" %}
- {% endif %} - {% if not virtual_machine.status == 'canceled' %} -
- {% csrf_token %} -
- - {% endif %} +
+ {% if virtual_machine.state == 'PENDING' %} +
{% trans "Pending" %}
+ {% elif virtual_machine.state == 'ACTIVE' %} +
{% trans "Online" %}
+ {% elif virtual_machine.state == 'FAILED'%} +
{% trans "Failed" %}
+ {% else %} +
+ {% endif %} + {% if not virtual_machine.status == 'canceled' %} +
+ {% csrf_token %} +
+ +
+ {% trans "Sorry, there was an unexpected error. Kindly retry." %} +
+ {% endif %} +
@@ -89,24 +96,41 @@ + + + {%endblock%} diff --git a/hosting/views.py b/hosting/views.py index 7727c1a3..d7c4c168 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1,6 +1,7 @@ import json import logging import uuid +from time import sleep from django import forms 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.files.base import ContentFile from django.core.urlresolvers import reverse_lazy, reverse -from django.http import Http404 -from django.http import HttpResponseRedirect, HttpResponse -from django.shortcuts import redirect -from django.shortcuts import render + +from django.http import Http404, HttpResponseRedirect, HttpResponse +from django.shortcuts import redirect, render from django.utils.http import urlsafe_base64_decode from django.utils.safestring import mark_safe -from django.utils.translation import get_language, ugettext_lazy as _ -from django.views.generic import View, CreateView, FormView, ListView, \ - DetailView, \ - DeleteView, TemplateView, UpdateView +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext +from django.views.generic import ( + View, CreateView, FormView, ListView, DetailView, DeleteView, + TemplateView, UpdateView +) from guardian.mixins import PermissionRequiredMixin from oca.pool import WrongIdError from stored_messages.api import mark_read @@ -35,8 +37,9 @@ from utils.forms import BillingAddressForm, PasswordResetRequestForm, \ UserBillingAddressForm from utils.mailer import BaseEmail from utils.stripe_utils import StripeUtils -from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, \ - LoginViewMixin +from utils.views import ( + PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin +) from .forms import HostingUserSignupForm, HostingUserLoginForm, \ UserHostingKeyForm, generate_ssh_key_name from .mixins import ProcessVMSelectionMixin @@ -985,7 +988,19 @@ class VirtualMachineView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): vm = self.get_object() 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: serializer = VirtualMachineSerializer(vm) context = { @@ -1000,6 +1015,7 @@ class VirtualMachineView(LoginRequiredMixin, View): return render(request, self.template_name, context) def post(self, request, *args, **kwargs): + response = {'status': False} owner = self.request.user vm = self.get_object() @@ -1009,42 +1025,50 @@ class VirtualMachineView(LoginRequiredMixin, View): email=owner.email, password=owner.password ) - vm_data = VirtualMachineSerializer(manager.get_vm(vm.id)).data - terminated = manager.delete_vm( - vm.id - ) + + try: + 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: - messages.error( - request, - 'Error terminating VM %s' % (opennebula_vm_id) - ) - return HttpResponseRedirect(self.get_success_url()) - context = { - 'vm': vm_data, - 'base_url': "{0}://{1}".format(self.request.scheme, - self.request.get_host()), - 'page_header': _('Virtual Machine Cancellation') - } - email_data = { - 'subject': context['page_header'], - 'to': self.request.user.email, - 'context': context, - 'template_name': 'vm_canceled', - 'template_path': 'hosting/emails/', - 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, - } - email = BaseEmail(**email_data) - email.send() - - messages.error( - request, - _('VM %(VM_ID)s terminated successfully') % { - 'VM_ID': opennebula_vm_id} + response['text'] = ugettext( + 'Error terminating VM') + opennebula_vm_id + else: + for t in range(15): + try: + manager.get_vm(opennebula_vm_id) + except WrongIdError: + response['status'] = True + response['text'] = ugettext('Terminated') + break + except BaseException: + break + else: + sleep(2) + context = { + 'vm': vm_data, + 'base_url': "{0}://{1}".format(self.request.scheme, + self.request.get_host()), + 'page_header': _('Virtual Machine Cancellation') + } + email_data = { + 'subject': context['page_header'], + 'to': self.request.user.email, + 'context': context, + 'template_name': 'vm_canceled', + '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, ListView):