merged master

This commit is contained in:
Arvind Tiwari 2017-09-26 03:06:12 +05:30
commit 050f750322
34 changed files with 1918 additions and 551 deletions

View file

@ -1,3 +1,16 @@
1.2.3: 2017-09-25
* #3484: [dcl, hosting] Refactored account activation, password reset, VM order and cancellation email
* #3731: [dcl, hosting] Added cdist ssh key handler
* #3628: [dcl] on hosting, VM is created at credit card info submit
* #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
* #3728: [hosting] VM Termination animation added
* #3777: [hosting] Create new VM calculator added like dcl landing
* #3781: [hosting] Resend activation mail
* #3806: [hosting] Fix can not create VMs after password reset
* 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

View file

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

View file

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

View file

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

View file

@ -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 += [

View file

@ -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-23 02:19+0530\n" "POT-Creation-Date: 2017-09-24 12:34+0000\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"
@ -28,10 +28,10 @@ msgid "User does not exist"
msgstr "Der Benutzer existiert nicht" msgstr "Der Benutzer existiert nicht"
msgid "Paste here your public key" msgid "Paste here your public key"
msgstr "Füge deinen Public Key ein" msgstr "Füge Deinen Public Key ein"
msgid "Give a name to your key" msgid "Give a name to your key"
msgstr "Gebe deinem SSH-Key einen Name" msgstr "Gebe Deinem SSH-Key einen Name"
msgid "Key name" msgid "Key name"
msgstr "Key-Name" msgstr "Key-Name"
@ -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"
@ -135,10 +156,10 @@ msgid "Upload"
msgstr "Hochladen" msgstr "Hochladen"
msgid "Your VM hosted in Switzerland" msgid "Your VM hosted in Switzerland"
msgstr "deine VM in der Schweiz" msgstr "Deine VM in der Schweiz"
msgid "Set your new password" msgid "Set your new password"
msgstr "Setze dein neues Passwort" msgstr "Setze Dein neues Passwort"
msgid "Reset" msgid "Reset"
msgstr "Zurücksetzen" msgstr "Zurücksetzen"
@ -149,35 +170,17 @@ 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"
msgid "My SSH Keys" msgid "My SSH Keys"
msgstr "Meine SSH Keys" msgstr "Meine SSH-Keys"
msgid "My Bills" msgid "My Bills"
msgstr "Meine Rechnungen" msgstr "Meine Rechnungen"
@ -229,6 +232,40 @@ msgstr "Dankeschön!"
msgid "Virtual Machine Cancellation" msgid "Virtual Machine Cancellation"
msgstr "VM Kündigung" msgstr "VM Kündigung"
#, python-format
msgid ""
"\n"
"You're receiving this email because you requested a password reset for your "
"user account at %(site_name)s.<br/>\n"
"Please go to the following page and choose a new password: %(base_url)s"
"%(password_reset_url)s<br/>\n"
"If you didn't request a new password, ignore this e-mail.<br/>\n"
"Thank you!\n"
msgstr ""
"\n"
"Du erhälst diese E-Mail da Du Dein Passwort für Deinen Account bei "
"%(site_name)s zurücksetzen möchtest.<br/>\n"
"Bitte folge diesem Link und wähle ein neues Passwort: %(base_url)s"
"%(password_reset_url)s Solltest Du kein neues Passwort angefordert haben, "
"dann ignoriere diese E-Mail.<br/>\n"
"Dankeschön!\n"
#, python-format
msgid ""
"You're receiving this email because you requested a password reset for your "
"user account at %(site_name)s.\n"
"Please go to the following page and choose a new password: %(base_url)s"
"%(password_reset_url)s\n"
"If you didn't request a new password, ignore this e-mail.\n"
"Thank you!\n"
msgstr ""
"Du erhälst diese E-Mail da Du Dein Passwort für Deinen Account bei "
"%(site_name)s zurücksetzen möchtest.\n"
"Bitte folge diesem Link und wähle ein neues Passwort: %(base_url)s"
"%(password_reset_url)s Solltest Du kein neues Passwort angefordert haben, "
"dann ignoriere diese E-Mail.\n"
"Dankeschön!\n"
#, python-format #, python-format
msgid "" msgid ""
"You are receiving this email because your virutal machine [%(vm_name)s] has " "You are receiving this email because your virutal machine [%(vm_name)s] has "
@ -246,11 +283,29 @@ msgstr "NEUE VM"
msgid "You can always order a new VM by following the link below." msgid "You can always order a new VM by following the link below."
msgstr "" msgstr ""
#, python-format
msgid ""
"You're receiving this mail because your virtual machine [%(vm_name)s] has "
"been cancelled.\n"
"You can see your order status by clicking here\n"
"%(base_url)s%(vm_order_url)s\n"
"If you want to order a new virtual machine, you can do it by clicking this "
"link.\n"
"%(base_url)s%(my_virtual_machines_url)s\n"
msgstr ""
"Du erhälst diese E-Mail, da Deine virtuelle Maschine [%(vm_name)s] gekündigt "
"wurde.\n"
"Um Deinen Auftragsstatus zu sehen, klicke hier.\n"
"%(base_url)s%(vm_order_url)s\n"
"Falls Du eine neue virtuelle Maschine bestellen möchtest, kannst Du dies "
"tun, indem Du diesen Link klickst.\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"
@ -264,6 +319,9 @@ msgstr "Registrieren"
msgid "Forgot your password ? " msgid "Forgot your password ? "
msgstr "Passwort vergessen?" msgstr "Passwort vergessen?"
msgid "Resend activation link"
msgstr "Aktivierungslink noch einmal senden"
msgid "Notifications" msgid "Notifications"
msgstr "Benachrichtigungen" msgstr "Benachrichtigungen"
@ -282,10 +340,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"
@ -294,10 +356,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"
@ -306,6 +368,9 @@ msgstr "endend in"
msgid "Order summary" msgid "Order summary"
msgstr "Bestellungsübersicht" msgstr "Bestellungsübersicht"
msgid "Product"
msgstr "Produkt"
msgid "Cores" msgid "Cores"
msgstr "Prozessorkerne" msgstr "Prozessorkerne"
@ -315,15 +380,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 "
@ -333,7 +392,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..."
@ -347,14 +409,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 ""
@ -365,6 +427,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"
@ -512,6 +577,9 @@ msgstr "Aktueller Preis"
msgid "Your VM is" msgid "Your VM is"
msgstr "Deine VM ist" msgstr "Deine VM ist"
msgid "Terminating"
msgstr "Beenden"
msgid "Pending" msgid "Pending"
msgstr "In Vorbereitung" msgstr "In Vorbereitung"
@ -524,6 +592,11 @@ msgstr "Fehlgeschlagen"
msgid "Terminate VM" msgid "Terminate VM"
msgstr "VM Beenden" msgstr "VM Beenden"
msgid "Sorry, there was an unexpected error. Kindly retry."
msgstr ""
"Bitte entschuldige, es scheint ein unerwarteter Fehler aufgetreten zu sein. "
"Versuche es doch bitte noch einmal."
msgid "Something doesn't work?" msgid "Something doesn't work?"
msgstr "Etwas funktioniert nicht?" msgstr "Etwas funktioniert nicht?"
@ -531,10 +604,7 @@ msgid "We are here to help you!"
msgstr "Wir sind hier, um Dir zu helfen!" msgstr "Wir sind hier, um Dir zu helfen!"
msgid "CONTACT" msgid "CONTACT"
msgstr "KONTACT" msgstr "KONTAKT"
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"
@ -545,6 +615,14 @@ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
msgid "OK" msgid "OK"
msgstr "" msgstr ""
#, python-format
msgid ""
"Your Virtual Machine <strong>%(machine_name)s</strong> is successfully "
"terminated!"
msgstr ""
"Deine Virtuelle Machine (VM) <strong>%(machine_name)s</strong> wurde erfolgreich "
"beendet!"
msgid "Virtual Machines" msgid "Virtual Machines"
msgstr "Virtuelle Maschinen" msgstr "Virtuelle Maschinen"
@ -579,13 +657,13 @@ msgid "Sorry. Your request is invalid."
msgstr "Entschuldigung, deine Anfrage ist ungültig." msgstr "Entschuldigung, deine Anfrage ist ungültig."
msgid "Password has been reset." msgid "Password has been reset."
msgstr "" msgstr "Dein Passwort wurde erfolgreich zurückgesetzt."
msgid "Password reset has not been successful." msgid "Password reset has not been successful."
msgstr "" msgstr "Dein Passwort konnte nicht zurückgesetzt werden."
msgid "The reset password link is no longer valid." msgid "The reset password link is no longer valid."
msgstr "" msgstr "Der Link zum Zurücksetzen Deines Passwortes ist nicht mehr gültig."
msgid "Invalid credit card" msgid "Invalid credit card"
msgstr "Ungültige Kreditkarte" msgstr "Ungültige Kreditkarte"
@ -593,6 +671,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."
@ -603,9 +690,14 @@ msgstr ""
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
"auf sie zugreifen kannst." "auf sie zugreifen kannst."
msgid "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 "
@ -616,6 +708,42 @@ msgstr "Kontaktiere den Data Center Light Support."
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 "Terminated"
msgstr "Beendet"
msgid "Error terminating VM"
msgstr "Fehler beenden VM"
msgid "Virtual Machine Cancellation"
msgstr "VM Kündigung"
#~ msgid "Close"
#~ msgstr "Schliessen"
#~ msgid "VM %(VM_ID)s terminated successfully"
#~ msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ msgid "days"
#~ msgstr "Tage"
#~ msgid "New Virtual Machine"
#~ msgstr "Neue virtuelle Maschine"
#~ msgid "Step 1. Select VM Template:"
#~ msgstr "Wähle eine Vorlage"
#~ msgid "Step2. Select VM Configuration"
#~ msgstr "Wähle eine Konfiguration"
#~ msgid "Price "
#~ msgstr "Preis"
#~ msgid "CHF/Month"
#~ msgstr "CHF/Monat"
#~ msgid "Start VM"
#~ msgstr "VM jetzt starten"
#~ msgid "View Invoice" #~ msgid "View Invoice"
#~ msgstr "Zur Rechnung" #~ msgstr "Zur Rechnung"
@ -693,7 +821,7 @@ msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ msgstr "Meine Bestellungen" #~ msgstr "Meine Bestellungen"
#~ msgid "SSH Keys" #~ msgid "SSH Keys"
#~ msgstr "SSH Key" #~ msgstr "SSH Keys"
#~ msgid "Notifications " #~ msgid "Notifications "
#~ msgstr "Benachrichtigungen" #~ msgstr "Benachrichtigungen"
@ -734,9 +862,6 @@ msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ msgid "Ipv6" #~ msgid "Ipv6"
#~ msgstr "IPv6" #~ msgstr "IPv6"
#~ msgid "Close"
#~ msgstr "Schliessen"
#~ msgid "Cancel" #~ msgid "Cancel"
#~ msgstr "Beenden" #~ msgstr "Beenden"
@ -744,19 +869,19 @@ msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ msgstr "Hinzufügen" #~ msgstr "Hinzufügen"
#~ msgid "Keys" #~ msgid "Keys"
#~ msgstr "Schlüssel" #~ msgstr "Keys"
#~ msgid "Log in" #~ msgid "Log in"
#~ msgstr "Anmelden" #~ msgstr "Anmelden"
#~ msgid "You haven been logged out" #~ msgid "You haven been logged out"
#~ msgstr "Sie wurden abgmeldet" #~ msgstr "Du wurdest abgemeldet"
#~ msgid "How it works" #~ msgid "How it works"
#~ msgstr "So funktioniert es" #~ msgstr "So funktioniert es"
#~ msgid "Your infrastructure" #~ msgid "Your infrastructure"
#~ msgstr "deine Infrastruktur" #~ msgstr "Deine Infrastruktur"
#~ msgid "Our inftrastructure" #~ msgid "Our inftrastructure"
#~ msgstr "Unsere Infrastruktur" #~ msgstr "Unsere Infrastruktur"
@ -765,10 +890,10 @@ msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ msgstr "Preise" #~ msgstr "Preise"
#~ msgid "Access Key" #~ msgid "Access Key"
#~ msgstr "Zugriffsschlüssel" #~ msgstr "SSH Key"
#~ msgid "Upload your own key. " #~ msgid "Upload your own key. "
#~ msgstr "Lade deinen Key hoch" #~ msgstr "Lade Deinen Key hoch"
#~ msgid "Generate Key Pair" #~ msgid "Generate Key Pair"
#~ msgstr "Schlüsselpaar generieren" #~ msgstr "Schlüsselpaar generieren"
@ -792,5 +917,5 @@ msgstr "VM %(VM_ID)s erfolgreich beendet"
#~ "Your SSH private key was already generated and downloaded, if you lost " #~ "Your SSH private key was already generated and downloaded, if you lost "
#~ "it, contact us. " #~ "it, contact us. "
#~ msgstr "" #~ msgstr ""
#~ "Dein privater SSH Schlüssel wurde bereits generiert und heruntergeladen. " #~ "Dein privater SSH Key wurde bereits generiert und heruntergeladen. "
#~ "Falls du ihn verloren hast, kontaktiere uns." #~ "Falls Du ihn verloren hast, kontaktiere uns."

View file

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

View file

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

View file

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

View file

@ -0,0 +1,237 @@
/* Create VM calculator */
.price-calc-section {
padding: 80px 40px !important;
}
@media (max-width: 768px) {
.price-calc-section {
margin-top: 40px;
}
}
.price-calc-section .text {
width: 50%;
}
.price-calc-section .text .section-heading {
font-size: 48px;
line-height: 48px;
padding-bottom: 27px;
color: #3a3a3a;
letter-spacing: 1px;
position: relative;
text-align: right;
}
.price-calc-section .text .description {
font-size: 20px;
text-align: right;
}
.price-calc-section .text .section-heading::before {
content: "";
position: absolute;
bottom: 0;
background: #29427A;
height: 7px;
width: 70px;
right: 0;
}
.price-calc-section .card {
width: 50%;
margin: 0 auto;
background: #fff;
box-shadow: 1px 3px 6px 2px rgba(0, 0, 0, 0.2);
padding-bottom: 30px;
text-align: center;
max-width: 320px;
position: relative;
}
@media (min-width: 768px) {
.price-calc-section .card {
margin-left: 0;
}
}
.price-calc-section .landing {
width: 100% !important;
}
.no-padding {
padding: 0 !important;
}
.price-calc-section .card .img-beta {
position: absolute;
top: 5px;
width: 60px;
left: 3px;
}
.price-calc-section .card .title {
padding: 15px 40px;
}
.price-calc-section .card .title h3 {
/*font-family: 'Lato', sans-serif;*/
font-weight: normal;
}
.price-calc-section .card .price {
background: #5A74AF;
padding: 22px;
color: #fff;
font-size: 32px;
}
.price-calc-section .card .price .price-text {
font-size: 14px;
}
.price-calc-section .card .description {
padding: 7px 8px 2px;
position: relative;
display: flex;
justify-content: space-around !important;
align-items: center !important;
}
.price-calc-section .card .description span {
font-size: 14px;
margin-left: 5px;
/* margin-left: 0px; */
/* justify-self: start; */
width: 29%;
text-align: left;
line-height: 16px;
/* font-weight: normal; */
}
.price-calc-section .card .description .select-number{
font-size: 16px;
text-align: center;
width: 85px;
}
.price-calc-section .card .description i {
color: #29427a;
cursor: pointer;
font-size: 20px;
border: 1px solid #ccc;
padding: 5px 6px 3px;
border-radius: 5px;
}
.price-calc-section .card .description .left {
margin-right: 7px;
}
.price-calc-section .card .description .right {
margin-left: 7px;
}
.price-calc-section .card .descriptions {
padding: 10px;
}
.price-calc-section .card .description p {
margin: 0;
}
.price-calc-section .card .btn {
margin-top: 15px;
font-size: 20px;
width: 150px;
border: none;
}
.price-calc-section .card .select-configuration select {
outline: none;
background: #fff;
border-color: #d0d0d0;
height: 32px;
width: 150px;
text-align: center;
font-size: 14px;
margin-left: 10px;
padding: 6px;
border-radius: 4px;
}
.price-calc-section .card .check-ip {
font-size: 18px;
}
.price-calc-section .card .justify-center {
justify-content: center !important;
}
.price-calc-section .card .description.input label {
font-size: 15px;
font-weight: 700;
/*font-weight: 800;*/
/*font-family: 'Lato';*/
margin-bottom: 0;
width: 40px;
}
/*Changed class****.price-calc-section .card .description.input input*/
.price-calc-section .card .description input {
width: 200px;
font-size: 14px;
text-align: left;
padding: 4px 10px;
border-radius: 4px;
border: 1px solid #d0d0d0;
background: #fff;
margin-left: 10px;
}
.price-calc-section .card .check-ip input[type=checkbox] {
font-size: 17px;
margin: 0 8px;
}
.price-calc-section .help-block.with-errors {
text-align: center;
margin: 0 0;
padding: 0 0 5px;
}
.price-calc-section .help-block.with-errors ul {
margin-bottom: 0;
}
.price-calc-section .form-group {
margin: 0;
position: relative;
}
.price-calc-section .form-group:after {
content: ' ';
display: block;
position: absolute;
bottom: 0;
left: 18%;
z-index: 20;
height: 1px;
width: 65%;
background: rgba(128, 128, 128, 0.2);
}
.price-calc-section .btn-primary {
background: #29427A;
border-color: #29427A;
color: #fff;
width: auto;
}
@media(min-width: 768px) {
.create-vm-container {
padding-top: 120px;
}
}

View file

@ -290,6 +290,11 @@
text-align: center; text-align: center;
} }
.vm-vmid .alert {
margin-top: 15px;
margin-bottom: -60px;
}
.vm-item-lg { .vm-item-lg {
font-size: 22px; font-size: 22px;
margin-top: 5px; margin-top: 5px;
@ -305,6 +310,10 @@
color: #e47f2f; color: #e47f2f;
} }
.vm-color-failed {
color: #eb4d5c;
}
.vm-detail-item .value{ .vm-detail-item .value{
font-weight: 400; font-weight: 400;
} }
@ -512,7 +521,7 @@
color: #87B6EA; color: #87B6EA;
} }
.vm-status, .vm-status-active, .vm-status-failed { .vm-status, .vm-status-active, .vm-status-failed, .vm-status-pending {
font-weight: 600; font-weight: 600;
} }
.vm-status-active { .vm-status-active {
@ -521,6 +530,9 @@
.vm-status-failed { .vm-status-failed {
color: #eb4d5c; color: #eb4d5c;
} }
.vm-status-pending {
color: #e47f2f;
}
@media (min-width:768px) { @media (min-width:768px) {
.dashboard-subtitle { .dashboard-subtitle {
@ -628,3 +640,27 @@
right: 8px; right: 8px;
} }
} }
.processing > .btn {
position: relative;
border-color: #eee;
}
.processing > .btn:hover,
.processing > .btn:focus,
.processing > .btn:active {
border-color: #eee;
}
.processing > .btn:after {
content: ' ';
display: block;
position: absolute;
background-image: url('/static/hosting/img/ajax-loader.gif');
background-repeat: no-repeat;
background-position: center;
background-color: #eee;
width: 100%;
top: 0;
height: 100%;
left: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

View file

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

View 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

View file

@ -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();
});
$(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:{ 'ram': {
items:2, 'id': 'ramValue',
nav:true 'value': 2,
'min': 2,
'max': 200,
'interval': 1
}, },
768:{ 'storage': {
items:3, 'id': 'storageValue',
nav:true 'value': 10,
}, 'min': 10,
990:{ 'max': 2000,
items:4, 'interval': 10
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);

View file

@ -0,0 +1,73 @@
(function($){
"use strict"; // Start of use strict
$(window).load(function(){
});
$(document).ready(function(){
_initOs();
});
$(window).resize(function(){
});
function _initOs(){
$('.os-circle').click(function(event){
$('.os-circle').removeClass('active');
$(this).addClass('active');
var idTemplate = $(this).data('id');
$('input[name=vm_template_id]').val(idTemplate);
});
$('.config-box').click(function(event){
$('.config-box').removeClass('active');
$(this).addClass('active');
var idConfig = $(this).data('id');
var price = $(this).data('price');
$('input[name=configuration]').val(idConfig);
$('.container-button').fadeIn();
$('#priceValue').text(price);
});
$('.owl-carousel').owlCarousel({
items:4,
nav: true,
margin:30,
responsiveClass:true,
navText: ['<i class="fa fa-angle-left"></i>', '<i class="fa fa-angle-right"></i>'],
responsive:{
0:{
items:1,
nav:true
},
600:{
items:2,
nav:true
},
768:{
items:3,
nav:true
},
990:{
items:4,
nav:true
}
}
});
}
})(jQuery);

View file

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

View 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();
});
});

View file

@ -1,4 +1,85 @@
$(document).ready(function () { function VMTerminateStatus($container, url) {
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
success: function(data) {
VMTerminateSuccess($container, data);
},
error: function() {
setTimeout(function(){
VMTerminateStatus($container, url);
}, 5000);
}
});
}
function VMTerminateActive($container, altText) {
$container.find('.alert-danger').addClass('hide');
$container.addClass('processing')
.find('.vm-item-lg').attr('class', '')
.addClass('vm-item-lg vm-color-failed')
.text(altText);
$container.find('.btn').prop('disabled', true);
$('#confirm-cancel').modal('hide');
}
function VMTerminateSuccess($container, data) {
$container.addClass('terminate-success')
.find('.vm-item-lg').text(data.text);
$container.find('.btn').remove();
$('#terminate-success').modal('show');
}
function VMTerminateFail($container, data, text) {
$container.addClass('terminate-fail')
.find('.vm-item-lg').text(text);
$container.find('.btn').prop('disabled', false);
$container.find('.alert-danger').text(data.text).removeClass('hide');
$container.removeClass('processing');
}
$(document).ready(function() {
$('#confirm-cancel').on('click', '.btn-ok', function(e) {
var url = $('#virtual_machine_cancel_form').attr('action');
var $container = $('#terminate-VM');
var text = $container.find('.vm-item-lg').text();
var altText = $container.attr('data-alt');
VMTerminateActive($container, altText);
$.post(url)
.done(function(data) {
if (data.status == true) {
VMTerminateSuccess($container, data);
} else {
if ('text' in data) {
VMTerminateFail($container, data, text);
} else {
VMTerminateStatus($container, url);
}
}
})
.fail(function(data) {
if (data.status==504) {
VMTerminateStatus($container, url);
} else {
VMTerminateFail($container, data, text);
}
})
});
var hash = window.location.hash;
hash && $('ul.nav a[href="' + hash + '"]').tab('show');
$('.nav-tabs a').click(function(e) {
$(this).tab('show');
var scrollmem = $('body').scrollTop() || $('html').scrollTop();
window.location.hash = this.hash;
$('html,body').scrollTop(scrollmem);
});
$('.modal-text').removeClass('hide');
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);
@ -22,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);
} }
@ -30,19 +112,4 @@ $(document).ready(function () {
}); });
return false; return false;
}); });
$('#confirm-cancel').on('click', '.btn-ok', function (e) {
$('#virtual_machine_cancel_form').trigger('submit');
});
var hash = window.location.hash;
hash && $('ul.nav a[href="' + hash + '"]').tab('show');
$('.nav-tabs a').click(function (e) {
$(this).tab('show');
var scrollmem = $('body').scrollTop() || $('html').scrollTop();
window.location.hash = this.hash;
$('html,body').scrollTop(scrollmem);
});
}); });

