Merged upstream master into task/3730/refactor_price_parameter

This commit is contained in:
PCoder 2017-09-24 04:20:41 +05:30
commit 39a2455817
18 changed files with 883 additions and 285 deletions

View file

@ -4,6 +4,8 @@ Pre-changelog: 1.2.3 2017-09-20
* #3628: [dcl] on hosting, VM is created at credit card info submit * #3628: [dcl] on hosting, VM is created at credit card info submit
* #3772: [dcl] Updated hosting app billing into monthly subscription and added new text and translations * #3772: [dcl] Updated hosting app billing into monthly subscription and added new text and translations
* #3786: [hosting] Redesigned the hosting invoice and order-confirmation page * #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
* 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

@ -188,13 +188,13 @@ msgid "All Rights Reserved"
msgstr "Alle Rechte vorbehalten" msgstr "Alle Rechte vorbehalten"
msgid "Toggle navigation" msgid "Toggle navigation"
msgstr "Konfiguration" msgstr "Umschalten"
msgid "Why Data Center Light?" msgid "Why Data Center Light?"
msgstr "Warum Data Center Light?" msgstr "Warum Data Center Light?"
msgid "Login" msgid "Login"
msgstr "" msgstr "Anmelden"
msgid "Dashboard" msgid "Dashboard"
msgstr "" msgstr ""

View file

@ -147,7 +147,6 @@
function _fetchPricing() { function _fetchPricing() {
Object.keys(cardPricing).map(function(element) { Object.keys(cardPricing).map(function(element) {
//$('#'+cardPricing[element].id).val(cardPricing[element].value);
$('input[name=' + element + ']').val(cardPricing[element].value); $('input[name=' + element + ']').val(cardPricing[element].value);
}); });
_calcPricing(); _calcPricing();

View file

@ -26,9 +26,9 @@
<div class="help-block with-errors"> <div class="help-block with-errors">
{% for message in messages %} {% for message in messages %}
{% if 'cores' in message.tags %} {% if 'cores' in message.tags %}
<ul class="list-unstyled"><li> <ul class="list-unstyled">
{{ message|safe }} <li>{{ message|safe }}</li>
</li></ul> </ul>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>

View file

@ -110,6 +110,27 @@ msgstr "vorherige"
msgid "next" msgid "next"
msgstr "nächste" 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" msgid "SSH Key"
msgstr "SSH Key" msgstr "SSH Key"
@ -149,30 +170,12 @@ msgstr "Hast Du bereits ein Benutzerkonto?"
msgid "Login" msgid "Login"
msgstr "Anmelden" msgstr "Anmelden"
msgid "New Virtual Machine" msgid "Create VM"
msgstr "Neue virtuelle Maschine" msgstr "VM erstellen"
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 "My Dashboard" msgid "My Dashboard"
msgstr "Mein Dashboard" msgstr "Mein Dashboard"
msgid "Create VM"
msgstr "VM erstellen"
msgid "My VMs" msgid "My VMs"
msgstr "Meine VMs" msgstr "Meine VMs"
@ -286,10 +289,10 @@ msgstr ""
"%(base_url)s%(my_virtual_machines_url)s\n" "%(base_url)s%(my_virtual_machines_url)s\n"
msgid "Toggle navigation" msgid "Toggle navigation"
msgstr "Konfiguration" msgstr "Umschalten"
msgid "Dashboard" msgid "Dashboard"
msgstr "Mein Dashboard" msgstr "Dashboard"
msgid "Logout" msgid "Logout"
msgstr "Abmelden" msgstr "Abmelden"
@ -414,9 +417,6 @@ msgstr "Konfiguration"
msgid "including VAT" msgid "including VAT"
msgstr "inkl. Mehrwertsteuer" msgstr "inkl. Mehrwertsteuer"
msgid "Month"
msgstr "Monat"
msgid "Billing Address" msgid "Billing Address"
msgstr "Rechnungsadresse" msgstr "Rechnungsadresse"
@ -561,6 +561,9 @@ msgstr "Aktueller Preis"
msgid "Your VM is" msgid "Your VM is"
msgstr "Deine VM ist" msgstr "Deine VM ist"
msgid "Terminating"
msgstr "Beenden"
msgid "Pending" msgid "Pending"
msgstr "In Vorbereitung" msgstr "In Vorbereitung"
@ -573,6 +576,11 @@ msgstr "Fehlgeschlagen"
msgid "Terminate VM" msgid "Terminate VM"
msgstr "VM Beenden" 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?" msgid "Something doesn't work?"
msgstr "Etwas funktioniert nicht?" msgstr "Etwas funktioniert nicht?"
@ -591,6 +599,11 @@ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
msgid "OK" msgid "OK"
msgstr "" 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" msgid "Virtual Machines"
msgstr "Virtuelle Maschinen" msgstr "Virtuelle Maschinen"
@ -664,17 +677,55 @@ msgstr ""
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
"auf sie zugreifen kannst." "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 "" msgid ""
"We could not find the requested VM. Please " "We could not find the requested VM. Please "
"contact Data Center Light Support." "contact Data Center Light Support."
msgstr "Kontaktiere den 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" msgid "Virtual Machine Cancellation"
msgstr "VM Kündigung" msgstr "VM Kündigung"
#, python-format #~ msgid "Close"
msgid "VM %(VM_ID)s terminated successfully" #~ msgstr "Schliessen"
msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ 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" #~ msgid "Finish Configuration"
#~ msgstr "Konfiguration beenden" #~ msgstr "Konfiguration beenden"
@ -730,9 +781,6 @@ msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ msgid "Ipv6" #~ msgid "Ipv6"
#~ msgstr "IPv6" #~ msgstr "IPv6"
#~ msgid "Close"
#~ msgstr "Schliessen"
#~ msgid "Cancel" #~ msgid "Cancel"
#~ msgstr "Beenden" #~ msgstr "Beenden"

View file

@ -139,6 +139,10 @@
.modal-text p:not(:last-of-type){ .modal-text p:not(:last-of-type){
margin-bottom: 5px; margin-bottom: 5px;
} }
.modal-title + .modal-footer {
margin-top: 5px;
}
.modal-footer { .modal-footer {
border-top: 0px solid #e5e5e5; border-top: 0px solid #e5e5e5;
width: 100%; width: 100%;

View file

@ -853,6 +853,9 @@ a.list-group-item-danger:focus,
.panel-danger > .panel-heading { .panel-danger > .panel-heading {
color: #eb4d5c; color: #eb4d5c;
} }
.alert-danger{
background: rgba(235, 204, 209, 0.2);
}
.has-error .form-control, .has-error .form-control,
.has-error .input-group-addon { .has-error .input-group-addon {
color: #eb4d5c; color: #eb4d5c;

View file

@ -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;
}
}

View file

@ -290,6 +290,11 @@
text-align: center; text-align: center;
} }
.vm-vmid .alert {
margin-top: 15px;
margin-bottom: -60px;
}
.vm-item-lg { .vm-item-lg {
font-size: 22px; font-size: 22px;
margin-top: 5px; margin-top: 5px;
@ -305,6 +310,10 @@
color: #e47f2f; color: #e47f2f;
} }
.vm-color-failed {
color: #eb4d5c;
}
.vm-detail-item .value{ .vm-detail-item .value{
font-weight: 400; font-weight: 400;
} }
@ -512,7 +521,7 @@
color: #87B6EA; color: #87B6EA;
} }
.vm-status, .vm-status-active, .vm-status-failed { .vm-status, .vm-status-active, .vm-status-failed, .vm-status-pending {
font-weight: 600; font-weight: 600;
} }
.vm-status-active { .vm-status-active {
@ -521,6 +530,9 @@
.vm-status-failed { .vm-status-failed {
color: #eb4d5c; color: #eb4d5c;
} }
.vm-status-pending {
color: #e47f2f;
}
@media (min-width:768px) { @media (min-width:768px) {
.dashboard-subtitle { .dashboard-subtitle {
@ -627,4 +639,28 @@
left: auto; left: auto;
right: 8px; 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;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

View file

@ -1,73 +1,75 @@
(function($){ (function($){
"use strict"; // Start of use strict "use strict"; // Start of use strict
$(window).load(function(){
});
$(document).ready(function(){
_initOs();
});
$(window).resize(function(){
});
var cardPricing = {
function _initOs(){ 'cpu': {
'id': 'coreValue',
'value': 1,
$('.os-circle').click(function(event){ 'min': 1,
$('.os-circle').removeClass('active'); 'max': 48,
$(this).addClass('active'); 'interval': 1
},
var idTemplate = $(this).data('id'); 'ram': {
$('input[name=vm_template_id]').val(idTemplate); 'id': 'ramValue',
}); 'value': 2,
$('.config-box').click(function(event){ 'min': 2,
$('.config-box').removeClass('active'); 'max': 200,
$(this).addClass('active'); 'interval': 1
var idConfig = $(this).data('id'); },
var price = $(this).data('price'); 'storage': {
$('input[name=configuration]').val(idConfig); 'id': 'storageValue',
$('.container-button').fadeIn(); 'value': 10,
$('#priceValue').text(price); 'min': 10,
}); 'max': 2000,
'interval': 10
$('.owl-carousel').owlCarousel({
items:4,
nav: true,
margin:30,
responsiveClass:true,
navText: ['<i class="fa fa-angle-left"></i>', '<i class="fa fa-angle-right"></i>'],
responsive:{
0:{
items:1,
nav:true
},
600:{
items:2,
nav:true
},
768:{
items:3,
nav:true
},
990:{
items:4,
nav:true
}
} }
}); };
}
function _initPricing() {
_fetchPricing();
})(jQuery); $('.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);

View file

@ -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: ['<i class="fa fa-angle-left"></i>', '<i class="fa fa-angle-right"></i>'],
responsive:{
0:{
items:1,
nav:true
},
600:{
items:2,
nav:true
},
768:{
items:3,
nav:true
},
990:{
items:4,
nav:true
}
}
});
}
})(jQuery);

View file

@ -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'); $('.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 () {
@ -32,19 +112,4 @@ $(document).ready(function () {
}); });
return false; 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);
});
}); });

