diff --git a/.gitignore b/.gitignore index 46bfbf54..cfef66a1 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ secret-key .env *.mo *.log +*.sql diff --git a/Changelog b/Changelog index 7ba5e634..58835832 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,11 @@ -Pre-changelog: 1.2.3 2017-09-20 +Next: + * #3764: [hosting] Show cancelled VMs' invoices + * #3736: [dcl] Refactor the place where we compute the VM price + * #3730: [dcl] Refactor price parameter passed in the DCL flow + * #3807: [dcl] Remove PricingView as it is no more used + * #3813: [hosting] JS error in create ssh key page + +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 * #3628: [dcl] on hosting, VM is created at credit card info submit @@ -6,7 +13,9 @@ Pre-changelog: 1.2.3 2017-09-20 * #3786: [hosting] Redesigned the hosting invoice and order-confirmation page * #3728: [hosting] VM Termination animation added * #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 diff --git a/datacenterlight/static/datacenterlight/css/landing-page.css b/datacenterlight/static/datacenterlight/css/landing-page.css index 7c2fb564..0d0729fb 100755 --- a/datacenterlight/static/datacenterlight/css/landing-page.css +++ b/datacenterlight/static/datacenterlight/css/landing-page.css @@ -1655,3 +1655,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); +} diff --git a/datacenterlight/static/datacenterlight/js/main.js b/datacenterlight/static/datacenterlight/js/main.js index ab37a68b..dd074397 100644 --- a/datacenterlight/static/datacenterlight/js/main.js +++ b/datacenterlight/static/datacenterlight/js/main.js @@ -155,9 +155,7 @@ function _calcPricing() { var total = (cardPricing['cpu'].value * 5) + (2 * cardPricing['ram'].value) + (0.6 * cardPricing['storage'].value); total = parseFloat(total.toFixed(2)); - $("#total").text(total); - $('input[name=total]').val(total); } function form_success() { @@ -191,4 +189,4 @@ }); }) } -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 1335869b..7d589570 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -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 @@ -52,7 +52,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, stripe_customer_id, billing_address_data, billing_address_id, charge, cc_details): - logger.debug("Running create_vm_task on {}".format(current_task.request.hostname)) + logger.debug( + "Running create_vm_task on {}".format(current_task.request.hostname)) vm_id = None try: final_price = specs.get('price') @@ -142,9 +143,10 @@ def create_vm_task(self, vm_template_id, user, specs, template, email.send() if 'pass' in user: - lang = 'en-us' + lang = 'en-us' if user.get('language') is not None: - logger.debug("Language is set to {}".format(user.get('language'))) + logger.debug( + "Language is set to {}".format(user.get('language'))) lang = user.get('language') translation.activate(lang) # Send notification to the user as soon as VM has been booked @@ -174,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 diff --git a/datacenterlight/templates/datacenterlight/beta_success.html b/datacenterlight/templates/datacenterlight/beta_success.html index 2512a05c..60df607c 100644 --- a/datacenterlight/templates/datacenterlight/beta_success.html +++ b/datacenterlight/templates/datacenterlight/beta_success.html @@ -8,7 +8,7 @@ diff --git a/datacenterlight/templates/datacenterlight/calculator_form.html b/datacenterlight/templates/datacenterlight/calculator_form.html index b5bac1f9..9a0fcaa3 100644 --- a/datacenterlight/templates/datacenterlight/calculator_form.html +++ b/datacenterlight/templates/datacenterlight/calculator_form.html @@ -77,7 +77,6 @@ {% endfor %} - diff --git a/datacenterlight/templates/datacenterlight/pricing.html b/datacenterlight/templates/datacenterlight/pricing.html deleted file mode 100644 index 0724a6ce..00000000 --- a/datacenterlight/templates/datacenterlight/pricing.html +++ /dev/null @@ -1,96 +0,0 @@ -{% extends "datacenterlight/base.html" %} -{% load staticfiles i18n%} -{% get_current_language as LANGUAGE_CODE %} - -{% block content %} -
- -
-

{% trans "We are cutting down the costs significantly!" %}

-
- -
- -
-
- -
-
- {% csrf_token %} - -
-

{% trans "VM hosting" %}

-
-
- 15 - CHF -
-

{% trans "VAT included" %}

-
-
-
-
-

{% trans "Hosted in Switzerland" %}

-
-
- - - Core - -
-
- - - GB RAM - -
-
- - - {% trans "GB Storage (SSD)" %} - -
- - - -
- - -
- - - - - -
- - -
-
-
- -
-

{% trans "Simple and affordable: Try our virtual machine with featherlight price." %}

- -
-

{% trans "Our VMs are hosted in Glarus, Switzerland, and our website is currently running in BETA mode. If you want more information that you did not find on our website, or if your order is more detailed, or if you encounter any technical hiccups, please contact us at support@datacenterlight.ch, our team will get in touch with you asap." %}

-
-
-
-{% endblock %} - - - diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py index c34c56ba..edde2db8 100644 --- a/datacenterlight/tests.py +++ b/datacenterlight/tests.py @@ -12,6 +12,7 @@ from datacenterlight.models import VMTemplate from datacenterlight.tasks import create_vm_task from membership.models import StripeCustomer from opennebula_api.serializers import VMTemplateSerializer +from utils.hosting_utils import get_vm_price from utils.models import BillingAddress from utils.stripe_utils import StripeUtils @@ -94,12 +95,11 @@ class CeleryTaskTestCase(TestCase): cpu = specs.get('cpu') memory = specs.get('memory') disk_size = specs.get('disk_size') - amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6) - plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format( - cpu=cpu, - memory=memory, - disk_size=disk_size) - + amount_to_be_charged = get_vm_price(cpu=cpu, memory=memory, + disk_size=disk_size) + plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu, + memory=memory, + disk_size=disk_size) stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, ram=memory, ssd=disk_size, diff --git a/datacenterlight/urls.py b/datacenterlight/urls.py index 772e691d..a8d8f49d 100644 --- a/datacenterlight/urls.py +++ b/datacenterlight/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url from .views import IndexView, BetaProgramView, LandingProgramView, \ - BetaAccessView, PricingView, SuccessView, \ + BetaAccessView, SuccessView, \ PaymentOrderView, OrderConfirmationView, \ WhyDataCenterLightView, ContactUsView @@ -15,7 +15,6 @@ urlpatterns = [ name='whydatacenterlight'), url(r'^beta-program/?$', BetaProgramView.as_view(), name='beta'), url(r'^landing/?$', LandingProgramView.as_view(), name='landing'), - url(r'^pricing/?$', PricingView.as_view(), name='pricing'), url(r'^payment/?$', PaymentOrderView.as_view(), name='payment'), url(r'^order-confirmation/?$', OrderConfirmationView.as_view(), name='order_confirmation'), diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 76d62076..7d3559dd 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -4,7 +4,6 @@ from django.contrib import messages from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect -from django.shortcuts import redirect from django.shortcuts import render from django.utils.translation import ugettext_lazy as _ from django.views.decorators.cache import cache_control @@ -13,10 +12,9 @@ from django.views.generic import FormView, CreateView, TemplateView, DetailView from datacenterlight.tasks import create_vm_task from hosting.models import HostingOrder from membership.models import CustomUser, StripeCustomer -from opennebula_api.models import OpenNebulaManager -from opennebula_api.serializers import VirtualMachineTemplateSerializer, \ - VMTemplateSerializer +from opennebula_api.serializers import VMTemplateSerializer from utils.forms import BillingAddressForm +from utils.hosting_utils import get_vm_price from utils.mailer import BaseEmail from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task @@ -88,56 +86,6 @@ class SuccessView(TemplateView): return render(request, self.template_name) -class PricingView(TemplateView): - template_name = "datacenterlight/pricing.html" - - def get(self, request, *args, **kwargs): - try: - manager = OpenNebulaManager() - templates = manager.get_templates() - - context = { - 'templates': VirtualMachineTemplateSerializer(templates, - many=True).data, - } - except: - messages.error(request, - 'We have a temporary problem to connect to our backend. \ - Please try again in a few minutes' - ) - context = { - 'error': 'connection' - } - - return render(request, self.template_name, context) - - def post(self, request): - - cores = request.POST.get('cpu') - memory = request.POST.get('ram') - storage = request.POST.get('storage') - price = request.POST.get('total') - - template_id = int(request.POST.get('config')) - manager = OpenNebulaManager() - template = manager.get_template(template_id) - - request.session['template'] = VirtualMachineTemplateSerializer( - template).data - - if not request.user.is_authenticated(): - request.session['next'] = reverse('hosting:payment') - - request.session['specs'] = { - 'cpu': cores, - 'memory': memory, - 'disk_size': storage, - 'price': price, - } - - return redirect(reverse('hosting:payment')) - - class BetaAccessView(FormView): template_name = "datacenterlight/beta_access.html" form_class = BetaAccessForm @@ -274,7 +222,6 @@ class IndexView(CreateView): memory_field = forms.IntegerField(validators=[self.validate_memory]) storage = request.POST.get('storage') storage_field = forms.IntegerField(validators=[self.validate_storage]) - price = request.POST.get('total') template_id = int(request.POST.get('config')) template = VMTemplate.objects.filter( opennebula_vm_template_id=template_id).first() @@ -334,7 +281,6 @@ class IndexView(CreateView): 'cpu': cores, 'memory': memory, 'disk_size': storage, - 'price': price } this_user = { @@ -534,12 +480,12 @@ class OrderConfirmationView(DetailView): cpu = specs.get('cpu') memory = specs.get('memory') disk_size = specs.get('disk_size') - amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6) - plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format( - cpu=cpu, - memory=memory, - disk_size=disk_size) - + amount_to_be_charged = get_vm_price(cpu=cpu, memory=memory, + disk_size=disk_size) + specs['price'] = amount_to_be_charged + plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu, + memory=memory, + disk_size=disk_size) stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, ram=memory, ssd=disk_size, diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po index d6b65f95..0cf50fd7 100644 --- a/hosting/locale/de/LC_MESSAGES/django.po +++ b/hosting/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-23 19:00+0530\n" +"POT-Creation-Date: 2017-09-24 12:34+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -306,6 +306,9 @@ msgstr "Registrieren" msgid "Forgot your password ? " msgstr "Passwort vergessen?" +msgid "Resend activation link" +msgstr "Aktivierungslink noch einmal senden" + msgid "Notifications" msgstr "Benachrichtigungen" @@ -385,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." diff --git a/hosting/migrations/0043_vmdetail.py b/hosting/migrations/0043_vmdetail.py new file mode 100644 index 00000000..66966233 --- /dev/null +++ b/hosting/migrations/0043_vmdetail.py @@ -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)), + ], + ), + ] diff --git a/hosting/models.py b/hosting/models.py index 478ed745..73c082bb 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -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) diff --git a/hosting/static/hosting/css/landing-page.css b/hosting/static/hosting/css/landing-page.css index ed8fb310..5275dd97 100644 --- a/hosting/static/hosting/css/landing-page.css +++ b/hosting/static/hosting/css/landing-page.css @@ -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); +} diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index db2621c1..01a58127 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -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'); + }) }); diff --git a/hosting/templates/hosting/choice_ssh_keys.html b/hosting/templates/hosting/choice_ssh_keys.html index 3a377388..87224156 100644 --- a/hosting/templates/hosting/choice_ssh_keys.html +++ b/hosting/templates/hosting/choice_ssh_keys.html @@ -47,20 +47,5 @@ window.location.href = '{{next_url}}'; {% endif %} - - - - - {%endblock%} diff --git a/hosting/templates/hosting/login.html b/hosting/templates/hosting/login.html index 9f18fda9..82056d2f 100644 --- a/hosting/templates/hosting/login.html +++ b/hosting/templates/hosting/login.html @@ -44,6 +44,8 @@ {% trans "Sign up"%} or {% trans "Forgot your password ? "%} + or
+ {% trans "Resend activation link"%} diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index daf4117d..c81a893f 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -44,7 +44,9 @@

{% trans "Status" %}: - {% if order.status == 'Approved' %} + {% if vm.terminated_at %} + {% trans "Terminated" %} + {% elif order.status == 'Approved' %} {% trans "Approved" %} {% else %} {% trans "Declined" %} @@ -102,14 +104,14 @@

- {% comment %} + {% if vm.created_at %} +

+ {% trans "Period" %}: + {{ vm.created_at|date:'Y/m/d' }} - {% if vm.terminated_at %}{{ vm.terminated_at|date:'Y/m/d' }}{% else %}{% now 'Y/m/d' %}{% endif %} +

+ {% endif %}

- {% trans "Period" %} - {{}} -

- {% endcomment %} -

- {% trans "Cores" %} + {% trans "Cores" %}: {% if vm.cores %} {{vm.cores|floatformat}} {% else %} @@ -117,11 +119,11 @@ {% endif %}

- {% trans "Memory" %} + {% trans "Memory" %}: {{vm.memory}} GB

- {% trans "Disk space" %} + {% trans "Disk space" %}: {{vm.disk_size}} GB

@@ -168,22 +170,19 @@

@@ -123,8 +123,9 @@