View file

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

View file

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

View file

@ -1,12 +1,12 @@
{% extends "hosting/base_short.html" %} {% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 i18n %} {% load staticfiles bootstrap3 i18n %}
{% block content %}
<div>
<div class="dashboard-container" >
<div class="row">
<div class="col-md-12"> {% block content %}
<br/> <div class="dashboard-container create-vm-container">
<div class="row">
<div class="col-sm-5">
<div class="dashboard-container-head">
<h3 class="dashboard-title-thin"><img src="{% static 'hosting/img/plusVM.svg' %}" class="un-icon" style="margin-top: -18px;width: 42px;height: 42px;"> {% trans "Create VM" %}</h3>
{% if messages %} {% if messages %}
<div class="alert alert-warning"> <div class="alert alert-warning">
{% for message in messages %} {% for message in messages %}
@ -15,56 +15,16 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if not error %}
<div class="dashboard-title">
<h3>{% trans "New Virtual Machine"%} </h3>
<hr/>
</div> </div>
<div class="col-sm-6">
<form method="POST" action=""> <div class="price-calc-section no-padding">
{% csrf_token %} <div class="landing card">
<div class="caption">
<div class="step-title"> {% include "hosting/calculator_form.html" %}
<h4>{% trans "Step 1. Select VM Template:" %} </h4> </div>
</div> </div>
<div class="parent-container"> </div>
<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 %}
</div> </div>
</div> </div>
</div> </div>
{%endblock%} {%endblock%}

