Merge upstream master
This commit is contained in:
commit
5a2a134070
14 changed files with 202 additions and 46 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -36,3 +36,4 @@ secret-key
|
|||
.env
|
||||
*.mo
|
||||
*.log
|
||||
*.sql
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
Next:
|
||||
* #3764: [hosting] Show cancelled VMs' invoices
|
||||
1.2.3: 2017-09-25
|
||||
* #3484: [dcl, hosting] Refactored account activation, password reset, VM order and cancellation email
|
||||
* #3731: [dcl, hosting] Added cdist ssh key handler
|
||||
|
@ -8,6 +10,7 @@
|
|||
* #3777: [hosting] Create new VM calculator added like dcl landing
|
||||
* #3781: [hosting] Resend activation mail
|
||||
* #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
|
||||
* Bugfix: [dcl, hosting] added host to celery error mails
|
||||
* Bugfix: [ungleich] Fixed wrong subdomain digitalglarus.ungleich.ch
|
||||
|
|
|
@ -1653,3 +1653,20 @@ a.list-group-item-danger.active:focus {
|
|||
.panel-danger > .panel-heading .badge {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ from hosting.models import HostingOrder, HostingBill
|
|||
from membership.models import StripeCustomer, CustomUser
|
||||
from opennebula_api.models import OpenNebulaManager
|
||||
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.mailer import BaseEmail
|
||||
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))
|
||||
if new_host is not None:
|
||||
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:
|
||||
public_keys = get_all_public_keys(custom_user)
|
||||
keys = [{'value': key, 'state': True} for key in
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<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>
|
||||
<p class="modal-text">{% trans "Thank you for your subscription! You will receive a confirmation mail from our team" %}</p>
|
||||
</div>
|
||||
|
|
|
@ -388,7 +388,7 @@ msgid "Processing..."
|
|||
msgstr "Abarbeitung..."
|
||||
|
||||
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."
|
||||
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
|
||||
|
|
34
hosting/migrations/0043_vmdetail.py
Normal file
34
hosting/migrations/0043_vmdetail.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -159,3 +159,16 @@ class HostingBill(AssignPermissionsMixin, models.Model):
|
|||
instance = cls.objects.create(customer=customer,
|
||||
billing_address=billing_address)
|
||||
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)
|
||||
|
|
|
@ -870,3 +870,41 @@ a.list-group-item-danger.active:focus {
|
|||
.panel-danger > .panel-heading .badge {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -79,7 +79,6 @@ $(document).ready(function() {
|
|||
$('html,body').scrollTop(scrollmem);
|
||||
});
|
||||
|
||||
$('.modal-text').removeClass('hide');
|
||||
var create_vm_form = $('#virtual_machine_create_form');
|
||||
create_vm_form.submit(function () {
|
||||
$('#btn-create-vm').prop('disabled', true);
|
||||
|
@ -90,26 +89,28 @@ $(document).ready(function() {
|
|||
success: function (data) {
|
||||
if (data.status === true) {
|
||||
fa_icon = $('.modal-icon > .fa');
|
||||
fa_icon.attr('class', 'fa fa-check');
|
||||
$('.modal-header > .close').attr('class', 'close');
|
||||
fa_icon.attr('class', 'checkmark');
|
||||
// $('.modal-header > .close').removeClass('hidden');
|
||||
$('#createvm-modal-title').text(data.msg_title);
|
||||
$('#createvm-modal-body').text(data.msg_body);
|
||||
$('#createvm-modal').on('hidden.bs.modal', function () {
|
||||
window.location = data.redirect;
|
||||
})
|
||||
$('#createvm-modal-done-btn')
|
||||
.attr('href', data.redirect)
|
||||
.removeClass('hide');
|
||||
}
|
||||
},
|
||||
error: function (xmlhttprequest, textstatus, message) {
|
||||
fa_icon = $('.modal-icon > .fa');
|
||||
fa_icon.attr('class', 'fa fa-times');
|
||||
$('.modal-header > .close').attr('class', 'close');
|
||||
$('.modal-text').addClass('hide');
|
||||
fa_icon.attr('class', 'fa fa-close');
|
||||
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);
|
||||
$('#createvm-modal-close-btn').removeClass('hide');
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
$('#createvm-modal').on('hidden.bs.modal', function () {
|
||||
$(this).find('.modal-footer .btn').addClass('hide');
|
||||
})
|
||||
});
|
||||
|
|
|
@ -42,7 +42,9 @@
|
|||
<p>
|
||||
<strong>{% trans "Status" %}: </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>
|
||||
{% else %}
|
||||
<span class="vm-status-failed">{% trans "Declined" %}</span>
|
||||
|
@ -160,22 +162,19 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close hidden" data-dismiss="modal"
|
||||
aria-label="create-vm-close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-icon">
|
||||
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
|
||||
<span class="sr-only">{% trans "Processing..." %}</span>
|
||||
</div>
|
||||
<h4 class="modal-title" id="createvm-modal-title">
|
||||
</h4>
|
||||
<h4 class="modal-title" id="createvm-modal-title"></h4>
|
||||
<div class="modal-text" id="createvm-modal-body">
|
||||
{% trans "Hold tight, we are processing your request" %}
|
||||
</div>
|
||||
<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>
|
||||
|
@ -202,4 +201,4 @@
|
|||
<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/order.js' %}"></script>
|
||||
{% endblock js_extra %}
|
||||
{% endblock js_extra %}
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
<p><strong>{{virtual_machine.name}}</strong></p>
|
||||
</div>
|
||||
<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>
|
||||
|
@ -123,8 +123,9 @@
|
|||
<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-icon"><i class="checkmark" aria-hidden="true"></i></div>
|
||||
<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">
|
||||
<a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from time import sleep
|
||||
|
||||
from django import forms
|
||||
|
@ -47,10 +48,11 @@ from utils.views import (
|
|||
from .forms import HostingUserSignupForm, HostingUserLoginForm, \
|
||||
UserHostingKeyForm, generate_ssh_key_name
|
||||
from .mixins import ProcessVMSelectionMixin
|
||||
from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey
|
||||
from .models import (
|
||||
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail
|
||||
)
|
||||
from datacenterlight.models import VMTemplate
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
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:
|
||||
# invoice for previous order
|
||||
try:
|
||||
manager = OpenNebulaManager(
|
||||
email=owner.email, password=owner.password
|
||||
)
|
||||
vm = manager.get_vm(obj.vm_id)
|
||||
context['vm'] = VirtualMachineSerializer(vm).data
|
||||
except WrongIdError:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('The VM you are looking for is unavailable at the '
|
||||
'moment. Please contact Data Center Light support.')
|
||||
)
|
||||
self.kwargs['error'] = 'WrongIdError'
|
||||
context['error'] = 'WrongIdError'
|
||||
except ConnectionRefusedError:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('In order to create a VM, you need to create/upload '
|
||||
'your SSH KEY first.')
|
||||
)
|
||||
vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
|
||||
context['vm'] = vm_detail.__dict__
|
||||
except VMDetail.DoesNotExist:
|
||||
try:
|
||||
manager = OpenNebulaManager(
|
||||
email=owner.email, password=owner.password
|
||||
)
|
||||
vm = manager.get_vm(obj.vm_id)
|
||||
context['vm'] = VirtualMachineSerializer(vm).data
|
||||
except WrongIdError:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('The VM you are looking for is unavailable at the '
|
||||
'moment. Please contact Data Center Light support.')
|
||||
)
|
||||
self.kwargs['error'] = 'WrongIdError'
|
||||
context['error'] = 'WrongIdError'
|
||||
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'):
|
||||
# new order, failed to get card details
|
||||
context['failed_payment'] = True
|
||||
|
@ -1055,6 +1061,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
except WrongIdError:
|
||||
response['status'] = True
|
||||
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
|
||||
except BaseException:
|
||||
break
|
||||
|
|
|
@ -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):
|
||||
|
@ -11,6 +17,38 @@ def get_all_public_keys(customer):
|
|||
"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):
|
||||
"""
|
||||
A helper function that computes price of a VM from given cpu, ram and
|
||||
|
|
Loading…
Reference in a new issue