merged master
This commit is contained in:
commit
7456baac16
23 changed files with 1420 additions and 423 deletions
|
@ -1,3 +1,12 @@
|
||||||
|
Pre-changelog: 1.2.3 2017-09-20
|
||||||
|
* #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
|
||||||
|
* #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
|
||||||
|
* 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
|
||||||
1.2.2: 2017-09-08
|
1.2.2: 2017-09-08
|
||||||
* #3704: [hosting] Added my settings page
|
* #3704: [hosting] Added my settings page
|
||||||
* #3771: [datacenterlight] Fixed the inconsistency in navbar style in billing page and onward
|
* #3771: [datacenterlight] Fixed the inconsistency in navbar style in billing page and onward
|
||||||
|
|
|
@ -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 ""
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -49,9 +49,14 @@ urlpatterns += i18n_patterns(
|
||||||
include('ungleich_page.urls',
|
include('ungleich_page.urls',
|
||||||
namespace='ungleich_page'),
|
namespace='ungleich_page'),
|
||||||
name='ungleich_page'),
|
name='ungleich_page'),
|
||||||
url(r'^blog/', include('ungleich.urls',
|
url(r'^cms/blog/',
|
||||||
namespace='ungleich')),
|
include('ungleich.urls', namespace='ungleich')),
|
||||||
url(r'^', include('cms.urls'))
|
url(
|
||||||
|
r'^blog/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>\w[-\w]*)/$',
|
||||||
|
RedirectView.as_view(pattern_name='ungleich:post-detail')),
|
||||||
|
url(r'^blog/|cms/$', RedirectView.as_view(
|
||||||
|
url=reverse_lazy('ungleich:post-list')), name='blog_list_view'),
|
||||||
|
url(r'^cms/', include('cms.urls')),
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2017-09-20 20:23+0000\n"
|
"POT-Creation-Date: 2017-09-23 19:00+0530\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -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"
|
||||||
|
@ -321,10 +324,14 @@ msgstr "Als gelesen markieren"
|
||||||
msgid "All notifications"
|
msgid "All notifications"
|
||||||
msgstr "Alle Benachrichtigungen"
|
msgstr "Alle Benachrichtigungen"
|
||||||
|
|
||||||
msgid "Date"
|
#, python-format
|
||||||
msgstr "Datum"
|
msgid "%(page_header_text)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Status:"
|
msgid "Invoice Date"
|
||||||
|
msgstr "Rechnung Datum"
|
||||||
|
|
||||||
|
msgid "Status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Approved"
|
msgid "Approved"
|
||||||
|
@ -333,10 +340,10 @@ msgstr "Akzeptiert"
|
||||||
msgid "Declined"
|
msgid "Declined"
|
||||||
msgstr "Abgelehnt"
|
msgstr "Abgelehnt"
|
||||||
|
|
||||||
msgid "Billed To:"
|
msgid "Billed to"
|
||||||
msgstr "Rechnungsadresse"
|
msgstr "Rechnungsadresse"
|
||||||
|
|
||||||
msgid "Payment Method:"
|
msgid "Payment method"
|
||||||
msgstr "Bezahlmethode"
|
msgstr "Bezahlmethode"
|
||||||
|
|
||||||
msgid "ending in"
|
msgid "ending in"
|
||||||
|
@ -345,6 +352,9 @@ msgstr "endend in"
|
||||||
msgid "Order summary"
|
msgid "Order summary"
|
||||||
msgstr "Bestellungsübersicht"
|
msgstr "Bestellungsübersicht"
|
||||||
|
|
||||||
|
msgid "Product"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cores"
|
msgid "Cores"
|
||||||
msgstr "Prozessorkerne"
|
msgstr "Prozessorkerne"
|
||||||
|
|
||||||
|
@ -354,15 +364,9 @@ msgstr "Arbeitsspeicher"
|
||||||
msgid "Disk space"
|
msgid "Disk space"
|
||||||
msgstr "Festplattenkapazität"
|
msgstr "Festplattenkapazität"
|
||||||
|
|
||||||
msgid "Configuration"
|
|
||||||
msgstr "Konfiguration"
|
|
||||||
|
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Gesamt"
|
msgstr "Gesamt"
|
||||||
|
|
||||||
msgid "Month"
|
|
||||||
msgstr "Monat"
|
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"By clicking \"Place order\" this plan will charge your credit card account "
|
"By clicking \"Place order\" this plan will charge your credit card account "
|
||||||
|
@ -372,8 +376,10 @@ msgstr ""
|
||||||
"pro Monat belastet"
|
"pro Monat belastet"
|
||||||
|
|
||||||
msgid "Place order"
|
msgid "Place order"
|
||||||
msgstr "Bestelle"
|
msgstr "Bestellen"
|
||||||
|
|
||||||
|
msgid "BACK TO LIST"
|
||||||
|
msgstr "ZURÜCK ZUR LISTE"
|
||||||
|
|
||||||
msgid "Processing..."
|
msgid "Processing..."
|
||||||
msgstr "Abarbeitung..."
|
msgstr "Abarbeitung..."
|
||||||
|
@ -387,14 +393,14 @@ msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
|
||||||
msgid "Order Nr."
|
msgid "Order Nr."
|
||||||
msgstr "Bestellung Nr."
|
msgstr "Bestellung Nr."
|
||||||
|
|
||||||
|
msgid "Date"
|
||||||
|
msgstr "Datum"
|
||||||
|
|
||||||
msgid "Amount"
|
msgid "Amount"
|
||||||
msgstr "Betrag"
|
msgstr "Betrag"
|
||||||
|
|
||||||
msgid "Status"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "See Invoice"
|
msgid "See Invoice"
|
||||||
msgstr "Rechnung"
|
msgstr "Siehe Rechnung"
|
||||||
|
|
||||||
msgid "Page"
|
msgid "Page"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -405,6 +411,9 @@ msgstr ""
|
||||||
msgid "Your Order"
|
msgid "Your Order"
|
||||||
msgstr "Deine Bestellung"
|
msgstr "Deine Bestellung"
|
||||||
|
|
||||||
|
msgid "Configuration"
|
||||||
|
msgstr "Konfiguration"
|
||||||
|
|
||||||
msgid "including VAT"
|
msgid "including VAT"
|
||||||
msgstr "inkl. Mehrwertsteuer"
|
msgstr "inkl. Mehrwertsteuer"
|
||||||
|
|
||||||
|
@ -581,9 +590,6 @@ msgstr "Wir sind hier, um Dir zu helfen!"
|
||||||
msgid "CONTACT"
|
msgid "CONTACT"
|
||||||
msgstr "KONTACT"
|
msgstr "KONTACT"
|
||||||
|
|
||||||
msgid "BACK TO LIST"
|
|
||||||
msgstr "ZURÜCK ZUR LISTE"
|
|
||||||
|
|
||||||
msgid "Terminate your Virtual Machine"
|
msgid "Terminate your Virtual Machine"
|
||||||
msgstr "Deine Virtuelle Maschine beenden"
|
msgstr "Deine Virtuelle Maschine beenden"
|
||||||
|
|
||||||
|
@ -652,6 +658,15 @@ msgstr "Ungültige Kreditkarte"
|
||||||
msgid "Confirm Order"
|
msgid "Confirm Order"
|
||||||
msgstr "Bestellung Bestätigen"
|
msgstr "Bestellung Bestätigen"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"The VM you are looking for is unavailable at the moment. Please contact Data "
|
||||||
|
"Center Light support."
|
||||||
|
msgstr "Kontaktiere den Data Center Light Support."
|
||||||
|
|
||||||
|
msgid "In order to create a VM, you need to create/upload your SSH KEY first."
|
||||||
|
msgstr ""
|
||||||
|
"Um eine VM zu erstellen musst du zuerst einen SSH-Key erstellen / hochladen."
|
||||||
|
|
||||||
msgid "Thank you for the order."
|
msgid "Thank you for the order."
|
||||||
msgstr "Danke für Deine Bestellung."
|
msgstr "Danke für Deine Bestellung."
|
||||||
|
|
||||||
|
@ -659,12 +674,17 @@ msgid ""
|
||||||
"Your VM will be up and running in a few moments. We will send you a "
|
"Your VM will be up and running in a few moments. We will send you a "
|
||||||
"confirmation email as soon as it is ready."
|
"confirmation email as soon as it is ready."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, "
|
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
|
||||||
"sobald Du auf sie zugreifen kannst."
|
"auf sie zugreifen kannst."
|
||||||
|
|
||||||
msgid "In order to create a VM, you need to create/upload your SSH KEY first."
|
msgid "Invalid number of cores"
|
||||||
msgstr ""
|
msgstr "Ungültige Anzahle CPU-Kerne"
|
||||||
"Um eine VM zu erstellen musst du zuerst einen SSH-Key erstellen / hochladen."
|
|
||||||
|
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 "
|
||||||
|
@ -686,6 +706,27 @@ msgstr "VM Kündigung"
|
||||||
#~ msgid "VM %(VM_ID)s terminated successfully"
|
#~ msgid "VM %(VM_ID)s terminated successfully"
|
||||||
#~ msgstr "VM %(VM_ID)s erfolgreich beendet"
|
#~ 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"
|
||||||
|
|
||||||
|
|
|
@ -360,3 +360,19 @@
|
||||||
.no-cards a {
|
.no-cards a {
|
||||||
color: #7ca3d0;
|
color: #7ca3d0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-plain {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
fill: #595959;
|
||||||
|
color: #595959;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.btn-plain:hover,
|
||||||
|
.btn-plain:focus,
|
||||||
|
.btn-plain:active,
|
||||||
|
.btn-plain:active:focus {
|
||||||
|
outline: none;
|
||||||
|
color: #999;
|
||||||
|
fill: #999;
|
||||||
|
}
|
|
@ -580,9 +580,9 @@ a.unlink:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dcl-place-order-text{
|
.dcl-place-order-text{
|
||||||
font-size: 13px;
|
/* font-size: 13px; */
|
||||||
color: #808080;
|
color: #808080;
|
||||||
margin-bottom: 15px;
|
/* margin-bottom: 15px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dcl-order-table-total .tbl-total {
|
.dcl-order-table-total .tbl-total {
|
||||||
|
|
|
@ -1,4 +1,15 @@
|
||||||
.order-detail-container {padding-top: 70px; padding-bottom: 70px; margin-bottom: 70px;}
|
.order-detail-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 100px auto 40px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(min-width: 768px) {
|
||||||
|
.order-detail-container {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.order-detail-container .invoice-title h2, .invoice-title h3 {
|
.order-detail-container .invoice-title h2, .invoice-title h3 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -15,3 +26,67 @@
|
||||||
.order-detail-container .table > tbody > tr > .thick-line {
|
.order-detail-container .table > tbody > tr > .thick-line {
|
||||||
border-top: 2px solid;
|
border-top: 2px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-detail-container .dashboard-title-thin {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-left: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-container .dashboard-title-thin .un-icon {
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-container .dashboard-container-head {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-container .dashboard-container-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.order-detail-container .dashboard-container-options .svg-img {
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-container .order-details {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-container .order-details strong {
|
||||||
|
color: #595959;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-container h4 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-container p {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #595959;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-container hr {
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.order-confirm-btn {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.order-detail-container .dashboard-container-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: -4px;
|
||||||
|
}
|
||||||
|
.order-detail-container .dashboard-container-options .svg-img {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
237
hosting/static/hosting/css/price_calculator.css
Normal file
237
hosting/static/hosting/css/price_calculator.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
18
hosting/static/hosting/img/icon-pdf.svg
Normal file
18
hosting/static/hosting/img/icon-pdf.svg
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="492px" height="649px" viewBox="0 0 492 649" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icon-pdf</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill-rule="evenodd">
|
||||||
|
<g id="icon-pdf" fill-rule="nonzero">
|
||||||
|
<g id="Group" transform="translate(93.000000, 269.000000)">
|
||||||
|
<path d="M98,47.8 C98,54.8 97,61.7 95.1,66.6 C93.1,72.5 90.1,76.5 86.2,80.5 C82.2,84.5 76.3,87.4 70.4,89.4 C64.5,91.4 55.6,92.3 46.6,92.3 L34.7,92.3 L34.7,134 L0,134 L0,0.3 L45.5,0.3 C54.4,0.3 62.4,1.3 69.3,3.2 C76.2,5.2 81.2,8.2 85.1,12.1 C89.1,16.1 92.1,21 94,27 C96.9,32 98,38.9 98,47.8 Z M61.3,46.8 C61.3,42.8 61.3,39.9 60.3,37.9 C60.3,35.9 59.3,33.9 57.4,32 C56.4,31 54.5,30 52.4,29.1 C50.4,29.1 47.4,28.1 44.5,28.1 L34.6,28.1 L34.6,63.7 L45.5,63.7 C48.4,63.7 50.4,63.7 52.5,62.7 C54.5,61.7 56.5,61.7 57.5,59.8 C58.5,57.8 59.5,56.9 60.4,54.8 C61.3,53.8 61.3,50.8 61.3,46.8 Z" id="Shape"></path>
|
||||||
|
<path d="M214.8,66.6 C214.8,78.5 213.8,89.3 210.8,97.3 C208.8,106.2 204.9,113.1 199.9,118.1 C195,123.1 189,127 182.1,130 C175.2,132 167.3,134 157.3,134 L108.8,134 L108.8,0.3 L156.3,0.3 C166.2,0.3 175.1,1.3 182,4.3 C188.9,7.2 194.9,10.2 199.8,16.2 C204.8,21.2 207.7,28.1 209.7,37 C213.8,44.8 214.8,54.7 214.8,66.6 Z M178.1,65.6 C178.1,58.7 178.1,52.7 177.1,47.8 C176.1,42.8 175.1,39.9 174.2,36.9 C172.2,34 170.2,32.9 168.3,31.9 C165.4,30.9 162.4,30.9 158.4,30.9 L145.6,30.9 L145.6,103.2 L158.4,103.2 C162.4,103.2 165.4,103.2 168.3,102.2 C171.2,101.2 173.3,99.3 174.2,97.2 C176.2,94.3 177.1,91.3 178.2,86.3 C178.1,80.5 178.1,74.5 178.1,65.6 Z" id="Shape"></path>
|
||||||
|
<polygon id="Shape" points="264.3 32 264.3 55.8 304.9 55.8 304.9 85.5 264.3 85.5 264.3 135 229.7 135 229.7 0.3 312.9 0.3 313.9 31 264.4 31 264.4 32"></polygon>
|
||||||
|
</g>
|
||||||
|
<path d="M491.3,649 L0.7,649 L0.7,0 L346.1,0 L491.3,145.2 L491.3,649 L491.3,649 Z M44.7,605 L448.4,605 L448.4,162.8 L327.4,42.9 L44.7,42.9 L44.7,605 Z" id="Shape"></path>
|
||||||
|
<polygon id="Shape" points="469.3 176 315.3 176 315.3 20.9 359.3 20.9 359.3 132 469.3 132"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
17
hosting/static/hosting/img/icon-print.svg
Normal file
17
hosting/static/hosting/img/icon-print.svg
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="612px" height="612px" viewBox="0 0 612 612" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>54471</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill-rule="evenodd">
|
||||||
|
<g id="54471" fill-rule="nonzero">
|
||||||
|
<path d="M0,225.633 L0,386.367 C0,417.272 30.6,417.272 30.6,417.272 L83.454,417.272 L83.454,389.454 L27.818,389.454 L27.818,222.545 L584.181,222.545 L584.181,389.454 L528.545,389.454 L528.545,417.272 L581.4,417.272 C581.4,417.272 612,417.272 612,386.367 L612,225.633 C612,225.633 612,194.727 581.4,194.727 L30.6,194.727 C0,194.728 0,225.633 0,225.633 Z" id="Shape"></path>
|
||||||
|
<polygon id="Shape" points="500.728 166.909 500.728 0 111.273 0 111.273 166.909 139.091 166.909 139.091 27.818 472.909 27.818 472.909 166.909"></polygon>
|
||||||
|
<path d="M528.546,292.091 C528.546,278.182 514.358,278.182 514.358,278.182 L97.642,278.182 C97.642,278.182 83.455,278.182 83.455,292.091 C83.455,306 97.642,306 97.642,306 L514.359,306 C514.358,306 528.546,306 528.546,292.091 Z" id="Shape"></path>
|
||||||
|
<rect id="Rectangle-path" x="166.909" y="500.728" width="278.182" height="27.818"></rect>
|
||||||
|
<path d="M500.728,612 L500.728,389.454 L111.273,389.454 L111.273,612 L500.728,612 Z M139.091,417.272 L472.909,417.272 L472.909,584.181 L139.091,584.181 L139.091,417.272 Z" id="Shape"></path>
|
||||||
|
<rect id="Rectangle-path" x="166.909" y="445.091" width="278.182" height="27.818"></rect>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -1,72 +1,74 @@
|
||||||
(function($){
|
(function($){
|
||||||
"use strict"; // Start of use strict
|
"use strict"; // Start of use strict
|
||||||
|
|
||||||
|
var cardPricing = {
|
||||||
$(window).load(function(){
|
'cpu': {
|
||||||
|
'id': 'coreValue',
|
||||||
|
'value': 1,
|
||||||
});
|
'min': 1,
|
||||||
|
'max': 48,
|
||||||
$(document).ready(function(){
|
'interval': 1
|
||||||
_initOs();
|
},
|
||||||
|
'ram': {
|
||||||
});
|
'id': 'ramValue',
|
||||||
|
'value': 2,
|
||||||
$(window).resize(function(){
|
'min': 2,
|
||||||
|
'max': 200,
|
||||||
|
'interval': 1
|
||||||
});
|
},
|
||||||
|
'storage': {
|
||||||
|
'id': 'storageValue',
|
||||||
|
'value': 10,
|
||||||
function _initOs(){
|
'min': 10,
|
||||||
|
'max': 2000,
|
||||||
|
'interval': 10
|
||||||
$('.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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
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);
|
})(jQuery);
|
||||||
|
|
||||||
|
|
73
hosting/static/hosting/js/createvm_old.js
Normal file
73
hosting/static/hosting/js/createvm_old.js
Normal 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);
|
||||||
|
|
||||||
|
|
387
hosting/static/hosting/js/html2pdf.js
Normal file
387
hosting/static/hosting/js/html2pdf.js
Normal file
|
@ -0,0 +1,387 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 Erik Koopmans
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a PDF from an HTML element or string using html2canvas and jsPDF.
|
||||||
|
*
|
||||||
|
* @param {Element|string} source The source element or HTML string.
|
||||||
|
* @param {Object=} opt An object of optional settings: 'margin', 'filename',
|
||||||
|
* 'image' ('type' and 'quality'), and 'html2canvas' / 'jspdf', which are
|
||||||
|
* sent as settings to their corresponding functions.
|
||||||
|
*/
|
||||||
|
var html2pdf = (function(html2canvas, jsPDF) {
|
||||||
|
|
||||||
|
/* ---------- MAIN FUNCTION ---------- */
|
||||||
|
|
||||||
|
var html2pdf = function(source, opt) {
|
||||||
|
// Handle input.
|
||||||
|
opt = objType(opt) === 'object' ? opt : {};
|
||||||
|
var source = html2pdf.parseInput(source, opt);
|
||||||
|
|
||||||
|
// Determine the PDF page size.
|
||||||
|
var pageSize = jsPDF.getPageSize(opt.jsPDF);
|
||||||
|
pageSize.inner = {
|
||||||
|
width: pageSize.width - opt.margin[1] - opt.margin[3],
|
||||||
|
height: pageSize.height - opt.margin[0] - opt.margin[2]
|
||||||
|
};
|
||||||
|
pageSize.inner.ratio = pageSize.inner.height / pageSize.inner.width;
|
||||||
|
|
||||||
|
// Copy the source element into a PDF-styled container div.
|
||||||
|
var container = html2pdf.makeContainer(source, pageSize);
|
||||||
|
var overlay = container.parentElement;
|
||||||
|
|
||||||
|
// Get the locations of all hyperlinks.
|
||||||
|
if (opt.enableLinks) {
|
||||||
|
// Find all anchor tags and get the container's bounds for reference.
|
||||||
|
opt.links = [];
|
||||||
|
var links = container.querySelectorAll('a');
|
||||||
|
var containerRect = unitConvert(container.getBoundingClientRect(), pageSize.k);
|
||||||
|
|
||||||
|
// Treat each client rect as a separate link (for text-wrapping).
|
||||||
|
Array.prototype.forEach.call(links, function(link) {
|
||||||
|
var clientRects = link.getClientRects();
|
||||||
|
for (var i=0; i<clientRects.length; i++) {
|
||||||
|
var clientRect = unitConvert(clientRects[i], pageSize.k);
|
||||||
|
clientRect.left -= containerRect.left;
|
||||||
|
clientRect.top -= containerRect.top;
|
||||||
|
opt.links.push({ el: link, clientRect: clientRect });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the canvas and pass the result to makePDF.
|
||||||
|
var onRendered = opt.html2canvas.onrendered || function() {};
|
||||||
|
opt.html2canvas.onrendered = function(canvas) {
|
||||||
|
onRendered(canvas);
|
||||||
|
document.body.removeChild(overlay);
|
||||||
|
html2pdf.makePDF(canvas, pageSize, opt);
|
||||||
|
}
|
||||||
|
html2canvas(container, opt.html2canvas);
|
||||||
|
};
|
||||||
|
|
||||||
|
html2pdf.parseInput = function(source, opt) {
|
||||||
|
// Parse the opt object.
|
||||||
|
opt.jsPDF = opt.jsPDF || {};
|
||||||
|
opt.html2canvas = opt.html2canvas || {};
|
||||||
|
opt.filename = opt.filename && objType(opt.filename) === 'string' ? opt.filename : 'file.pdf';
|
||||||
|
opt.enableLinks = opt.hasOwnProperty('enableLinks') ? opt.enableLinks : true;
|
||||||
|
opt.image = opt.image || {};
|
||||||
|
opt.image.type = opt.image.type || 'jpeg';
|
||||||
|
opt.image.quality = opt.image.quality || 0.95;
|
||||||
|
|
||||||
|
// Parse the margin property of the opt object.
|
||||||
|
switch (objType(opt.margin)) {
|
||||||
|
case 'undefined':
|
||||||
|
opt.margin = 0;
|
||||||
|
case 'number':
|
||||||
|
opt.margin = [opt.margin, opt.margin, opt.margin, opt.margin];
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
if (opt.margin.length === 2) {
|
||||||
|
opt.margin = [opt.margin[0], opt.margin[1], opt.margin[0], opt.margin[1]];
|
||||||
|
}
|
||||||
|
if (opt.margin.length === 4) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw 'Invalid margin array.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the source element/string.
|
||||||
|
if (!source) {
|
||||||
|
throw 'Missing source element or string.';
|
||||||
|
} else if (objType(source) === 'string') {
|
||||||
|
source = createElement('div', { innerHTML: source });
|
||||||
|
} else if (objType(source) === 'element') {
|
||||||
|
source = cloneNode(source, opt.html2canvas.javascriptEnabled);
|
||||||
|
} else {
|
||||||
|
throw 'Invalid source - please specify an HTML Element or string.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the parsed input (opt is modified in-place, no need to return).
|
||||||
|
return source;
|
||||||
|
};
|
||||||
|
|
||||||
|
html2pdf.makeContainer = function(source, pageSize) {
|
||||||
|
// Define the CSS styles for the container and its overlay parent.
|
||||||
|
var overlayCSS = {
|
||||||
|
position: 'fixed', overflow: 'hidden', zIndex: 1000,
|
||||||
|
left: 0, right: 0, bottom: 0, top: 0,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.8)'
|
||||||
|
};
|
||||||
|
var containerCSS = {
|
||||||
|
position: 'absolute', width: pageSize.inner.width + pageSize.unit,
|
||||||
|
left: 0, right: 0, top: 0, height: 'auto', margin: 'auto',
|
||||||
|
backgroundColor: 'white'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the overlay to hidden (could be changed in the future to provide a print preview).
|
||||||
|
overlayCSS.opacity = 0;
|
||||||
|
|
||||||
|
// Create and attach the elements.
|
||||||
|
var overlay = createElement('div', { className: 'html2pdf__overlay', style: overlayCSS });
|
||||||
|
var container = createElement('div', { className: 'html2pdf__container', style: containerCSS });
|
||||||
|
container.appendChild(source);
|
||||||
|
overlay.appendChild(container);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
// Enable page-breaks.
|
||||||
|
var pageBreaks = source.querySelectorAll('.html2pdf__page-break');
|
||||||
|
var pxPageHeight = pageSize.inner.height * pageSize.k / 72 * 96;
|
||||||
|
Array.prototype.forEach.call(pageBreaks, function(el) {
|
||||||
|
el.style.display = 'block';
|
||||||
|
var clientRect = el.getBoundingClientRect();
|
||||||
|
el.style.height = pxPageHeight - (clientRect.top % pxPageHeight) + 'px';
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// Return the container.
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
html2pdf.makePDF = function(canvas, pageSize, opt) {
|
||||||
|
// Calculate the number of pages.
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
var pxFullHeight = canvas.height;
|
||||||
|
var pxPageHeight = Math.floor(canvas.width * pageSize.inner.ratio);
|
||||||
|
var nPages = Math.ceil(pxFullHeight / pxPageHeight);
|
||||||
|
|
||||||
|
// Create a one-page canvas to split up the full image.
|
||||||
|
var pageCanvas = document.createElement('canvas');
|
||||||
|
var pageCtx = pageCanvas.getContext('2d');
|
||||||
|
var pageHeight = pageSize.inner.height;
|
||||||
|
pageCanvas.width = canvas.width;
|
||||||
|
pageCanvas.height = pxPageHeight;
|
||||||
|
|
||||||
|
// Initialize the PDF.
|
||||||
|
var pdf = new jsPDF(opt.jsPDF);
|
||||||
|
|
||||||
|
for (var page=0; page<nPages; page++) {
|
||||||
|
// Trim the final page to reduce file size.
|
||||||
|
if (page === nPages-1) {
|
||||||
|
pageCanvas.height = pxFullHeight % pxPageHeight;
|
||||||
|
pageHeight = pageCanvas.height * pageSize.inner.width / pageCanvas.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the page.
|
||||||
|
var w = pageCanvas.width;
|
||||||
|
var h = pageCanvas.height;
|
||||||
|
pageCtx.fillStyle = 'white';
|
||||||
|
pageCtx.fillRect(0, 0, w, h);
|
||||||
|
pageCtx.drawImage(canvas, 0, page*pxPageHeight, w, h, 0, 0, w, h);
|
||||||
|
|
||||||
|
// Add the page to the PDF.
|
||||||
|
if (page) pdf.addPage();
|
||||||
|
var imgData = pageCanvas.toDataURL('image/' + opt.image.type, opt.image.quality);
|
||||||
|
pdf.addImage(imgData, opt.image.type, opt.margin[1], opt.margin[0],
|
||||||
|
pageSize.inner.width, pageHeight);
|
||||||
|
|
||||||
|
// Add hyperlinks.
|
||||||
|
if (opt.enableLinks) {
|
||||||
|
var pageTop = page * pageSize.inner.height;
|
||||||
|
opt.links.forEach(function(link) {
|
||||||
|
if (link.clientRect.top > pageTop && link.clientRect.top < pageTop + pageSize.inner.height) {
|
||||||
|
var left = opt.margin[1] + link.clientRect.left;
|
||||||
|
var top = opt.margin[0] + link.clientRect.top - pageTop;
|
||||||
|
pdf.link(left, top, link.clientRect.width, link.clientRect.height, { url: link.el.href });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish the PDF.
|
||||||
|
pdf.save( opt.filename );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- UTILS ---------- */
|
||||||
|
|
||||||
|
// Determine the type of a variable/object.
|
||||||
|
var objType = function(obj) {
|
||||||
|
if (typeof obj === 'undefined') return 'undefined';
|
||||||
|
else if (typeof obj === 'string' || obj instanceof String) return 'string';
|
||||||
|
else if (typeof obj === 'number' || obj instanceof Number) return 'number';
|
||||||
|
else if (!!obj && obj.constructor === Array) return 'array';
|
||||||
|
else if (obj && obj.nodeType === 1) return 'element';
|
||||||
|
else if (typeof obj === 'object') return 'object';
|
||||||
|
else return 'unknown';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create an HTML element with optional className, innerHTML, and style.
|
||||||
|
var createElement = function(tagName, opt) {
|
||||||
|
var el = document.createElement(tagName);
|
||||||
|
if (opt.className) el.className = opt.className;
|
||||||
|
if (opt.innerHTML) {
|
||||||
|
el.innerHTML = opt.innerHTML;
|
||||||
|
var scripts = el.getElementsByTagName('script');
|
||||||
|
for (var i = scripts.length; i-- > 0; null) {
|
||||||
|
scripts[i].parentNode.removeChild(scripts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var key in opt.style) {
|
||||||
|
el.style[key] = opt.style[key];
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Deep-clone a node and preserve contents/properties.
|
||||||
|
var cloneNode = function(node, javascriptEnabled) {
|
||||||
|
// Recursively clone the node.
|
||||||
|
var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
|
||||||
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||||
|
if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {
|
||||||
|
clone.appendChild(cloneNode(child, javascriptEnabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.nodeType === 1) {
|
||||||
|
// Preserve contents/properties of special nodes.
|
||||||
|
if (node.nodeName === 'CANVAS') {
|
||||||
|
clone.width = node.width;
|
||||||
|
clone.height = node.height;
|
||||||
|
clone.getContext('2d').drawImage(node, 0, 0);
|
||||||
|
} else if (node.nodeName === 'TEXTAREA' || node.nodeName === 'SELECT') {
|
||||||
|
clone.value = node.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve the node's scroll position when it loads.
|
||||||
|
clone.addEventListener('load', function() {
|
||||||
|
clone.scrollTop = node.scrollTop;
|
||||||
|
clone.scrollLeft = node.scrollLeft;
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the cloned node.
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert units using the conversion value 'k' from jsPDF.
|
||||||
|
var unitConvert = function(obj, k) {
|
||||||
|
var newObj = {};
|
||||||
|
for (var key in obj) {
|
||||||
|
newObj[key] = obj[key] * 72 / 96 / k;
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get dimensions of a PDF page, as determined by jsPDF.
|
||||||
|
jsPDF.getPageSize = function(orientation, unit, format) {
|
||||||
|
// Decode options object
|
||||||
|
if (typeof orientation === 'object') {
|
||||||
|
var options = orientation;
|
||||||
|
orientation = options.orientation;
|
||||||
|
unit = options.unit || unit;
|
||||||
|
format = options.format || format;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default options
|
||||||
|
unit = unit || 'mm';
|
||||||
|
format = format || 'a4';
|
||||||
|
orientation = ('' + (orientation || 'P')).toLowerCase();
|
||||||
|
var format_as_string = ('' + format).toLowerCase();
|
||||||
|
|
||||||
|
// Size in pt of various paper formats
|
||||||
|
pageFormats = {
|
||||||
|
'a0' : [2383.94, 3370.39], 'a1' : [1683.78, 2383.94],
|
||||||
|
'a2' : [1190.55, 1683.78], 'a3' : [ 841.89, 1190.55],
|
||||||
|
'a4' : [ 595.28, 841.89], 'a5' : [ 419.53, 595.28],
|
||||||
|
'a6' : [ 297.64, 419.53], 'a7' : [ 209.76, 297.64],
|
||||||
|
'a8' : [ 147.40, 209.76], 'a9' : [ 104.88, 147.40],
|
||||||
|
'a10' : [ 73.70, 104.88], 'b0' : [2834.65, 4008.19],
|
||||||
|
'b1' : [2004.09, 2834.65], 'b2' : [1417.32, 2004.09],
|
||||||
|
'b3' : [1000.63, 1417.32], 'b4' : [ 708.66, 1000.63],
|
||||||
|
'b5' : [ 498.90, 708.66], 'b6' : [ 354.33, 498.90],
|
||||||
|
'b7' : [ 249.45, 354.33], 'b8' : [ 175.75, 249.45],
|
||||||
|
'b9' : [ 124.72, 175.75], 'b10' : [ 87.87, 124.72],
|
||||||
|
'c0' : [2599.37, 3676.54], 'c1' : [1836.85, 2599.37],
|
||||||
|
'c2' : [1298.27, 1836.85], 'c3' : [ 918.43, 1298.27],
|
||||||
|
'c4' : [ 649.13, 918.43], 'c5' : [ 459.21, 649.13],
|
||||||
|
'c6' : [ 323.15, 459.21], 'c7' : [ 229.61, 323.15],
|
||||||
|
'c8' : [ 161.57, 229.61], 'c9' : [ 113.39, 161.57],
|
||||||
|
'c10' : [ 79.37, 113.39], 'dl' : [ 311.81, 623.62],
|
||||||
|
'letter' : [612, 792],
|
||||||
|
'government-letter' : [576, 756],
|
||||||
|
'legal' : [612, 1008],
|
||||||
|
'junior-legal' : [576, 360],
|
||||||
|
'ledger' : [1224, 792],
|
||||||
|
'tabloid' : [792, 1224],
|
||||||
|
'credit-card' : [153, 243]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unit conversion
|
||||||
|
switch (unit) {
|
||||||
|
case 'pt': k = 1; break;
|
||||||
|
case 'mm': k = 72 / 25.4; break;
|
||||||
|
case 'cm': k = 72 / 2.54; break;
|
||||||
|
case 'in': k = 72; break;
|
||||||
|
case 'px': k = 72 / 96; break;
|
||||||
|
case 'pc': k = 12; break;
|
||||||
|
case 'em': k = 12; break;
|
||||||
|
case 'ex': k = 6; break;
|
||||||
|
default:
|
||||||
|
throw ('Invalid unit: ' + unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dimensions are stored as user units and converted to points on output
|
||||||
|
if (pageFormats.hasOwnProperty(format_as_string)) {
|
||||||
|
pageHeight = pageFormats[format_as_string][1] / k;
|
||||||
|
pageWidth = pageFormats[format_as_string][0] / k;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
pageHeight = format[1];
|
||||||
|
pageWidth = format[0];
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error('Invalid format: ' + format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle page orientation
|
||||||
|
if (orientation === 'p' || orientation === 'portrait') {
|
||||||
|
orientation = 'p';
|
||||||
|
if (pageWidth > pageHeight) {
|
||||||
|
tmp = pageWidth;
|
||||||
|
pageWidth = pageHeight;
|
||||||
|
pageHeight = tmp;
|
||||||
|
}
|
||||||
|
} else if (orientation === 'l' || orientation === 'landscape') {
|
||||||
|
orientation = 'l';
|
||||||
|
if (pageHeight > pageWidth) {
|
||||||
|
tmp = pageWidth;
|
||||||
|
pageWidth = pageHeight;
|
||||||
|
pageHeight = tmp;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw('Invalid orientation: ' + orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return information (k is the unit conversion ratio from pts)
|
||||||
|
var info = { 'width': pageWidth, 'height': pageHeight, 'unit': unit, 'k': k };
|
||||||
|
return info;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Expose the html2pdf function.
|
||||||
|
return html2pdf;
|
||||||
|
}(html2canvas, jsPDF));
|
15
hosting/static/hosting/js/order.js
Normal file
15
hosting/static/hosting/js/order.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.btn-pdf').click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var $target = $($(this).attr('data-target')) || $('body');
|
||||||
|
var fileName = $target.attr('id') + '.pdf';
|
||||||
|
html2pdf($target[0], {
|
||||||
|
filename: fileName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('.btn-print').click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('a');
|
||||||
|
window.print();
|
||||||
|
});
|
||||||
|
});
|
|
@ -79,6 +79,7 @@ $(document).ready(function() {
|
||||||
$('html,body').scrollTop(scrollmem);
|
$('html,body').scrollTop(scrollmem);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('.modal-text').removeClass('hide');
|
||||||
var create_vm_form = $('#virtual_machine_create_form');
|
var create_vm_form = $('#virtual_machine_create_form');
|
||||||
create_vm_form.submit(function () {
|
create_vm_form.submit(function () {
|
||||||
$('#btn-create-vm').prop('disabled', true);
|
$('#btn-create-vm').prop('disabled', true);
|
||||||
|
@ -102,6 +103,7 @@ $(document).ready(function() {
|
||||||
fa_icon = $('.modal-icon > .fa');
|
fa_icon = $('.modal-icon > .fa');
|
||||||
fa_icon.attr('class', 'fa fa-times');
|
fa_icon.attr('class', 'fa fa-times');
|
||||||
$('.modal-header > .close').attr('class', 'close');
|
$('.modal-header > .close').attr('class', 'close');
|
||||||
|
$('.modal-text').addClass('hide');
|
||||||
if (typeof(create_vm_error_message) !== 'undefined') {
|
if (typeof(create_vm_error_message) !== 'undefined') {
|
||||||
$('#createvm-modal-title').text(create_vm_error_message);
|
$('#createvm-modal-title').text(create_vm_error_message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
@ -94,6 +92,13 @@
|
||||||
<!-- Init JavaScript -->
|
<!-- Init JavaScript -->
|
||||||
<script src="{% static 'hosting/js/initial.js' %}"></script>
|
<script src="{% static 'hosting/js/initial.js' %}"></script>
|
||||||
|
|
||||||
|
{% block js_extra %}
|
||||||
|
{% comment %}
|
||||||
|
this block is above some files, because on stripe error scripts below the stripe
|
||||||
|
script are not properly executed.
|
||||||
|
{% endcomment %}
|
||||||
|
{% endblock js_extra %}
|
||||||
|
|
||||||
<script src="https://js.stripe.com/v3/"></script>
|
<script src="https://js.stripe.com/v3/"></script>
|
||||||
<script src="https://js.stripe.com/v2/"></script>
|
<script src="https://js.stripe.com/v2/"></script>
|
||||||
<!-- Stripe Lib -->
|
<!-- Stripe Lib -->
|
||||||
|
@ -108,8 +113,6 @@
|
||||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
|
||||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment-with-locales.js"></script>
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment-with-locales.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
87
hosting/templates/hosting/calculator_form.html
Normal file
87
hosting/templates/hosting/calculator_form.html
Normal 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>
|
|
@ -1,70 +1,30 @@
|
||||||
{% extends "hosting/base_short.html" %}
|
{% extends "hosting/base_short.html" %}
|
||||||
{% load staticfiles bootstrap3 i18n %}
|
{% load staticfiles bootstrap3 i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div>
|
<div class="dashboard-container create-vm-container">
|
||||||
<div class="dashboard-container" >
|
<div class="row">
|
||||||
<div class="row">
|
<div class="col-sm-5">
|
||||||
|
<div class="dashboard-container-head">
|
||||||
<div class="col-md-12">
|
<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>
|
||||||
<br/>
|
{% if messages %}
|
||||||
{% if messages %}
|
<div class="alert alert-warning">
|
||||||
<div class="alert alert-warning">
|
{% for message in messages %}
|
||||||
{% 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%}
|
|
@ -4,206 +4,189 @@
|
||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div id="order-detail{{order.pk}}" class="order-detail-container">
|
||||||
<div class="order-detail-container">
|
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="row">
|
<div class="alert alert-warning">
|
||||||
<div class="col-xs-12 col-md-8 col-md-offset-2">
|
{% for message in messages %}
|
||||||
<br/>
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
{% for message in messages %}
|
|
||||||
<span>{{ message }}</span>
|
<span>{{ message }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not error %}
|
{% if not error %}
|
||||||
<div class="row">
|
<div class="dashboard-container-head">
|
||||||
<div class="col-xs-12 col-md-8 col-md-offset-2">
|
<h1 class="dashboard-title-thin">
|
||||||
<div class="invoice-title">
|
<img src="{% static 'hosting/img/billing.svg' %}" class="un-icon">{% blocktrans with page_header_text=page_header_text|default:"Invoice" %}{{page_header_text}}{% endblocktrans %}
|
||||||
<h2>{{page_header_text}}</h2>
|
</h1>
|
||||||
<h3 class="pull-right">
|
<div class="dashboard-container-options">
|
||||||
|
<button type="button" class="btn-plain btn-pdf" data-target="#order-detail{{order.pk}}"><img src="{% static 'hosting/img/icon-pdf.svg' %}" class="svg-img"></button>
|
||||||
|
<button type="button" class="btn-plain btn-print"><img src="{% static 'hosting/img/icon-print.svg' %}" class="svg-img"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="order-details">
|
||||||
|
{% if order %}
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Order #" %} {{order.id}}</strong>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Invoice Date" %}:</strong>
|
||||||
|
<span id="order-created_at">
|
||||||
{% if order %}
|
{% if order %}
|
||||||
{% trans "Order #"%} {{order.id}}
|
{{order.created_at|date:'Y-m-d H:i'}}
|
||||||
|
{% else %}
|
||||||
|
{% now "Y-m-d H:i" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</span>
|
||||||
|
</p>
|
||||||
|
{% if order %}
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Status" %}: </strong>
|
||||||
|
<strong>
|
||||||
|
{% if order.status == 'Approved' %}
|
||||||
|
<span class="vm-color-online">{% trans "Approved" %}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="vm-status-failed">{% trans "Declined" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<address>
|
||||||
|
<h4>{% trans "Billed to" %}:</h4>
|
||||||
|
<p>
|
||||||
|
{% if order %}
|
||||||
|
{{user.name}}<br>
|
||||||
|
{{order.billing_address.street_address}}, {{order.billing_address.postal_code}}<br>
|
||||||
|
{{order.billing_address.city}}, {{order.billing_address.country}}
|
||||||
|
{% else %}
|
||||||
|
{% with request.session.billing_address_data as billing_address %}
|
||||||
|
{{billing_address.cardholder_name}}<br>
|
||||||
|
{{billing_address.street_address}}, {{billing_address.postal_code}}<br>
|
||||||
|
{{billing_address.city}}, {{billing_address.country}}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</address>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row">
|
<div>
|
||||||
<div class="col-xs-12 col-md-6 pull-right order-confirm-date">
|
<h4>{% trans "Payment method" %}:</h4>
|
||||||
<address>
|
<p>
|
||||||
<strong>{% trans "Date"%}:</strong><br>
|
{% if order %}
|
||||||
<span id="order-created_at">
|
|
||||||
{% if order %}
|
|
||||||
{{order.created_at|date:'Y-m-d H:i'}}
|
|
||||||
{% else %}
|
|
||||||
{% now "Y-m-d H:i" %}
|
|
||||||
{% endif %}
|
|
||||||
</span><br><br>
|
|
||||||
{% if order %}
|
|
||||||
<strong>{% trans "Status:"%}</strong><br>
|
|
||||||
{% if order.status == 'Approved' %}
|
|
||||||
<strong class="text-success">
|
|
||||||
{% trans "Approved" %}
|
|
||||||
</strong>
|
|
||||||
{% else %}
|
|
||||||
<strong class="text-danger">
|
|
||||||
{% trans "Declined" %}
|
|
||||||
</strong>
|
|
||||||
{% endif %}
|
|
||||||
<br><br>
|
|
||||||
{% endif %}
|
|
||||||
</address>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-md-6">
|
|
||||||
<address>
|
|
||||||
<h3><b>{% trans "Billed To:"%}</b></h3>
|
|
||||||
{% if order %}
|
|
||||||
{{user.name}}<br>
|
|
||||||
{{order.billing_address.street_address}},{{order.billing_address.postal_code}}<br>
|
|
||||||
{{order.billing_address.city}},
|
|
||||||
{{order.billing_address.country}}.
|
|
||||||
{% else %}
|
|
||||||
{% with request.session.billing_address_data as billing_address %}
|
|
||||||
{{billing_address|get_value_from_dict:'cardholder_name'}}<br>
|
|
||||||
{{billing_address|get_value_from_dict:'street_address'}},
|
|
||||||
{{billing_address|get_value_from_dict:'postal_code'}}<br>
|
|
||||||
{{billing_address|get_value_from_dict:'city'}},
|
|
||||||
{{billing_address|get_value_from_dict:'country'}}.
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
</address>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-6">
|
|
||||||
<address>
|
|
||||||
<strong>{% trans "Payment Method:"%}</strong><br>
|
|
||||||
{% if order %}
|
|
||||||
{{order.cc_brand}} {% trans "ending in" %} ****
|
{{order.cc_brand}} {% trans "ending in" %} ****
|
||||||
{{order.last4}}<br>
|
{{order.last4}}<br>
|
||||||
{{user.email}}
|
{{user.email}}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{cc_brand}} {% trans "ending in" %} ****
|
{{cc_brand|default:'Card'}} {% trans "ending in" %} ****
|
||||||
{{cc_last4}}<br>
|
{{cc_last4}}<br>
|
||||||
{{request.session.user.email}}
|
{% if request.user.is_authenticated %}
|
||||||
|
{{request.user.email}}
|
||||||
|
{% else %}
|
||||||
|
{{request.session.user.email}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</address>
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<h4>{% trans "Order summary" %}</h4>
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Product" %}:</strong> {{vm.name}}
|
||||||
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
{% comment %}
|
||||||
|
<p>
|
||||||
|
<span>{% trans "Period" %}</span>
|
||||||
|
<span class="pull-right">{{}}</span>
|
||||||
|
</p>
|
||||||
|
{% endcomment %}
|
||||||
|
<p>
|
||||||
|
<span>{% trans "Cores" %}</span>
|
||||||
|
{% if vm.cores %}
|
||||||
|
<span class="pull-right">{{vm.cores|floatformat}}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="pull-right">{{vm.cpu|floatformat}}</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span>{% trans "Memory" %}</span>
|
||||||
|
<span class="pull-right">{{vm.memory}} GB</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span>{% trans "Disk space" %}</span>
|
||||||
|
<span class="pull-right">{{vm.disk_size}} GB</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span>{% trans "Total" %}</span>
|
||||||
|
<span class="pull-right">{{vm.price}} CHF</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-8 col-md-offset-2">
|
|
||||||
<h3><b>{% trans "Order summary"%}</b></h3>
|
|
||||||
<hr>
|
|
||||||
<div class="content">
|
|
||||||
{% if request.session.specs %}
|
|
||||||
{% with request.session.specs as vm %}
|
|
||||||
<p><b>{% trans "Cores"%}</b>
|
|
||||||
<span class="pull-right">{{vm.cpu}}</span>
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
<p><b>{% trans "Memory"%}</b>
|
|
||||||
<span class="pull-right">{{vm.memory}} GB</span>
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
<p><b>{% trans "Disk space"%}</b>
|
|
||||||
<span class="pull-right">{{vm.disk_size}} GB</span>
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
<p><b>{% trans "Configuration"%}</b>
|
|
||||||
<span class="pull-right">{{request.session.template.name}}</span>
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
<h4>{% trans "Total"%}
|
|
||||||
<p class="pull-right">
|
|
||||||
<b>{{vm.price}} CHF</b>
|
|
||||||
<span class="dcl-price-month"> /{% trans "Month" %}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</h4>
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
<p><b>{% trans "Cores"%}</b>
|
|
||||||
<span class="pull-right">{{vm.cores}}</span>
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
<p><b>{% trans "Memory"%}</b>
|
|
||||||
<span class="pull-right">{{vm.memory}} GB</span>
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
<p><b>{% trans "Disk space"%}</b>
|
|
||||||
<span class="pull-right">{{vm.disk_size}} GB</span>
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
<h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}}
|
|
||||||
CHF</b><span
|
|
||||||
class="dcl-price-month"> /{% trans "Month" %}</span>
|
|
||||||
</p></h4>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
{% if not order %}
|
{% if not order %}
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if not order %}
|
||||||
<form method="post" id="virtual_machine_create_form">
|
<form method="post" id="virtual_machine_create_form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<p class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</p>
|
<div class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 content">
|
<div class="col-sm-4 order-confirm-btn text-right">
|
||||||
<button class="btn btn-info pull-right"
|
<button class="btn choice-btn" id="btn-create-vm" data-href="{% url 'hosting:order-confirmation' %}" data-toggle="modal" data-target="#createvm-modal">
|
||||||
id="btn-create-vm"
|
{% trans "Place order" %}
|
||||||
data-href="{% url 'hosting:order-confirmation' %}"
|
|
||||||
data-toggle="modal"
|
|
||||||
data-target="#createvm-modal">
|
|
||||||
{% trans "Place order"%}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<!-- Create VM Modal -->
|
|
||||||
<div class="modal fade" id="createvm-modal" tabindex="-1" role="dialog"
|
{% if order %}
|
||||||
aria-hidden="true" data-backdrop="static" data-keyboard="false">
|
<div class="text-center" style="margin-bottom: 50px;">
|
||||||
<div class="modal-dialog">
|
<a class="btn btn-vm-back" href="{% url 'hosting:orders' %}">{% trans "BACK TO LIST" %}</a>
|
||||||
<div class="modal-content">
|
</div>
|
||||||
<div class="modal-header">
|
{% endif %}
|
||||||
<button type="button" class="close hidden" data-dismiss="modal"
|
|
||||||
aria-label="create-vm-close">
|
{% if not order %}
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Create VM Modal -->
|
||||||
</button>
|
<div class="modal fade" id="createvm-modal" tabindex="-1" role="dialog"
|
||||||
</div>
|
aria-hidden="true" data-backdrop="static" data-keyboard="false">
|
||||||
<div class="modal-body">
|
<div class="modal-dialog">
|
||||||
<div class="modal-icon">
|
<div class="modal-content">
|
||||||
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
|
<div class="modal-header">
|
||||||
<span class="sr-only">{% trans "Processing..." %}</span>
|
<button type="button" class="close hidden" data-dismiss="modal"
|
||||||
|
aria-label="create-vm-close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h4 class="modal-title" id="createvm-modal-title">
|
<div class="modal-body">
|
||||||
</h4>
|
<div class="modal-icon">
|
||||||
<div class="modal-text" id="createvm-modal-body">
|
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
|
||||||
{% trans "Hold tight, we are processing your request" %}
|
<span class="sr-only">{% trans "Processing..." %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<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">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- / Create VM Modal -->
|
||||||
<!-- / Create VM Modal -->
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
{% trans "Some problem encountered. Please try again later." as err_msg %}
|
{% trans "Some problem encountered. Please try again later." as err_msg %}
|
||||||
var create_vm_error_message = '{{err_msg|safe}}.';
|
var create_vm_error_message = '{{err_msg|safe}}';
|
||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
var locale_date = moment.utc(document.getElementById("order-created_at").textContent, 'YYYY-MM-DD HH:mm').toDate();
|
var locale_date = moment.utc(document.getElementById("order-created_at").textContent, 'YYYY-MM-DD HH:mm').toDate();
|
||||||
|
@ -211,7 +194,12 @@
|
||||||
document.getElementById('order-created_at').innerHTML = locale_date;
|
document.getElementById('order-created_at').innerHTML = locale_date;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{%endblock%}
|
{%endblock%}
|
||||||
|
|
||||||
|
{% block js_extra %}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.5/jspdf.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
|
||||||
|
<script src="{% static 'hosting/js/html2pdf.js' %}"></script>
|
||||||
|
<script src="{% static 'hosting/js/order.js' %}"></script>
|
||||||
|
{% endblock js_extra %}
|
|
@ -41,13 +41,15 @@
|
||||||
<td data-header="IPv6">{{vm.ipv6}}</td>
|
<td data-header="IPv6">{{vm.ipv6}}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td data-header="{% trans 'Status' %}">
|
<td data-header="{% trans 'Status' %}">
|
||||||
{% if vm.state == 'ACTIVE' %}
|
<strong>
|
||||||
<span class="vm-status-active"><strong>{{vm.state|title}}</strong></span>
|
{% if vm.state == 'ACTIVE' %}
|
||||||
{% elif vm.state == 'FAILED' %}
|
<span class="vm-status-active">{{vm.state|title}}</span>
|
||||||
<span class="vm-status-failed"><strong>{{vm.state|title}}</strong></span>
|
{% elif vm.state == 'FAILED' %}
|
||||||
{% else %}
|
<span class="vm-status-failed">{{vm.state|title}}</span>
|
||||||
<span class="vm-status-pending"><strong>{{vm.state|title}}</strong></span>
|
{% else %}
|
||||||
{% endif %}
|
<span class="vm-status">{{vm.state|title}}</span>
|
||||||
|
{% endif %}
|
||||||
|
</strong>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right last-td">
|
<td class="text-right last-td">
|
||||||
<a class="btn btn-vm-detail" href="{% url 'hosting:virtual_machines' vm.vm_id %}">{% trans "View Detail" %}</a>
|
<a class="btn btn-vm-detail" href="{% url 'hosting:virtual_machines' vm.vm_id %}">{% trans "View Detail" %}</a>
|
||||||
|
|
175
hosting/views.py
175
hosting/views.py
|
@ -3,10 +3,12 @@ import logging
|
||||||
import uuid
|
import uuid
|
||||||
from time import sleep
|
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
|
||||||
|
|
||||||
|
@ -30,7 +32,7 @@ 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.mailer import BaseEmail
|
from utils.mailer import BaseEmail
|
||||||
|
@ -42,6 +44,8 @@ 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__)
|
||||||
|
|
||||||
|
@ -647,7 +651,7 @@ class OrdersHostingDetailView(LoginRequiredMixin,
|
||||||
model = HostingOrder
|
model = HostingOrder
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return HostingOrder.objects.filter(
|
return HostingOrder.objects.get(
|
||||||
pk=self.kwargs.get('pk')) if self.kwargs.get('pk') else None
|
pk=self.kwargs.get('pk')) if self.kwargs.get('pk') else None
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -655,54 +659,79 @@ class OrdersHostingDetailView(LoginRequiredMixin,
|
||||||
context = super(DetailView, self).get_context_data(**kwargs)
|
context = super(DetailView, self).get_context_data(**kwargs)
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
owner = self.request.user
|
owner = self.request.user
|
||||||
if 'specs' not in self.request.session:
|
|
||||||
return HttpResponseRedirect(
|
|
||||||
reverse('hosting:create_virtual_machine'))
|
|
||||||
if 'token' not in self.request.session:
|
|
||||||
return HttpResponseRedirect(reverse('hosting:payment'))
|
|
||||||
stripe_customer_id = self.request.session.get('customer')
|
stripe_customer_id = self.request.session.get('customer')
|
||||||
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
|
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
|
||||||
stripe_utils = StripeUtils()
|
stripe_utils = StripeUtils()
|
||||||
card_details = stripe_utils.get_card_details(customer.stripe_id,
|
if customer:
|
||||||
self.request.session.get(
|
card_details = stripe_utils.get_card_details(
|
||||||
'token'))
|
customer.stripe_id,
|
||||||
if not card_details.get('response_object'):
|
self.request.session.get('token')
|
||||||
msg = card_details.get('error')
|
)
|
||||||
messages.add_message(self.request, messages.ERROR, msg,
|
else:
|
||||||
extra_tags='failed_payment')
|
card_details = {}
|
||||||
return HttpResponseRedirect(
|
|
||||||
reverse('hosting:payment') + '#payment_error')
|
|
||||||
|
|
||||||
if self.request.GET.get('page', '') == 'payment':
|
if self.request.GET.get('page') == 'payment':
|
||||||
context['page_header_text'] = _('Confirm Order')
|
context['page_header_text'] = _('Confirm Order')
|
||||||
else:
|
else:
|
||||||
context['page_header_text'] = _('Invoice')
|
context['page_header_text'] = _('Invoice')
|
||||||
|
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
|
# invoice for previous order
|
||||||
try:
|
try:
|
||||||
manager = OpenNebulaManager(email=owner.email,
|
manager = OpenNebulaManager(
|
||||||
password=owner.password)
|
email=owner.email, password=owner.password
|
||||||
|
)
|
||||||
vm = manager.get_vm(obj.vm_id)
|
vm = manager.get_vm(obj.vm_id)
|
||||||
context['vm'] = VirtualMachineSerializer(vm).data
|
context['vm'] = VirtualMachineSerializer(vm).data
|
||||||
except WrongIdError:
|
except WrongIdError:
|
||||||
messages.error(self.request,
|
messages.error(
|
||||||
'The VM you are looking for is unavailable at the moment. \
|
self.request,
|
||||||
Please contact Data Center Light support.'
|
_('The VM you are looking for is unavailable at the '
|
||||||
)
|
'moment. Please contact Data Center Light support.')
|
||||||
|
)
|
||||||
self.kwargs['error'] = 'WrongIdError'
|
self.kwargs['error'] = 'WrongIdError'
|
||||||
context['error'] = 'WrongIdError'
|
context['error'] = 'WrongIdError'
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
messages.error(self.request,
|
messages.error(
|
||||||
'In order to create a VM, you need to create/upload your SSH KEY first.'
|
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
|
||||||
|
context['card_details'] = card_details
|
||||||
else:
|
else:
|
||||||
|
# new order, confirm payment
|
||||||
context['site_url'] = reverse('hosting:create_virtual_machine')
|
context['site_url'] = reverse('hosting:create_virtual_machine')
|
||||||
context['cc_last4'] = card_details.get('response_object').get(
|
context['cc_last4'] = card_details.get('response_object').get(
|
||||||
'last4')
|
'last4')
|
||||||
context['cc_brand'] = card_details.get('response_object').get(
|
context['cc_brand'] = card_details.get('response_object').get(
|
||||||
'cc_brand')
|
'cc_brand')
|
||||||
|
context['vm'] = self.request.session.get('specs')
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if not self.kwargs.get('pk'):
|
||||||
|
if 'specs' not in self.request.session:
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse('hosting:create_virtual_machine')
|
||||||
|
)
|
||||||
|
if 'token' not in self.request.session:
|
||||||
|
return HttpResponseRedirect(reverse('hosting:payment'))
|
||||||
|
self.object = self.get_object()
|
||||||
|
context = self.get_context_data(object=self.object)
|
||||||
|
if 'failed_payment' in context:
|
||||||
|
msg = context['card_details'].get('error')
|
||||||
|
messages.add_message(
|
||||||
|
self.request, messages.ERROR, msg,
|
||||||
|
extra_tags='failed_payment'
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse('hosting:payment') + '#payment_error'
|
||||||
|
)
|
||||||
|
return self.render_to_response(context)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
template = request.session.get('template')
|
template = request.session.get('template')
|
||||||
specs = request.session.get('specs')
|
specs = request.session.get('specs')
|
||||||
|
@ -844,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
|
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'))
|
return redirect(reverse('hosting:payment'))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue