Merge upstream master

This commit is contained in:
PCoder 2017-09-28 01:35:09 +05:30
commit 5a2a134070
14 changed files with 202 additions and 46 deletions

1
.gitignore vendored
View file

@ -36,3 +36,4 @@ secret-key
.env .env
*.mo *.mo
*.log *.log
*.sql

View file

@ -1,3 +1,5 @@
Next:
* #3764: [hosting] Show cancelled VMs' invoices
1.2.3: 2017-09-25 1.2.3: 2017-09-25
* #3484: [dcl, hosting] Refactored account activation, password reset, VM order and cancellation email * #3484: [dcl, hosting] Refactored account activation, password reset, VM order and cancellation email
* #3731: [dcl, hosting] Added cdist ssh key handler * #3731: [dcl, hosting] Added cdist ssh key handler
@ -8,6 +10,7 @@
* #3777: [hosting] Create new VM calculator added like dcl landing * #3777: [hosting] Create new VM calculator added like dcl landing
* #3781: [hosting] Resend activation mail * #3781: [hosting] Resend activation mail
* #3806: [hosting] Fix can not create VMs after password reset * #3806: [hosting] Fix can not create VMs after password reset
* #3812: [hosting] Modal check icon made thin and font-size fixed
* Feature: [cms, blog] Added /cms prefix for all the django-cms generated urls * Feature: [cms, blog] Added /cms prefix for all the django-cms generated urls
* Bugfix: [dcl, hosting] added host to celery error mails * Bugfix: [dcl, hosting] added host to celery error mails
* Bugfix: [ungleich] Fixed wrong subdomain digitalglarus.ungleich.ch * Bugfix: [ungleich] Fixed wrong subdomain digitalglarus.ungleich.ch

View file