View file

@ -25,6 +25,7 @@
<link href="{% static 'hosting/css/commons.css' %}" rel="stylesheet"> <link href="{% static 'hosting/css/commons.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/virtual-machine.css' %}" rel="stylesheet"> <link href="{% static 'hosting/css/virtual-machine.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/dashboard.css' %}" rel="stylesheet"> <link href="{% static 'hosting/css/dashboard.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/price_calculator.css' %}" rel="stylesheet">
{% block css_extra %} {% block css_extra %}
{% endblock css_extra %} {% endblock css_extra %}
@ -33,9 +34,6 @@
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<link href="//fonts.googleapis.com/css?family=Lato:300,400,500,700,300italic,400italic,700italic" rel="stylesheet" type="text/css"> <link href="//fonts.googleapis.com/css?family=Lato:300,400,500,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="img/favicon.ico" type="image/x-icon" /> <link rel="shortcut icon" href="img/favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="{% static 'hosting/css/owl.carousel.min.css' %}">
<link rel="stylesheet" href="{% static 'hosting/css/owl.theme.default.min.css' %}">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
@ -76,8 +74,8 @@
{% endif %} {% endif %}
<!-- jQuery --> <!-- jQuery -->
<script src="{% static 'hosting/js/jquery.js' %}"></script> <script src="{% static 'hosting/js/jquery.js' %}"></script>
<script type="text/javascript" src="//cdn.jsdelivr.net/jquery.validation/1.13.1/jquery.validate.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.16.0/jquery.validate.min.js"></script>
<script src="{% static 'hosting/js/vendor/owl.carousel.min.js'%}"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.9/validator.min.js"></script>
<!-- Copy Clipboard --> <!-- Copy Clipboard -->
<script src="//cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.10/clipboard.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.10/clipboard.min.js"></script>

View file