View file

@ -44,6 +44,8 @@
<a class="unlink" href="{% url 'hosting:signup' %}">{% trans "Sign up"%}</a> <a class="unlink" href="{% url 'hosting:signup' %}">{% trans "Sign up"%}</a>
<span class="text"> or </span> <span class="text"> or </span>
<a class="unlink" href="{% url 'hosting:reset_password' %}">{% trans "Forgot your password ? "%}</a> <a class="unlink" href="{% url 'hosting:reset_password' %}">{% trans "Forgot your password ? "%}</a>
<span class="text"> or </span><br/>
<a class="unlink" href="{% url 'hosting:resend_activation_link' %}">{% trans "Resend activation link"%}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -4,175 +4,158 @@
{% 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="col-xs-12 col-md-8 col-md-offset-2">
<br/>
<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> </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">
{% if order %} <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>
{% trans "Order #"%} {{order.id}} <button type="button" class="btn-plain btn-print"><img src="{% static 'hosting/img/icon-print.svg' %}" class="svg-img"></button>
{% endif %}
</h3>
</div> </div>
<hr> </div>
<div class="row"> <div class="order-details">
<div class="col-xs-12 col-md-6 pull-right order-confirm-date"> {% if order %}
<address> <p>
<strong>{% trans "Date"%}:</strong><br> <strong>{% trans "Order #" %} {{order.id}}</strong>
</p>
{% endif %}
<p>
<strong>{% trans "Invoice Date" %}:</strong>
<span id="order-created_at"> <span id="order-created_at">
{% if order %} {% if order %}
{{order.created_at|date:'Y-m-d H:i'}} {{order.created_at|date:'Y-m-d H:i'}}
{% else %} {% else %}
{% now "Y-m-d H:i" %} {% now "Y-m-d H:i" %}
{% endif %} {% endif %}
</span><br><br> </span>
</p>
{% if order %} {% if order %}
<strong>{% trans "Status:"%}</strong><br> <p>
<strong>{% trans "Status" %}: </strong>
<strong>
{% if order.status == 'Approved' %} {% if order.status == 'Approved' %}
<strong class="text-success"> <span class="vm-color-online">{% trans "Approved" %}</span>
{% trans "Approved" %}
</strong>
{% else %} {% else %}
<strong class="text-danger"> <span class="vm-status-failed">{% trans "Declined" %}</span>
{% trans "Declined" %} {% endif %}
</strong> </strong>
</p>
{% endif %} {% endif %}
<br><br> <hr>
{% endif %} <div>
</address>
</div>
<div class="col-xs-12 col-md-6">
<address> <address>
<h3><b>{% trans "Billed To:"%}</b></h3> <h4>{% trans "Billed to" %}:</h4>
<p>
{% if order %} {% if order %}
{{user.name}}<br> {{user.name}}<br>
{{order.billing_address.street_address}},{{order.billing_address.postal_code}}<br> {{order.billing_address.street_address}}, {{order.billing_address.postal_code}}<br>
{{order.billing_address.city}}, {{order.billing_address.city}}, {{order.billing_address.country}}
{{order.billing_address.country}}.
{% else %} {% else %}
{% with request.session.billing_address_data as billing_address %} {% with request.session.billing_address_data as billing_address %}
{{billing_address|get_value_from_dict:'cardholder_name'}}<br> {{billing_address.cardholder_name}}<br>
{{billing_address|get_value_from_dict:'street_address'}}, {{billing_address.street_address}}, {{billing_address.postal_code}}<br>
{{billing_address|get_value_from_dict:'postal_code'}}<br> {{billing_address.city}}, {{billing_address.country}}
{{billing_address|get_value_from_dict:'city'}},
{{billing_address|get_value_from_dict:'country'}}.
{% endwith %} {% endwith %}
{% endif %} {% endif %}
</p>
</address> </address>
</div> </div>
<hr>
</div> <div>
<div class="row"> <h4>{% trans "Payment method" %}:</h4>
<div class="col-xs-6"> <p>
<address>
<strong>{% trans "Payment Method:"%}</strong><br>
{% if order %} {% 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>
{% if request.user.is_authenticated %}
{{request.user.email}}
{% else %}
{{request.session.user.email}} {{request.session.user.email}}
{% endif %} {% endif %}
</address> {% endif %}
</p>
</div> </div>
</div> <hr>
</div> <div>
</div> <h4>{% trans "Order summary" %}</h4>
<p>
<strong>{% trans "Product" %}:</strong> {{vm.name}}
</p>
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-sm-6">
<h3><b>{% trans "Order summary"%}</b></h3> {% comment %}
<hr> <p>
<div class="content"> <span>{% trans "Period" %}</span>
{% if request.session.specs %} <span class="pull-right">{{}}</span>
{% with request.session.specs as vm %}
<p><b>{% trans "Cores"%}</b>
<span class="pull-right">{{vm.cpu}}</span>
</p> </p>
<hr> {% endcomment %}
<p><b>{% trans "Memory"%}</b> <p>
<span class="pull-right">{{vm.memory}} GB</span> <span>{% trans "Cores" %}</span>
</p> {% if vm.cores %}
<hr> <span class="pull-right">{{vm.cores|floatformat}}</span>
<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 %} {% else %}
<p><b>{% trans "Cores"%}</b> <span class="pull-right">{{vm.cpu|floatformat}}</span>
<span class="pull-right">{{vm.cores}}</span> {% endif %}
</p> </p>
<hr> <p>
<p><b>{% trans "Memory"%}</b> <span>{% trans "Memory" %}</span>
<span class="pull-right">{{vm.memory}} GB</span> <span class="pull-right">{{vm.memory}} GB</span>
</p> </p>
<hr> <p>
<p><b>{% trans "Disk space"%}</b> <span>{% trans "Disk space" %}</span>
<span class="pull-right">{{vm.disk_size}} GB</span> <span class="pull-right">{{vm.disk_size}} GB</span>
</p> </p>
<p>
<span>{% trans "Total" %}</span>
<span class="pull-right">{{vm.price}} CHF</span>
</p>
</div>
</div>
</div>
{% if not order %}
<hr> <hr>
<h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}}
CHF</b><span
class="dcl-price-month"> /{% trans "Month" %}</span>
</p></h4>
{% endif %} {% endif %}
</div> </div>
<br/>
{% if not order %} {% 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 %}
<div class="text-center" style="margin-bottom: 50px;">
<a class="btn btn-vm-back" href="{% url 'hosting:orders' %}">{% trans "BACK TO LIST" %}</a>
</div>
{% endif %}
{% if not order %}
<!-- Create VM Modal -->
<div class="modal fade" id="createvm-modal" tabindex="-1" role="dialog"
aria-hidden="true" data-backdrop="static" data-keyboard="false"> aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -197,13 +180,13 @@
</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 %}