@ -1653,3 +1653,20 @@ a.list-group-item-danger.active:focus {
.panel-danger > .panel-heading .badge { .panel-danger > .panel-heading .badge {
background-color: #eb4d5c; background-color: #eb4d5c;
} }
.checkmark {
display: inline-block;
}
.checkmark:after {
/*Add another block-level blank space*/
content: '';
display: block;
/*Make it a small rectangle so the border will create an L-shape*/
width: 25px;
height: 60px;
/*Add a white border on the bottom and left, creating that 'L' */
border: solid #777;
border-width: 0 3px 3px 0;
/*Rotate the L 45 degrees to turn it into a checkmark*/
transform: rotate(45deg);
}

View file

@ -13,7 +13,7 @@ from hosting.models import HostingOrder, HostingBill
from membership.models import StripeCustomer, CustomUser from membership.models import StripeCustomer, CustomUser
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer from opennebula_api.serializers import VirtualMachineSerializer
from utils.hosting_utils import get_all_public_keys from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail
from utils.forms import UserBillingAddressForm from utils.forms import UserBillingAddressForm
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.models import BillingAddress from utils.models import BillingAddress
@ -176,6 +176,7 @@ def create_vm_task(self, vm_template_id, user, specs, template,
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
if new_host is not None: if new_host is not None:
custom_user = CustomUser.objects.get(email=user.get('email')) custom_user = CustomUser.objects.get(email=user.get('email'))
get_or_create_vm_detail(custom_user, manager, vm_id)
if custom_user is not None: if custom_user is not None:
public_keys = get_all_public_keys(custom_user) public_keys = get_all_public_keys(custom_user)
keys = [{'value': key, 'state': True} for key in keys = [{'value': key, 'state': True} for key in

View file

@ -8,7 +8,7 @@
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><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-check" aria-hidden="true"></i></div> <div class="modal-icon"><i class="checkmark" aria-hidden="true"></i></div>
<h4 class="modal-title">{% trans "Request Sent" %}</h4> <h4 class="modal-title">{% trans "Request Sent" %}</h4>
<p class="modal-text">{% trans "Thank you for your subscription! You will receive a confirmation mail from our team" %}</p> <p class="modal-text">{% trans "Thank you for your subscription! You will receive a confirmation mail from our team" %}</p>
</div> </div>

View file

@ -388,7 +388,7 @@ msgid "Processing..."
msgstr "Abarbeitung..." msgstr "Abarbeitung..."
msgid "Hold tight, we are processing your request" msgid "Hold tight, we are processing your request"
msgstr "Bitte warten - wir verbeiten Deine Anfrage gerade" msgstr "Bitte warten - wir bearbeiten Deine Anfrage gerade"
msgid "Some problem encountered. Please try again later." msgid "Some problem encountered. Please try again later."
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal." msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."

View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-09-24 18:12
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hosting', '0042_hostingorder_subscription_id'),
]
operations = [
migrations.CreateModel(
name='VMDetail',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vm_id', models.IntegerField(default=0)),
('disk_size', models.FloatField(default=0.0)),
('cores', models.FloatField(default=0.0)),
('memory', models.FloatField(default=0.0)),
('configuration', models.CharField(default='', max_length=25)),
('ipv4', models.TextField(default='')),
('ipv6', models.TextField(default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('terminated_at', models.DateTimeField(null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -159,3 +159,16 @@ class HostingBill(AssignPermissionsMixin, models.Model):
instance = cls.objects.create(customer=customer, instance = cls.objects.create(customer=customer,
billing_address=billing_address) billing_address=billing_address)
return instance return instance
class VMDetail(models.Model):
user = models.ForeignKey(CustomUser)
vm_id = models.IntegerField(default=0)
disk_size = models.FloatField(default=0.0)
cores = models.FloatField(default=0.0)
memory = models.FloatField(default=0.0)
configuration = models.CharField(default='', max_length=25)
ipv4 = models.TextField(default='')
ipv6 = models.TextField(default='')
created_at = models.DateTimeField(auto_now_add=True)
terminated_at = models.DateTimeField(null=True)

View file

@ -870,3 +870,41 @@ a.list-group-item-danger.active:focus {
.panel-danger > .panel-heading .badge { .panel-danger > .panel-heading .badge {
background-color: #eb4d5c; background-color: #eb4d5c;
} }
.checkmark {
display: inline-block;
}
.checkmark:after {
/*Add another block-level blank space*/
content: '';
display: block;
/*Make it a small rectangle so the border will create an L-shape*/
width: 25px;
height: 60px;
/*Add a white border on the bottom and left, creating that 'L' */
border: solid #777;
border-width: 0 3px 3px 0;
/*Rotate the L 45 degrees to turn it into a checkmark*/
transform: rotate(45deg);
}
.closemark {
display: inline-block;
width: 50px;
height: 50px;
position: relative;
}
.closemark:before, .closemark:after {
position: absolute;
left: 25px;
content: ' ';
height: 50px;
width: 2px;
background-color: #777;
}
.closemark:before {
transform: rotate(45deg);
}
.closemark:after {
transform: rotate(-45deg);
}

View file

@ -79,7 +79,6 @@ $(document).ready(function() {
$('html,body').scrollTop(scrollmem); $('html,body').scrollTop(scrollmem);
}); });
$('.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 () {
$('#btn-create-vm').prop('disabled', true); $('#btn-create-vm').prop('disabled', true);
@ -90,26 +89,28 @@ $(document).ready(function() {
success: function (data) { success: function (data) {
if (data.status === true) { if (data.status === true) {
fa_icon = $('.modal-icon > .fa'); fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-check'); fa_icon.attr('class', 'checkmark');
$('.modal-header > .close').attr('class', 'close'); // $('.modal-header > .close').removeClass('hidden');
$('#createvm-modal-title').text(data.msg_title); $('#createvm-modal-title').text(data.msg_title);
$('#createvm-modal-body').text(data.msg_body); $('#createvm-modal-body').text(data.msg_body);
$('#createvm-modal').on('hidden.bs.modal', function () { $('#createvm-modal-done-btn')
window.location = data.redirect; .attr('href', data.redirect)
}) .removeClass('hide');
} }
}, },
error: function (xmlhttprequest, textstatus, message) { error: function (xmlhttprequest, textstatus, message) {
fa_icon = $('.modal-icon > .fa'); fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-times'); fa_icon.attr('class', 'fa fa-close');
$('.modal-header > .close').attr('class', 'close');
$('.modal-text').addClass('hide');
if (typeof(create_vm_error_message) !== 'undefined') { if (typeof(create_vm_error_message) !== 'undefined') {
$('#createvm-modal-title').text(create_vm_error_message); $('#createvm-modal-text').text(create_vm_error_message);
} }
$('#btn-create-vm').prop('disabled', false); $('#btn-create-vm').prop('disabled', false);
$('#createvm-modal-close-btn').removeClass('hide');
} }
}); });
return false; return false;
}); });
$('#createvm-modal').on('hidden.bs.modal', function () {
$(this).find('.modal-footer .btn').addClass('hide');
})
}); });