@ -0,0 +1,87 @@
{% load staticfiles i18n%}
<form id="order_form" method="POST" action="" data-toggle="validator" role="form">
{% csrf_token %}
<div class="price">
<span id="total">15</span>
<span>CHF/{% trans "Month" %}</span>
<div class="price-text">
<span>{% trans "VAT included" %}</span>
</div>
</div>
<div class="descriptions">
<div class="form-group">
<div class="description input">
<i class="fa fa-minus left" data-minus="cpu" aria-hidden="true"></i>
<input class="input-price select-number" type="number" min="1" max="48" id="coreValue" name="cpu" data-error="{% trans 'Please enter a value in range 1 - 48.' %}" required>
<span> Core</span>
<i class="fa fa-plus right" data-plus="cpu" aria-hidden="true"></i>
</div>
<div class="help-block with-errors">
{% for message in messages %}
{% if 'cores' in message.tags %}
<ul class="list-unstyled"><li>
{{ message|safe }}
</li></ul>
{% endif %}
{% endfor %}
</div>
</div>
<div class="form-group">
<div class="description input">
<i class="fa fa-minus left" data-minus="ram" aria-hidden="true"></i>
<input id="ramValue" class="input-price select-number" type="number" min="2" max="200" name="ram"
data-error="{% trans 'Please enter a value in range 2 - 200.' %}" required>
<span> GB RAM</span>
<i class="fa fa-plus right" data-plus="ram" aria-hidden="true"></i>
</div>
<div class="help-block with-errors">
{% for message in messages %}
{% if 'memory' in message.tags %}
<ul class="list-unstyled"><li>
{{ message|safe }}
</li></ul>
{% endif %}
{% endfor %}
</div>
</div>
<div class="form-group">
<div class="description input">
<i class="fa fa-minus left" data-minus="storage" aria-hidden="true"></i>
<input id="storageValue" class="input-price select-number" type="number" min="10" max="2000" step="10"
name="storage" data-error="{% trans 'Please enter a value in range 10 - 2000.' %}" required>
<span>{% trans "GB Storage (SSD)" %}</span>
<i class="fa fa-plus right" data-plus="storage" aria-hidden="true"></i>
</div>
<div class="help-block with-errors">
{% for message in messages %}
{% if 'storage' in message.tags %}
<ul class="list-unstyled"><li>
{{ message|safe }}
</li></ul>
{% endif %}
{% endfor %}
</div>
</div>
<div class="form-group">
<div class="description select-configuration input justify-center">
<label for="config">OS</label>
<select name="config" id="">
{% for template in templates %}
<option value="{{template.opennebula_vm_template_id}}">{{template.name}}</option>
{% endfor %}
</select>
</div>
<div class="help-block with-errors">
{% for message in messages %}
{% if 'cores' in message.tags %}
<ul class="list-unstyled"><li>
{{ message|safe }}
</li></ul>
{% endif %}
{% endfor %}
</div>
</div>
<input type="hidden" name="total">
</div>
<input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input>
</form>

View file

@ -1,70 +1,30 @@
{% extends "hosting/base_short.html" %} {% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 i18n %} {% load staticfiles bootstrap3 i18n %}
{% block content %}
<div>
<div class="dashboard-container" >
<div class="row">
<div class="col-md-12"> {% block content %}
<br/> <div class="dashboard-container create-vm-container">
{% if messages %} <div class="row">
<div class="alert alert-warning"> <div class="col-sm-5">
{% for message in messages %} <div class="dashboard-container-head">
<h3 class="dashboard-title-thin"><img src="{% static 'hosting/img/plusVM.svg' %}" class="un-icon" style="margin-top: -18px;width: 42px;height: 42px;"> {% trans "Create VM" %}</h3>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span> <span>{{ message }}</span>
{% endfor %} {% endfor %}
</div>
{% endif %}
</div>
{% if not error %}
<div class="dashboard-title">
<h3>{% trans "New Virtual Machine"%} </h3>
<hr/>
</div>
<form method="POST" action="">
{% csrf_token %}
<div class="step-title">
<h4>{% trans "Step 1. Select VM Template:" %} </h4>
</div> </div>
<div class="parent-container">
<div class="container-os owl-carousel owl-theme" id="containerOs">
{% for template in templates %}
<div class="os-circle" data-id="{{template.id}}">
<span class="text" >{{template.name}}</span>
</div>
{% endfor %}
</div>
<input type="hidden" name="vm_template_id">
</div>
<div class="step-title">
<h4>{% trans "Step2. Select VM Configuration" %}</h4>
</div>
<div class="parent-container">
<div class="container-os config owl-carousel owl-theme">
{% for config in configuration_options %}
<div class="config-box" data-id="{{config.id}}" data-price="{{config.price|floatformat}}">
<span>CORE: {{config.cpu|floatformat}}</span>
<span>RAM: {{config.memory|floatformat}} GB</span>
<span>SSD: {{config.disk_size|floatformat}} GB</span>
</div>
{% endfor %}
</div>
<input type="hidden" name="configuration">
</div>
<div class="container-button">
<div class="price">
<span class="label-price">{% trans "Price " %}<span id="priceValue">0</span>{% trans "CHF/Month" %}</span>
</div>
<button class="btn btn-success" >{% trans "Start VM"%} </button>
</div>
</form>
{% endif %} {% endif %}
</div>
</div>
<div class="col-sm-6">
<div class="price-calc-section no-padding">
<div class="landing card">
<div class="caption">
{% include "hosting/calculator_form.html" %}
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
{%endblock%}
{%endblock%}