View file

@ -0,0 +1,36 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3%}
{% load i18n %}
{% block navbar %}
{% include 'hosting/includes/_navbar_transparent.html' %}
{% endblock navbar %}
{% block content %}
<div class="auth-container">
<div class="auth-bg"></div>
<div class="auth-center">
<div class="auth-title">
<h2>{% trans "Your VM hosted in Switzerland"%}</h2>
</div>
<div class="auth-content">
<div class="intro-message auth-box sign-up">
<h2 class="section-heading">{% trans "Resend activation link"%}</h2>
<form action="{% url 'hosting:resend_activation_link' %}" method="post" class="form" novalidate>
{% csrf_token %}
{% for field in form %}
{% bootstrap_field field show_label=False %}
{% endfor %}
{% buttons %}
<button type="submit" class="btn btn-block btn-success">
{% trans "Submit"%}
</button>
{% endbuttons %}
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -53,22 +53,29 @@
<h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2> <h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2>
<div class="vm-vmid"> <div class="vm-vmid">
<div class="vm-item-subtitle">{% trans "Your VM is" %}</div> <div class="vm-item-subtitle">{% trans "Your VM is" %}</div>
<div id="terminate-VM" data-alt="{% trans 'Terminating' %}">
{% if virtual_machine.state == 'PENDING' %} {% if virtual_machine.state == 'PENDING' %}
<div class="vm-item-lg vm-color-pending">{% trans "Pending" %}</div> <div class="vm-item-lg vm-color-pending">{% trans "Pending" %}</div>
{% elif virtual_machine.state == 'ACTIVE' %} {% elif virtual_machine.state == 'ACTIVE' %}
<div class="vm-item-lg vm-color-online">{% trans "Online" %}</div> <div class="vm-item-lg vm-color-online">{% trans "Online" %}</div>
{% elif virtual_machine.state == 'FAILED'%} {% elif virtual_machine.state == 'FAILED'%}
<div class="vm-item-lg vm-color-failed">{% trans "Failed" %}</div> <div class="vm-item-lg vm-color-failed">{% trans "Failed" %}</div>
{% else %}
<div class="vm-item-lg"></div>
{% endif %} {% endif %}
{% if not virtual_machine.status == 'canceled' %} {% if not virtual_machine.status == 'canceled' %}
<form method="POST" id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}"> <form method="POST" id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}">
{% csrf_token %} {% csrf_token %}
</form> </form>
<button data-href="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-vm-term">{% trans "Terminate VM" %}</button> <button data-toggle="modal" data-target="#confirm-cancel" class="btn btn-vm-term">{% trans "Terminate VM" %}</button>
<div class="alert alert-danger hide">
{% trans "Sorry, there was an unexpected error. Kindly retry." %}
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="vm-contact-us"> <div class="vm-contact-us">
<div> <div>
<h2 class="vm-detail-title">{% trans "Support / Contact" %} <img class="un-icon visible-xs" src="{% static 'hosting/img/24-hours-support.svg' %}"></h2> <h2 class="vm-detail-title">{% trans "Support / Contact" %} <img class="un-icon visible-xs" src="{% static 'hosting/img/24-hours-support.svg' %}"></h2>
@ -96,7 +103,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div> <div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine"%}</h4> <h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine" %}</h4>
<div class="modal-text"> <div class="modal-text">
<p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p> <p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p>
<p><strong>{{virtual_machine.name}}</strong></p> <p><strong>{{virtual_machine.name}}</strong></p>
@ -109,4 +116,21 @@
</div> </div>
</div> </div>
<!-- / Cancel Modal --> <!-- / Cancel Modal -->
<!-- Success Modal -->
<div class="modal fade" id="terminate-success" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
</div>
<div class="modal-body">
<div class="modal-icon"><i class="fa fa-check" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine <strong>{{machine_name}}</strong> is successfully terminated!{% endblocktrans %}</h4>
<div class="modal-footer">
<a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a>
</div>
</div>
</div>
</div>
</div>
<!-- / Cancel Modal -->
{%endblock%} {%endblock%}

