diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index dedd3f84..3c7869a1 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -188,13 +188,13 @@ msgid "All Rights Reserved" msgstr "Alle Rechte vorbehalten" msgid "Toggle navigation" -msgstr "Konfiguration" +msgstr "Umschalten" msgid "Why Data Center Light?" msgstr "Warum Data Center Light?" msgid "Login" -msgstr "" +msgstr "Anmelden" msgid "Dashboard" msgstr "" diff --git a/datacenterlight/static/datacenterlight/js/main.js b/datacenterlight/static/datacenterlight/js/main.js index c508d76d..ab37a68b 100644 --- a/datacenterlight/static/datacenterlight/js/main.js +++ b/datacenterlight/static/datacenterlight/js/main.js @@ -147,7 +147,6 @@ function _fetchPricing() { Object.keys(cardPricing).map(function(element) { - //$('#'+cardPricing[element].id).val(cardPricing[element].value); $('input[name=' + element + ']').val(cardPricing[element].value); }); _calcPricing(); diff --git a/datacenterlight/templates/datacenterlight/calculator_form.html b/datacenterlight/templates/datacenterlight/calculator_form.html index cdba4809..b5bac1f9 100644 --- a/datacenterlight/templates/datacenterlight/calculator_form.html +++ b/datacenterlight/templates/datacenterlight/calculator_form.html @@ -26,9 +26,9 @@
{% for message in messages %} {% if 'cores' in message.tags %} - + {% endif %} {% endfor %}
diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po index 37b29cbd..5231b04d 100644 --- a/hosting/locale/de/LC_MESSAGES/django.po +++ b/hosting/locale/de/LC_MESSAGES/django.po @@ -110,6 +110,27 @@ msgstr "vorherige" msgid "next" msgstr "nächste" +msgid "Month" +msgstr "Monat" + +msgid "VAT included" +msgstr "MwSt. inklusive" + +msgid "Please enter a value in range 1 - 48." +msgstr "Bitte gib einen Wert von 1 bis 48 ein." + +msgid "Please enter a value in range 2 - 200." +msgstr "Bitte gib einen Wert von 2 bis 200 ein." + +msgid "Please enter a value in range 10 - 2000." +msgstr "Bitte gib einen Wert von 10 bis 200 ein." + +msgid "GB Storage (SSD)" +msgstr "GB Storage (SSD)" + +msgid "Continue" +msgstr "Weiter" + msgid "SSH Key" msgstr "SSH Key" @@ -149,30 +170,12 @@ msgstr "Hast Du bereits ein Benutzerkonto?" msgid "Login" msgstr "Anmelden" -msgid "New Virtual Machine" -msgstr "Neue virtuelle Maschine" - -msgid "Step 1. Select VM Template:" -msgstr "Wähle eine Vorlage" - -msgid "Step2. Select VM Configuration" -msgstr "Wähle eine Konfiguration" - -msgid "Price " -msgstr "Preis" - -msgid "CHF/Month" -msgstr "CHF/Monat" - -msgid "Start VM" -msgstr "VM jetzt starten" +msgid "Create VM" +msgstr "VM erstellen" msgid "My Dashboard" msgstr "Mein Dashboard" -msgid "Create VM" -msgstr "VM erstellen" - msgid "My VMs" msgstr "Meine VMs" @@ -286,10 +289,10 @@ msgstr "" "%(base_url)s%(my_virtual_machines_url)s\n" msgid "Toggle navigation" -msgstr "Konfiguration" +msgstr "Umschalten" msgid "Dashboard" -msgstr "Mein Dashboard" +msgstr "Dashboard" msgid "Logout" msgstr "Abmelden" @@ -664,6 +667,15 @@ msgstr "" "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " "auf sie zugreifen kannst." +msgid "Invalid number of cores" +msgstr "Ungültige Anzahle CPU-Kerne" + +msgid "Invalid RAM size" +msgstr "Ungültige RAM-Grösse" + +msgid "Invalid storage size" +msgstr "Ungültige Speicher-Grösse" + msgid "" "We could not find the requested VM. Please " "contact Data Center Light Support." @@ -676,6 +688,27 @@ msgstr "VM Kündigung" msgid "VM %(VM_ID)s terminated successfully" msgstr "VM %(VM_ID)s erfolgreich beendet" +#~ msgid "days" +#~ msgstr "tage" + +#~ msgid "New Virtual Machine" +#~ msgstr "Neue virtuelle Maschine" + +#~ msgid "Step 1. Select VM Template:" +#~ msgstr "Wähle eine Vorlage" + +#~ msgid "Step2. Select VM Configuration" +#~ msgstr "Wähle eine Konfiguration" + +#~ msgid "Price " +#~ msgstr "Preis" + +#~ msgid "CHF/Month" +#~ msgstr "CHF/Monat" + +#~ msgid "Start VM" +#~ msgstr "VM jetzt starten" + #~ msgid "Finish Configuration" #~ msgstr "Konfiguration beenden" diff --git a/hosting/static/hosting/css/price_calculator.css b/hosting/static/hosting/css/price_calculator.css new file mode 100644 index 00000000..24624f10 --- /dev/null +++ b/hosting/static/hosting/css/price_calculator.css @@ -0,0 +1,237 @@ +/* Create VM calculator */ + +.price-calc-section { + padding: 80px 40px !important; +} + +@media (max-width: 768px) { + .price-calc-section { + margin-top: 40px; + } +} + +.price-calc-section .text { + width: 50%; +} + +.price-calc-section .text .section-heading { + font-size: 48px; + line-height: 48px; + padding-bottom: 27px; + color: #3a3a3a; + letter-spacing: 1px; + position: relative; + text-align: right; +} + +.price-calc-section .text .description { + font-size: 20px; + text-align: right; +} + +.price-calc-section .text .section-heading::before { + content: ""; + position: absolute; + bottom: 0; + background: #29427A; + height: 7px; + width: 70px; + right: 0; +} + +.price-calc-section .card { + width: 50%; + margin: 0 auto; + background: #fff; + box-shadow: 1px 3px 6px 2px rgba(0, 0, 0, 0.2); + padding-bottom: 30px; + text-align: center; + max-width: 320px; + position: relative; +} + +@media (min-width: 768px) { + .price-calc-section .card { + margin-left: 0; + } +} + +.price-calc-section .landing { + width: 100% !important; +} + +.no-padding { + padding: 0 !important; +} + +.price-calc-section .card .img-beta { + position: absolute; + top: 5px; + width: 60px; + left: 3px; +} + +.price-calc-section .card .title { + padding: 15px 40px; +} + +.price-calc-section .card .title h3 { + /*font-family: 'Lato', sans-serif;*/ + font-weight: normal; +} + +.price-calc-section .card .price { + background: #5A74AF; + padding: 22px; + color: #fff; + font-size: 32px; +} + +.price-calc-section .card .price .price-text { + font-size: 14px; +} + +.price-calc-section .card .description { + padding: 7px 8px 2px; + position: relative; + display: flex; + justify-content: space-around !important; + align-items: center !important; +} + +.price-calc-section .card .description span { + font-size: 14px; + margin-left: 5px; + /* margin-left: 0px; */ + /* justify-self: start; */ + width: 29%; + text-align: left; + line-height: 16px; + /* font-weight: normal; */ +} + +.price-calc-section .card .description .select-number{ + font-size: 16px; + text-align: center; + width: 85px; +} + +.price-calc-section .card .description i { + color: #29427a; + cursor: pointer; + font-size: 20px; + border: 1px solid #ccc; + padding: 5px 6px 3px; + border-radius: 5px; +} + +.price-calc-section .card .description .left { + margin-right: 7px; +} + +.price-calc-section .card .description .right { + margin-left: 7px; +} + +.price-calc-section .card .descriptions { + padding: 10px; +} + +.price-calc-section .card .description p { + margin: 0; +} + +.price-calc-section .card .btn { + margin-top: 15px; + font-size: 20px; + width: 150px; + border: none; +} + +.price-calc-section .card .select-configuration select { + outline: none; + background: #fff; + border-color: #d0d0d0; + height: 32px; + width: 150px; + text-align: center; + font-size: 14px; + margin-left: 10px; + padding: 6px; + border-radius: 4px; +} + +.price-calc-section .card .check-ip { + font-size: 18px; +} + +.price-calc-section .card .justify-center { + justify-content: center !important; +} + +.price-calc-section .card .description.input label { + font-size: 15px; + font-weight: 700; + /*font-weight: 800;*/ + /*font-family: 'Lato';*/ + margin-bottom: 0; + width: 40px; +} + + +/*Changed class****.price-calc-section .card .description.input input*/ + +.price-calc-section .card .description input { + width: 200px; + font-size: 14px; + text-align: left; + padding: 4px 10px; + border-radius: 4px; + border: 1px solid #d0d0d0; + background: #fff; + margin-left: 10px; +} + +.price-calc-section .card .check-ip input[type=checkbox] { + font-size: 17px; + margin: 0 8px; +} + +.price-calc-section .help-block.with-errors { + text-align: center; + margin: 0 0; + padding: 0 0 5px; +} +.price-calc-section .help-block.with-errors ul { + margin-bottom: 0; +} + +.price-calc-section .form-group { + margin: 0; + position: relative; +} + +.price-calc-section .form-group:after { + content: ' '; + display: block; + position: absolute; + bottom: 0; + left: 18%; + z-index: 20; + height: 1px; + width: 65%; + background: rgba(128, 128, 128, 0.2); +} + +.price-calc-section .btn-primary { + background: #29427A; + border-color: #29427A; + color: #fff; + width: auto; +} + +@media(min-width: 768px) { + .create-vm-container { + padding-top: 120px; + } +} diff --git a/hosting/static/hosting/js/createvm.js b/hosting/static/hosting/js/createvm.js index 53646b40..726513ad 100644 --- a/hosting/static/hosting/js/createvm.js +++ b/hosting/static/hosting/js/createvm.js @@ -1,73 +1,75 @@ (function($){ "use strict"; // Start of use strict - - - $(window).load(function(){ - - - }); - - $(document).ready(function(){ - _initOs(); - - }); - - $(window).resize(function(){ - - - }); - - - function _initOs(){ - - - $('.os-circle').click(function(event){ - $('.os-circle').removeClass('active'); - $(this).addClass('active'); - - var idTemplate = $(this).data('id'); - $('input[name=vm_template_id]').val(idTemplate); - }); - $('.config-box').click(function(event){ - $('.config-box').removeClass('active'); - $(this).addClass('active'); - var idConfig = $(this).data('id'); - var price = $(this).data('price'); - $('input[name=configuration]').val(idConfig); - $('.container-button').fadeIn(); - $('#priceValue').text(price); - }); - - $('.owl-carousel').owlCarousel({ - items:4, - nav: true, - margin:30, - responsiveClass:true, - navText: ['', ''], - responsive:{ - 0:{ - items:1, - nav:true - }, - 600:{ - items:2, - nav:true - }, - 768:{ - items:3, - nav:true - }, - 990:{ - items:4, - nav:true - } + var cardPricing = { + 'cpu': { + 'id': 'coreValue', + 'value': 1, + 'min': 1, + 'max': 48, + 'interval': 1 + }, + 'ram': { + 'id': 'ramValue', + 'value': 2, + 'min': 2, + 'max': 200, + 'interval': 1 + }, + 'storage': { + 'id': 'storageValue', + 'value': 10, + 'min': 10, + 'max': 2000, + 'interval': 10 } - }); - } - - - -})(jQuery); + }; + + function _initPricing() { + _fetchPricing(); + + $('.fa-minus.left').click(function(event) { + var data = $(this).data('minus'); + + if (cardPricing[data].value > cardPricing[data].min) { + cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval; + } + _fetchPricing(); + }); + $('.fa-plus.right').click(function(event) { + var data = $(this).data('plus'); + if (cardPricing[data].value < cardPricing[data].max) { + cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval; + } + _fetchPricing(); + }); + + $('.input-price').change(function() { + var data = $(this).attr("name"); + cardPricing[data].value = $('input[name=' + data + ']').val(); + _fetchPricing(); + }); + } + + function _fetchPricing() { + Object.keys(cardPricing).map(function(element) { + $('input[name=' + element + ']').val(cardPricing[element].value); + }); + _calcPricing(); + } + + 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); + } + + $(document).ready(function() { + _initPricing(); + }); + +})(jQuery); diff --git a/hosting/static/hosting/js/createvm_old.js b/hosting/static/hosting/js/createvm_old.js new file mode 100644 index 00000000..53646b40 --- /dev/null +++ b/hosting/static/hosting/js/createvm_old.js @@ -0,0 +1,73 @@ +(function($){ + "use strict"; // Start of use strict + + + $(window).load(function(){ + + + }); + + $(document).ready(function(){ + _initOs(); + + }); + + $(window).resize(function(){ + + + }); + + + + function _initOs(){ + + + $('.os-circle').click(function(event){ + $('.os-circle').removeClass('active'); + $(this).addClass('active'); + + var idTemplate = $(this).data('id'); + $('input[name=vm_template_id]').val(idTemplate); + }); + $('.config-box').click(function(event){ + $('.config-box').removeClass('active'); + $(this).addClass('active'); + var idConfig = $(this).data('id'); + var price = $(this).data('price'); + $('input[name=configuration]').val(idConfig); + $('.container-button').fadeIn(); + $('#priceValue').text(price); + }); + + $('.owl-carousel').owlCarousel({ + items:4, + nav: true, + margin:30, + responsiveClass:true, + navText: ['', ''], + responsive:{ + 0:{ + items:1, + nav:true + }, + 600:{ + items:2, + nav:true + }, + 768:{ + items:3, + nav:true + }, + 990:{ + items:4, + nav:true + } + } + }); + } + + + +})(jQuery); + + diff --git a/hosting/templates/hosting/base_short.html b/hosting/templates/hosting/base_short.html index 3906133b..5cf0953c 100644 --- a/hosting/templates/hosting/base_short.html +++ b/hosting/templates/hosting/base_short.html @@ -25,6 +25,7 @@ + {% block css_extra %} {% endblock css_extra %} @@ -33,9 +34,6 @@ - - - @@ -76,8 +74,8 @@ {% endif %} - - + + diff --git a/hosting/templates/hosting/calculator_form.html b/hosting/templates/hosting/calculator_form.html new file mode 100644 index 00000000..18bdd3cb --- /dev/null +++ b/hosting/templates/hosting/calculator_form.html @@ -0,0 +1,87 @@ +{% load staticfiles i18n%} +
+ {% csrf_token %} +
+ 15 + CHF/{% trans "Month" %} +
+ {% trans "VAT included" %} +
+
+
+
+
+ + + Core + +
+
+ {% for message in messages %} + {% if 'cores' in message.tags %} +
  • + {{ message|safe }} +
+ {% endif %} + {% endfor %} +
+
+
+
+ + + GB RAM + +
+
+ {% for message in messages %} + {% if 'memory' in message.tags %} +
  • + {{ message|safe }} +
+ {% endif %} + {% endfor %} +
+
+
+
+ + + {% trans "GB Storage (SSD)" %} + +
+
+ {% for message in messages %} + {% if 'storage' in message.tags %} +
  • + {{ message|safe }} +
+ {% endif %} + {% endfor %} +
+
+
+
+ + +
+
+ {% for message in messages %} + {% if 'cores' in message.tags %} +
  • + {{ message|safe }} +
+ {% endif %} + {% endfor %} +
+
+ +
+ +
diff --git a/hosting/templates/hosting/create_virtual_machine.html b/hosting/templates/hosting/create_virtual_machine.html index f876ad76..4ba5274e 100644 --- a/hosting/templates/hosting/create_virtual_machine.html +++ b/hosting/templates/hosting/create_virtual_machine.html @@ -1,70 +1,30 @@ {% extends "hosting/base_short.html" %} {% load staticfiles bootstrap3 i18n %} -{% block content %} -
-
-
-
-
- {% if messages %} -
- {% for message in messages %} +{% block content %} +
+
+
+
+

{% trans "Create VM" %}

+ {% if messages %} +
+ {% for message in messages %} {{ message }} - {% endfor %} -
- {% endif %} -
- {% if not error %} -
-

{% trans "New Virtual Machine"%}

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

{% trans "Step 1. Select VM Template:" %}

+ {% endfor %}
-
- - -
-
-

{% trans "Step2. Select VM Configuration" %}

-
-
- - -
-
-
- {% trans "Price " %}0{% trans "CHF/Month" %} -
- -
-
{% endif %} +
+
+
+
+
+
+ {% include "hosting/calculator_form.html" %} +
+
+
-
- -{%endblock%} +{%endblock%} \ No newline at end of file diff --git a/hosting/views.py b/hosting/views.py index c736a576..7727c1a3 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -2,10 +2,12 @@ import json import logging import uuid +from django import forms from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin 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 @@ -28,7 +30,7 @@ from datacenterlight.tasks import create_vm_task from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import VirtualMachineSerializer, \ - VirtualMachineTemplateSerializer + VirtualMachineTemplateSerializer, VMTemplateSerializer from utils.forms import BillingAddressForm, PasswordResetRequestForm, \ UserBillingAddressForm from utils.mailer import BaseEmail @@ -39,6 +41,8 @@ from .forms import HostingUserSignupForm, HostingUserLoginForm, \ UserHostingKeyForm, generate_ssh_key_name from .mixins import ProcessVMSelectionMixin from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey +from datacenterlight.models import VMTemplate + logger = logging.getLogger(__name__) @@ -866,48 +870,80 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View): template_name = "hosting/create_virtual_machine.html" login_url = reverse_lazy('hosting:login') - def get(self, request, *args, **kwargs): + def validate_cores(self, value): + if (value > 48) or (value < 1): + raise ValidationError(_('Invalid number of cores')) + def validate_memory(self, value): + if (value > 200) or (value < 2): + raise ValidationError(_('Invalid RAM size')) + + def validate_storage(self, value): + if (value > 2000) or (value < 10): + raise ValidationError(_('Invalid storage size')) + + def get(self, request, *args, **kwargs): if not UserHostingKey.objects.filter(user=self.request.user).exists(): messages.success( request, _( - 'In order to create a VM, you need to create/upload your SSH KEY first.') + 'In order to create a VM, you need to ' + 'create/upload your SSH KEY first.' + ) ) return HttpResponseRedirect(reverse('hosting:ssh_keys')) - - try: - manager = OpenNebulaManager() - templates = manager.get_templates() - configuration_options = HostingPlan.get_serialized_configs() - - context = { - 'templates': VirtualMachineTemplateSerializer(templates, - many=True).data, - 'configuration_options': configuration_options, - } - except: - messages.error( - request, - 'We could not load the VM templates due to a backend connection \ - error. Please try again in a few minutes' - ) - context = { - 'error': 'connection' - } - + context = {'templates': VMTemplate.objects.all()} return render(request, self.template_name, context) def post(self, request): - manager = OpenNebulaManager() - template_id = request.POST.get('vm_template_id') - template = manager.get_template(template_id) - configuration_id = int(request.POST.get('configuration')) - configuration = HostingPlan.objects.get(id=configuration_id) - request.session['template'] = VirtualMachineTemplateSerializer( - template).data + cores = request.POST.get('cpu') + cores_field = forms.IntegerField(validators=[self.validate_cores]) + memory = request.POST.get('ram') + 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() + template_data = VMTemplateSerializer(template).data - request.session['specs'] = configuration.serialize() + try: + cores = cores_field.clean(cores) + except ValidationError as err: + msg = '{} : {}.'.format(cores, str(err)) + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='cores') + return HttpResponseRedirect( + reverse('datacenterlight:index') + "#order_form") + + try: + memory = memory_field.clean(memory) + except ValidationError as err: + msg = '{} : {}.'.format(memory, str(err)) + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='memory') + return HttpResponseRedirect( + reverse('datacenterlight:index') + "#order_form") + + try: + storage = storage_field.clean(storage) + except ValidationError as err: + msg = '{} : {}.'.format(storage, str(err)) + messages.add_message(self.request, messages.ERROR, msg, + extra_tags='storage') + return HttpResponseRedirect( + reverse('datacenterlight:index') + "#order_form") + + specs = { + 'cpu': cores, + 'memory': memory, + 'disk_size': storage, + 'price': price + } + + request.session['specs'] = specs + request.session['template'] = template_data return redirect(reverse('hosting:payment'))