View file

@ -53,19 +53,26 @@
<h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2> <h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2>
<div class="vm-vmid"> <div class="vm-vmid">
<div class="vm-item-subtitle">{% trans "Your VM is" %}</div> <div class="vm-item-subtitle">{% trans "Your VM is" %}</div>
{% if virtual_machine.state == 'PENDING' %} <div id="terminate-VM" data-alt="{% trans 'Terminating' %}">
<div class="vm-item-lg vm-color-pending">{% trans "Pending" %}</div> {% if virtual_machine.state == 'PENDING' %}
{% elif virtual_machine.state == 'ACTIVE' %} <div class="vm-item-lg vm-color-pending">{% trans "Pending" %}</div>
<div class="vm-item-lg vm-color-online">{% trans "Online" %}</div> {% elif virtual_machine.state == 'ACTIVE' %}
{% elif virtual_machine.state == 'FAILED'%} <div class="vm-item-lg vm-color-online">{% trans "Online" %}</div>
<div class="vm-item-lg vm-color-failed">{% trans "Failed" %}</div> {% elif virtual_machine.state == 'FAILED'%}
{% endif %} <div class="vm-item-lg vm-color-failed">{% trans "Failed" %}</div>
{% if not virtual_machine.status == 'canceled' %} {% else %}
<form method="POST" id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}"> <div class="vm-item-lg"></div>
{% csrf_token %} {% endif %}
</form> {% if not virtual_machine.status == 'canceled' %}
<button data-href="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-vm-term">{% trans "Terminate VM" %}</button> <form method="POST" id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}">
{% endif %} {% csrf_token %}
</form>
<button data-toggle="modal" data-target="#confirm-cancel" class="btn btn-vm-term">{% trans "Terminate VM" %}</button>
<div class="alert alert-danger hide">
{% trans "Sorry, there was an unexpected error. Kindly retry." %}
</div>
{% endif %}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -89,24 +96,41 @@
</div> </div>
<!-- Cancel Modal --> <!-- Cancel Modal -->
<div class="modal fade" id="confirm-cancel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal fade" id="confirm-cancel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<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" data-dismiss="modal" aria-label="Confirm"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="Confirm"><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-ban" aria-hidden="true"></i></div> <div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine"%}</h4> <h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine" %}</h4>
<div class="modal-text"> <div class="modal-text">
<p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p> <p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p>
<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>
</div> </div>
</div>
<!-- / Cancel Modal -->
<!-- Success Modal -->
<div class="modal fade" id="terminate-success" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<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-footer">
<a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a>
</div>
</div>
</div>
</div>
</div> </div>
<!-- / Cancel Modal --> <!-- / Cancel Modal -->
{%endblock%} {%endblock%}

View file