View file

@ -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' %}">
<strong>
{% if vm.state == 'ACTIVE' %} {% if vm.state == 'ACTIVE' %}
<span class="vm-status-active"><strong>{{vm.state|title}}</strong></span> <span class="vm-status-active">{{vm.state|title}}</span>
{% elif vm.state == 'FAILED' %} {% elif vm.state == 'FAILED' %}
<span class="vm-status-failed"><strong>{{vm.state|title}}</strong></span> <span class="vm-status-failed">{{vm.state|title}}</span>
{% else %} {% else %}
<span class="vm-status"><strong>{{vm.state|title}}</strong></span> <span class="vm-status">{{vm.state|title}}</span>
{% endif %} {% 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>

View file

@ -8,8 +8,7 @@ from .views import (
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView,
HostingPricingView, CreateVirtualMachinesView, HostingBillListView, HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView, HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView,
SSHKeyChoiceView, DashboardView, SettingsView) SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView)
urlpatterns = [ urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'), url(r'index/?$', IndexView.as_view(), name='index'),
@ -52,6 +51,8 @@ urlpatterns = [
url(r'signup/?$', SignupView.as_view(), name='signup'), url(r'signup/?$', SignupView.as_view(), name='signup'),
url(r'signup-validate/?$', SignupValidateView.as_view(), url(r'signup-validate/?$', SignupValidateView.as_view(),
name='signup-validate'), name='signup-validate'),
url(r'resend-activation-link/?$', ResendActivationEmailView.as_view(),
name='resend_activation_link'),
url(r'reset-password/?$', PasswordResetView.as_view(), url(r'reset-password/?$', PasswordResetView.as_view(),
name='reset_password'), name='reset_password'),
url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',

View file

@ -1,23 +1,27 @@
import json import json
import logging import logging
import uuid import uuid
from time import sleep
from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.http import Http404
from django.http import HttpResponseRedirect, HttpResponse from django.http import Http404, HttpResponseRedirect, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect, render
from django.shortcuts import render
from django.utils.http import urlsafe_base64_decode from django.utils.http import urlsafe_base64_decode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import get_language, ugettext_lazy as _
from django.views.generic import View, CreateView, FormView, ListView, \ from django.utils.translation import ugettext
DetailView, \ from django.views.generic import (
DeleteView, TemplateView, UpdateView View, CreateView, FormView, ListView, DetailView, DeleteView,
TemplateView, UpdateView
)
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from oca.pool import WrongIdError from oca.pool import WrongIdError
from stored_messages.api import mark_read from stored_messages.api import mark_read
@ -28,17 +32,23 @@ 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 (
UserBillingAddressForm BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
ResendActivationEmailForm
)
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, \ from utils.views import (
LoginViewMixin PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin,
ResendActivationLinkViewMixin
)
from .forms import HostingUserSignupForm, HostingUserLoginForm, \ from .forms import HostingUserSignupForm, HostingUserLoginForm, \
UserHostingKeyForm, generate_ssh_key_name UserHostingKeyForm, generate_ssh_key_name
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey
from datacenterlight.models import VMTemplate
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -294,6 +304,14 @@ class SignupValidatedView(SignupValidateView):
return context return context
class ResendActivationEmailView(ResendActivationLinkViewMixin):
template_name = 'hosting/resend_activation_link.html'
form_class = ResendActivationEmailForm
success_url = reverse_lazy('hosting:login')
email_template_path = 'datacenterlight/emails/'
email_template_name = 'user_activation'
class PasswordResetView(PasswordResetViewMixin): class PasswordResetView(PasswordResetViewMixin):
site = 'dcl' site = 'dcl'
template_name = 'hosting/reset_password.html' template_name = 'hosting/reset_password.html'
@ -331,7 +349,7 @@ class PasswordResetConfirmView(PasswordResetConfirmViewMixin):
messages.success(request, _('Password has been reset.')) messages.success(request, _('Password has been reset.'))
# Change opennebula password # Change opennebula password
opennebula_client.change_user_password(new_password) opennebula_client.change_user_password(user.password)
return self.form_valid(form) return self.form_valid(form)
else: else:
@ -663,7 +681,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):
@ -671,54 +689,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')
@ -860,48 +903,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'))
@ -943,7 +1018,19 @@ class VirtualMachineView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
vm = self.get_object() vm = self.get_object()
if vm is None: if vm is None:
if self.request.is_ajax():
storage = messages.get_messages(request)
for m in storage:
pass
storage.used = True
return HttpResponse(
json.dumps({'text': ugettext('Terminated')}),
content_type="application/json"
)
else:
return redirect(reverse('hosting:virtual_machines')) return redirect(reverse('hosting:virtual_machines'))
elif self.request.is_ajax():
return HttpResponse()
try: try:
serializer = VirtualMachineSerializer(vm) serializer = VirtualMachineSerializer(vm)
context = { context = {
@ -958,6 +1045,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
response = {'status': False}
owner = self.request.user owner = self.request.user
vm = self.get_object() vm = self.get_object()
@ -967,17 +1055,29 @@ class VirtualMachineView(LoginRequiredMixin, View):
email=owner.email, email=owner.email,
password=owner.password password=owner.password
) )
try:
vm_data = VirtualMachineSerializer(manager.get_vm(vm.id)).data vm_data = VirtualMachineSerializer(manager.get_vm(vm.id)).data
terminated = manager.delete_vm( except WrongIdError:
vm.id return redirect(reverse('hosting:virtual_machines'))
)
terminated = manager.delete_vm(vm.id)
if not terminated: if not terminated:
messages.error( response['text'] = ugettext(
request, 'Error terminating VM') + opennebula_vm_id
'Error terminating VM %s' % (opennebula_vm_id) else:
) for t in range(15):
return HttpResponseRedirect(self.get_success_url()) try:
manager.get_vm(opennebula_vm_id)
except WrongIdError:
response['status'] = True
response['text'] = ugettext('Terminated')
break
except BaseException:
break
else:
sleep(2)
context = { context = {
'vm': vm_data, 'vm': vm_data,
'base_url': "{0}://{1}".format(self.request.scheme, 'base_url': "{0}://{1}".format(self.request.scheme,
@ -994,15 +1094,11 @@ class VirtualMachineView(LoginRequiredMixin, View):
} }
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
return HttpResponse(
messages.error( json.dumps(response),
request, content_type="application/json"
_('VM %(VM_ID)s terminated successfully') % {
'VM_ID': opennebula_vm_id}
) )
return HttpResponseRedirect(self.get_success_url())
class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin, class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin,
ListView): ListView):

View file

@ -438,11 +438,11 @@ class OpenNebulaManager():
self.oneadmin_client.call(oca.VmTemplate.METHODS[ self.oneadmin_client.call(oca.VmTemplate.METHODS[
'delete'], template_id, False) 'delete'], template_id, False)
def change_user_password(self, new_password): def change_user_password(self, passwd_hash):
self.oneadmin_client.call( self.oneadmin_client.call(
oca.User.METHODS['passwd'], oca.User.METHODS['passwd'],
self.opennebula_user.id, self.opennebula_user.id,
new_password passwd_hash
) )
def add_public_key(self, user, public_key='', merge=False): def add_public_key(self, user, public_key='', merge=False):

View file

@ -96,5 +96,4 @@ pyflakes==1.5.0
billiard==3.5.0.3 billiard==3.5.0.3
amqp==2.2.1 amqp==2.2.1
vine==1.1.4 vine==1.1.4
#git+https://github.com/ungleich/cdist.git#egg=cdist cdist==4.7.0
file:///home/app/cdist#egg=cdist

View file

@ -18,7 +18,8 @@ class SignupFormMixin(forms.ModelForm):
model = CustomUser model = CustomUser
fields = ['name', 'email', 'password'] fields = ['name', 'email', 'password']
widgets = { widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Enter your name or company name')}), 'name': forms.TextInput(
attrs={'placeholder': _('Enter your name or company name')}),
} }
def clean_confirm_password(self): def clean_confirm_password(self):
@ -42,7 +43,7 @@ class LoginFormMixin(forms.Form):
is_auth = authenticate(email=email, password=password) is_auth = authenticate(email=email, password=password)
if not is_auth: if not is_auth:
raise forms.ValidationError( raise forms.ValidationError(
"Your username and/or password were incorrect.") _("Your username and/or password were incorrect."))
return self.cleaned_data return self.cleaned_data
def clean_email(self): def clean_email(self):
@ -51,7 +52,24 @@ class LoginFormMixin(forms.Form):
CustomUser.objects.get(email=email) CustomUser.objects.get(email=email)
return email return email
except CustomUser.DoesNotExist: except CustomUser.DoesNotExist:
raise forms.ValidationError("User does not exist") raise forms.ValidationError(_("User does not exist"))
class ResendActivationEmailForm(forms.Form):
email = forms.CharField(widget=forms.EmailInput())
class Meta:
fields = ['email']
def clean_email(self):
email = self.cleaned_data.get('email')
try:
c = CustomUser.objects.get(email=email)
if c.validated == 1:
raise forms.ValidationError(_("The account is already active."))
return email
except CustomUser.DoesNotExist:
raise forms.ValidationError(_("User does not exist"))
class PasswordResetRequestForm(forms.Form): class PasswordResetRequestForm(forms.Form):
@ -66,7 +84,7 @@ class PasswordResetRequestForm(forms.Form):
CustomUser.objects.get(email=email) CustomUser.objects.get(email=email)
return email return email
except CustomUser.DoesNotExist: except CustomUser.DoesNotExist:
raise forms.ValidationError("User does not exist") raise forms.ValidationError(_("User does not exist"))
class SetPasswordForm(forms.Form): class SetPasswordForm(forms.Form):
@ -75,11 +93,11 @@ class SetPasswordForm(forms.Form):
password password
""" """
error_messages = { error_messages = {
'password_mismatch': ("The two password fields didn't match."), 'password_mismatch': _("The two password fields didn't match."),
} }
new_password1 = forms.CharField(label=("New password"), new_password1 = forms.CharField(label=_("New password"),
widget=forms.PasswordInput) widget=forms.PasswordInput)
new_password2 = forms.CharField(label=("New password confirmation"), new_password2 = forms.CharField(label=_("New password confirmation"),
widget=forms.PasswordInput) widget=forms.PasswordInput)
def clean_new_password2(self): def clean_new_password2(self):