View file

@ -42,7 +42,9 @@
<p> <p>
<strong>{% trans "Status" %}: </strong> <strong>{% trans "Status" %}: </strong>
<strong> <strong>
{% if order.status == 'Approved' %} {% if vm.terminated_at %}
<span class="vm-color-failed">{% trans "Terminated" %}</span>
{% elif order.status == 'Approved' %}
<span class="vm-color-online">{% trans "Approved" %}</span> <span class="vm-color-online">{% trans "Approved" %}</span>
{% else %} {% else %}
<span class="vm-status-failed">{% trans "Declined" %}</span> <span class="vm-status-failed">{% trans "Declined" %}</span>
@ -160,22 +162,19 @@
<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 hidden" data-dismiss="modal"
aria-label="create-vm-close">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"> <div class="modal-icon">
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i> <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">{% trans "Processing..." %}</span> <span class="sr-only">{% trans "Processing..." %}</span>
</div> </div>
<h4 class="modal-title" id="createvm-modal-title"> <h4 class="modal-title" id="createvm-modal-title"></h4>
</h4>
<div class="modal-text" id="createvm-modal-body"> <div class="modal-text" id="createvm-modal-body">
{% trans "Hold tight, we are processing your request" %} {% trans "Hold tight, we are processing your request" %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a id="createvm-modal-done-btn" class="btn btn-danger btn-ok btn-wide hide" href="{% url 'hosting:virtual_machines' %}">{% trans "OK" %}</a>
<button id="createvm-modal-close-btn" type="button" class="btn btn-danger btn-ok btn-wide hide" data-dismiss="modal" aria-label="create-vm-close">{% trans "Close" %}</button>
</div> </div>
</div> </div>
</div> </div>
@ -202,4 +201,4 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
<script src="{% static 'hosting/js/html2pdf.js' %}"></script> <script src="{% static 'hosting/js/html2pdf.js' %}"></script>
<script src="{% static 'hosting/js/order.js' %}"></script> <script src="{% static 'hosting/js/order.js' %}"></script>
{% endblock js_extra %} {% endblock js_extra %}

View file

@ -109,7 +109,7 @@
<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>
@ -123,8 +123,9 @@
<div class="modal-header"> <div class="modal-header">
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"><i class="fa fa-check" aria-hidden="true"></i></div> <div class="modal-icon"><i class="checkmark" 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> <h4 class="modal-title"></h4>
<div class="modal-text" id="ModalLabel">{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine <strong>{{machine_name}}</strong> is successfully terminated!{% endblocktrans %}</div>
<div class="modal-footer"> <div class="modal-footer">
<a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a> <a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a>
</div> </div>

View file

@ -1,6 +1,7 @@
import json import json
import logging import logging
import uuid import uuid
from datetime import datetime
from time import sleep from time import sleep
from django import forms from django import forms
@ -47,10 +48,11 @@ from utils.views import (
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
from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey from .models import (
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail
)
from datacenterlight.models import VMTemplate from datacenterlight.models import VMTemplate
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \ CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \
@ -690,25 +692,29 @@ class OrdersHostingDetailView(LoginRequiredMixin,
if obj is not None: if obj is not None:
# invoice for previous order # invoice for previous order
try: try:
manager = OpenNebulaManager( vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
email=owner.email, password=owner.password context['vm'] = vm_detail.__dict__
) except VMDetail.DoesNotExist:
vm = manager.get_vm(obj.vm_id) try:
context['vm'] = VirtualMachineSerializer(vm).data manager = OpenNebulaManager(
except WrongIdError: email=owner.email, password=owner.password
messages.error( )
self.request, vm = manager.get_vm(obj.vm_id)
_('The VM you are looking for is unavailable at the ' context['vm'] = VirtualMachineSerializer(vm).data
'moment. Please contact Data Center Light support.') except WrongIdError:
) messages.error(
self.kwargs['error'] = 'WrongIdError' self.request,
context['error'] = 'WrongIdError' _('The VM you are looking for is unavailable at the '
except ConnectionRefusedError: 'moment. Please contact Data Center Light support.')
messages.error( )
self.request, self.kwargs['error'] = 'WrongIdError'
_('In order to create a VM, you need to create/upload ' context['error'] = 'WrongIdError'
'your SSH KEY first.') except ConnectionRefusedError:
) messages.error(
self.request,
_('In order to create a VM, you need to create/upload '
'your SSH KEY first.')
)
elif not card_details.get('response_object'): elif not card_details.get('response_object'):
# new order, failed to get card details # new order, failed to get card details
context['failed_payment'] = True context['failed_payment'] = True
@ -1055,6 +1061,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
except WrongIdError: except WrongIdError:
response['status'] = True response['status'] = True
response['text'] = ugettext('Terminated') response['text'] = ugettext('Terminated')
vm_detail_obj = VMDetail.objects.filter(
vm_id=opennebula_vm_id).first()
vm_detail_obj.terminated_at = datetime.utcnow()
vm_detail_obj.save()
break break
except BaseException: except BaseException:
break break

View file

@ -1,4 +1,10 @@
from hosting.models import UserHostingKey import logging
from oca.pool import WrongIdError
from hosting.models import UserHostingKey, VMDetail
from opennebula_api.serializers import VirtualMachineSerializer
logger = logging.getLogger(__name__)
def get_all_public_keys(customer): def get_all_public_keys(customer):
@ -11,6 +17,38 @@ def get_all_public_keys(customer):
"public_key", flat=True) "public_key", flat=True)
def get_or_create_vm_detail(user, manager, vm_id):
"""
Returns VMDetail object related to given vm_id. Creates the object
if it does not exist
:param vm_id: The ID of the VM which should be greater than 0.
:param user: The CustomUser object that owns this VM
:param manager: The OpenNebulaManager object
:return: The VMDetail object. None if vm_id is less than or equal to 0.
Also, for the cases where the VMDetail does not exist and we can not
fetch data about the VM from OpenNebula, the function returns None
"""
if vm_id <= 0:
return None
try:
vm_detail_obj = VMDetail.objects.get(vm_id=vm_id)
except VMDetail.DoesNotExist:
try:
vm_obj = manager.get_vm(vm_id)
except (WrongIdError, ConnectionRefusedError) as e:
logger.error(str(e))
return None
vm = VirtualMachineSerializer(vm_obj).data
vm_detail_obj = VMDetail.objects.create(
user=user, vm_id=vm_id, disk_size=vm['disk_size'],
cores=vm['cores'], memory=vm['memory'],
configuration=vm['configuration'], ipv4=vm['ipv4'],
ipv6=vm['ipv6']
)
return vm_detail_obj
def get_vm_price(cpu, memory, disk_size): def get_vm_price(cpu, memory, disk_size):
""" """
A helper function that computes price of a VM from given cpu, ram and A helper function that computes price of a VM from given cpu, ram and