@ -1,23 +1,27 @@
import json import json
import logging import logging
import uuid import uuid
from time import sleep
from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.tokens import default_token_generator 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.files.base import ContentFile
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.http import Http404
from django.http import HttpResponseRedirect, HttpResponse from django.http import Http404, HttpResponseRedirect, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect, render
from django.shortcuts import render
from django.utils.http import urlsafe_base64_decode from django.utils.http import urlsafe_base64_decode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import get_language, ugettext_lazy as _
from django.views.generic import View, CreateView, FormView, ListView, \ from django.utils.translation import ugettext
DetailView, \ from django.views.generic import (
DeleteView, TemplateView, UpdateView View, CreateView, FormView, ListView, DetailView, DeleteView,
TemplateView, UpdateView
)
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from oca.pool import WrongIdError from oca.pool import WrongIdError
from stored_messages.api import mark_read from stored_messages.api import mark_read
@ -28,18 +32,21 @@ from datacenterlight.tasks import create_vm_task
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer, \ from opennebula_api.serializers import VirtualMachineSerializer, \
VirtualMachineTemplateSerializer VirtualMachineTemplateSerializer, VMTemplateSerializer
from utils.forms import BillingAddressForm, PasswordResetRequestForm, \ from utils.forms import BillingAddressForm, PasswordResetRequestForm, \
UserBillingAddressForm UserBillingAddressForm
from utils.hosting_utils import get_vm_price
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, \ from utils.views import (
LoginViewMixin PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
from utils.hosting_utils import get_vm_price )
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
from datacenterlight.models import VMTemplate
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -866,48 +873,80 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
template_name = "hosting/create_virtual_machine.html" template_name = "hosting/create_virtual_machine.html"
login_url = reverse_lazy('hosting:login') 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(): if not UserHostingKey.objects.filter(user=self.request.user).exists():
messages.success( messages.success(
request, 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')) return HttpResponseRedirect(reverse('hosting:ssh_keys'))
context = {'templates': VMTemplate.objects.all()}
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'
}
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request): def post(self, request):
manager = OpenNebulaManager() cores = request.POST.get('cpu')
template_id = request.POST.get('vm_template_id') cores_field = forms.IntegerField(validators=[self.validate_cores])
template = manager.get_template(template_id) memory = request.POST.get('ram')
configuration_id = int(request.POST.get('configuration')) memory_field = forms.IntegerField(validators=[self.validate_memory])
configuration = HostingPlan.objects.get(id=configuration_id) storage = request.POST.get('storage')
request.session['template'] = VirtualMachineTemplateSerializer( storage_field = forms.IntegerField(validators=[self.validate_storage])
template).data 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")
price = get_vm_price(cpu=cores, memory=memory,
disk_size=storage)
specs = {
'cpu': cores,
'memory': memory,
'disk_size': storage,
'price': price
}
request.session['specs'] = specs
request.session['template'] = template_data
return redirect(reverse('hosting:payment')) return redirect(reverse('hosting:payment'))
@ -949,7 +988,19 @@ class VirtualMachineView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
vm = self.get_object() vm = self.get_object()
if vm is None: 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: try:
serializer = VirtualMachineSerializer(vm) serializer = VirtualMachineSerializer(vm)
context = { context = {
@ -964,6 +1015,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
response = {'status': False}
owner = self.request.user owner = self.request.user
vm = self.get_object() vm = self.get_object()
@ -973,42 +1025,50 @@ class VirtualMachineView(LoginRequiredMixin, View):
email=owner.email, email=owner.email,
password=owner.password password=owner.password
) )
vm_data = VirtualMachineSerializer(manager.get_vm(vm.id)).data
terminated = manager.delete_vm( try:
vm.id 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: if not terminated:
messages.error( response['text'] = ugettext(
request, 'Error terminating VM') + opennebula_vm_id
'Error terminating VM %s' % (opennebula_vm_id) else:
) for t in range(15):
return HttpResponseRedirect(self.get_success_url()) try:
context = { manager.get_vm(opennebula_vm_id)
'vm': vm_data, except WrongIdError:
'base_url': "{0}://{1}".format(self.request.scheme, response['status'] = True
self.request.get_host()), response['text'] = ugettext('Terminated')
'page_header': _('Virtual Machine Cancellation') break
} except BaseException:
email_data = { break
'subject': context['page_header'], else:
'to': self.request.user.email, sleep(2)
'context': context, context = {
'template_name': 'vm_canceled', 'vm': vm_data,
'template_path': 'hosting/emails/', 'base_url': "{0}://{1}".format(self.request.scheme,
'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, self.request.get_host()),
} 'page_header': _('Virtual Machine Cancellation')
email = BaseEmail(**email_data) }
email.send() email_data = {
'subject': context['page_header'],
messages.error( 'to': self.request.user.email,
request, 'context': context,
_('VM %(VM_ID)s terminated successfully') % { 'template_name': 'vm_canceled',
'VM_ID': opennebula_vm_id} '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, class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin,
ListView): ListView):