View file

@ -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-02 11:50+0000\n" "POT-Creation-Date: 2017-09-25 20:11+0000\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"
@ -738,6 +738,24 @@ msgstr ""
msgid "Enter your name or company name" msgid "Enter your name or company name"
msgstr "Geben Sie Ihren Namen oder der Ihrer Firma ein" msgstr "Geben Sie Ihren Namen oder der Ihrer Firma ein"
msgid "Your username and/or password were incorrect."
msgstr "Dein Benutzername und/oder Dein Passwort ist falsch."
msgid "User does not exist"
msgstr "Der Benutzer existiert nicht"
msgid "The account is already active."
msgstr "Das Benutzerkonto ist bereits aktiv."
msgid "The two password fields didn't match."
msgstr "Die beiden Passwörter stimmen nicht überein."
msgid "New password"
msgstr "Neues Passwort"
msgid "New password confirmation"
msgstr "Neues Passwort Bestätigung"
msgid "Cardholder Name" msgid "Cardholder Name"
msgstr "Name des Kartenbesitzer" msgstr "Name des Kartenbesitzer"
@ -768,17 +786,25 @@ msgstr "Telefon"
msgid "Message" msgid "Message"
msgstr "Nachricht" msgstr "Nachricht"
msgid "An email with the activation link has been sent to your email"
msgstr ""
"Der Link zum Zurücksetzen deines Passwortes wurde an deine E-Mail gesendet"
msgid "Account Activation"
msgstr "Accountaktivierung"
msgid "The link to reset your email has been sent to your email" msgid "The link to reset your email has been sent to your email"
msgstr "Der Link zum Zur?cksetzen deines Passwortes wurde an deine E-Mail gesendet" msgstr ""
"Der Link zum Zurücksetzen deines Passwortes wurde an deine E-Mail gesendet"
msgid "Password Reset" msgid "Password Reset"
msgstr "" msgstr "Passwort zurücksetzen"
msgid "Password has been reset." msgid "Password has been reset."
msgstr "Das Passwort wurde zur?ckgesetzt." msgstr "Das Passwort wurde zurückgesetzt."
msgid "Password reset has not been successful." msgid "Password reset has not been successful."
msgstr "Das Zur?cksetzen war nicht erfolgreich." msgstr "Das Zurücksetzen war nicht erfolgreich."
msgid "The reset password link is no longer valid." msgid "The reset password link is no longer valid."
msgstr "Der Link zum Zur?cksetzen deines Passwortes ist nicht l?nger g?ltig." msgstr "Der Link zum Zurücksetzen Deines Passwortes ist nicht länger gültig."

View file

@ -2,6 +2,7 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
@ -63,9 +64,45 @@ class LoginViewMixin(FormView):
return super(LoginViewMixin, self).get(request, *args, **kwargs) return super(LoginViewMixin, self).get(request, *args, **kwargs)
class ResendActivationLinkViewMixin(FormView):
success_message = _(
"An email with the activation link has been sent to your email")
def generate_email_context(self, user):
context = {
'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host()),
'activation_link': reverse_lazy(
'hosting:validate',
kwargs={'validate_slug': user.validation_slug}
),
'dcl_text': settings.DCL_TEXT,
}
return context
def form_valid(self, form):
email = form.cleaned_data.get('email')
user = CustomUser.objects.get(email=email)
messages.add_message(self.request, messages.SUCCESS,
self.success_message)
context = self.generate_email_context(user)
email_data = {
'subject': '{dcl_text} {account_activation}'.format(
dcl_text=settings.DCL_TEXT,
account_activation=_('Account Activation')
),
'to': email,
'context': context,
'template_name': self.email_template_name,
'template_path': self.email_template_path,
'from_address': settings.DCL_SUPPORT_FROM_ADDRESS
}
email = BaseEmail(**email_data)
email.send()
return HttpResponseRedirect(self.get_success_url())
class PasswordResetViewMixin(FormView): class PasswordResetViewMixin(FormView):
# template_name = 'hosting/reset_password.html'
# form_class = PasswordResetRequestForm
success_message = _( success_message = _(
"The link to reset your email has been sent to your email") "The link to reset your email has been sent to your email")
site = '' site = ''
@ -78,7 +115,6 @@ class PasswordResetViewMixin(FormView):
'site_name': 'ungleich' if self.site != 'dcl' else settings.DCL_TEXT, 'site_name': 'ungleich' if self.site != 'dcl' else settings.DCL_TEXT,
'base_url': "{0}://{1}".format(self.request.scheme, 'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host()) self.request.get_host())
} }
return context return context
@ -104,11 +140,8 @@ class PasswordResetViewMixin(FormView):
class PasswordResetConfirmViewMixin(FormView): class PasswordResetConfirmViewMixin(FormView):
# template_name = 'hosting/confirm_reset_password.html'
form_class = SetPasswordForm form_class = SetPasswordForm
# success_url = reverse_lazy('hosting:login')
def post(self, request, uidb64=None, token=None, *arg, **kwargs): def post(self, request, uidb64=None, token=None, *arg, **kwargs):
try: try:
uid = urlsafe_base64_decode(uidb64) uid = urlsafe_base64_decode(uidb64)