Merge branch 'develop'

This commit is contained in:
sangheekim 2017-05-25 21:25:51 +02:00
commit 879d0efb9a
101 changed files with 6875 additions and 3206 deletions

2
Changelog Normal file
View file

@ -0,0 +1,2 @@
1.0.0: 2017-05-25
* Initial stable release

View file

@ -7,7 +7,7 @@ class BetaAccessForm(forms.ModelForm):
email = forms.CharField(widget=forms.EmailInput())
class Meta:
fields = ['email']
fields = ['name', 'email']
model = BetaAccess

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-01-22 14:06-0500\n"
"POT-Creation-Date: 2017-05-23 17:26-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,6 +18,20 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: templates/datacenterlight/beta_access.html:21
msgid "Request Beta Access"
msgstr "Beantrage Beta-Zugang"
#: templates/datacenterlight/beta_success.html:9
#, fuzzy
#| msgid "Request Beta Access"
msgid "Request Sent"
msgstr "Anfrage verschickt"
#: templates/datacenterlight/beta_success.html:12
msgid "Thank you, we will contact you as soon as possible"
msgstr "Vielen Dank, wir werden Sie sobald als möglich kontaktieren."
#: templates/datacenterlight/emails/request_access_confirmation.html:99
#: templates/datacenterlight/emails/request_access_confirmation.txt:99
msgid "Thank you for your request."
@ -51,28 +65,36 @@ msgstr ""
msgid "Thank you!"
msgstr "Vielen Dank!"
#: templates/datacenterlight/index.html:60
#: templates/datacenterlight/index.html:62
#: templates/datacenterlight/pricing.html:62
msgid "What is it"
msgstr "Was ist es?"
#: templates/datacenterlight/index.html:63
#: templates/datacenterlight/index.html:165
#: templates/datacenterlight/index.html:351
#: templates/datacenterlight/index.html:65
#: templates/datacenterlight/index.html:171
#: templates/datacenterlight/index.html:331
#: templates/datacenterlight/pricing.html:65
#: templates/datacenterlight/pricing.html:188
msgid "Scale out"
msgstr "Skalierung"
#: templates/datacenterlight/index.html:66
#: templates/datacenterlight/index.html:188
#: templates/datacenterlight/index.html:354
#: templates/datacenterlight/index.html:68
#: templates/datacenterlight/index.html:197
#: templates/datacenterlight/index.html:334
#: templates/datacenterlight/pricing.html:68
#: templates/datacenterlight/pricing.html:191
msgid "Reliable and light"
msgstr "Zuverlässig und leicht"
#: templates/datacenterlight/index.html:69
#: templates/datacenterlight/index.html:71
#: templates/datacenterlight/pricing.html:71
msgid "Buy VM"
msgstr "Kaufe VM"
msgstr "VM Kaufen"
#: templates/datacenterlight/index.html:72
#: templates/datacenterlight/index.html:361
#: templates/datacenterlight/index.html:74
#: templates/datacenterlight/index.html:341
#: templates/datacenterlight/pricing.html:74
#: templates/datacenterlight/pricing.html:198
msgid "Contact"
msgstr "Kontakt"
@ -88,122 +110,133 @@ msgstr "Was ist es?"
msgid "I want it!"
msgstr "Das will ich haben!"
#: templates/datacenterlight/index.html:139
msgid "How it works :"
msgstr "Warum können wir diese Leistung so günstig anbieten:"
#: templates/datacenterlight/index.html:142
#: templates/datacenterlight/index.html:328
#: templates/datacenterlight/pricing.html:185
msgid "How it works"
msgstr "Wie es funktioniert"
#: templates/datacenterlight/index.html:141
#: templates/datacenterlight/index.html:147
msgid "Reuse existing factory halls intead of building an expensive building."
msgstr ""
"Wiederverwendung ehemaliger Fabrikhallen anstatt eines teuren, neuen Gebäudes"
"Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der "
"Errichtung eines neuen Gebäudes"
#: templates/datacenterlight/index.html:144
#: templates/datacenterlight/index.html:150
msgid "Being creative, using modern and alternative design for a datacenter."
msgstr ""
"Kreatives handeln, Nutzung eines modernen und alternativem Designs des "
"Datacenters"
"Kreativität: Verwendung eines modernen und alternativen Designs für unser "
"Datencenter"
#: templates/datacenterlight/index.html:146
msgid "Being open : Using FOSS exclusively, we can save money for licenses."
#: templates/datacenterlight/index.html:152
msgid "Being open: Using FOSS exclusively, we can save money for licenses."
msgstr ""
"Offene Verfahrensweise: Die Benutzung eines eigenen Frameworks, FOSS, "
"erspart Lizenzgebühren"
#: templates/datacenterlight/index.html:166
#: templates/datacenterlight/index.html:174
msgid ""
"We don't use special hardware. We use commodity hardware: we buy computers "
"that you buy. Just many more and put them in a cozy home for computers "
"called data center."
msgstr ""
"Wir benutzen keine spezielle Hardware, sondern am Markt verfügbare, "
"erschwingliche Systeme.Bei größerer Auslastung werden mehrStandard "
"Komponenten hinzugekauft und skalieren so das Datacenter"
"erschwingliche Systeme. Bei grösserer Auslastung werden mehr Standard "
"komponenten hinzugekauft und skalieren so das Datencenter."
#: templates/datacenterlight/index.html:189
#: templates/datacenterlight/index.html:200
msgid ""
"Our VMs are located in Switzerland, with reliable power supply and fast "
"internet connection. Our VM costs less thanks to our featherlight "
"infrastructure."
msgstr ""
"Unser Datacenter befindet sich in der Schweiz undist mit zuverlässiger "
"Energieversorgung sowie schneller Internetverbindung ausgestattet.Unser "
"Angebot ist aufgrund unserer federleichten Infrastruktur überaus "
"kostengünstig."
"Unser Datacenter befindet sich in der Schweiz und ist mit zuverlässiger "
"Energieversorgung sowie schneller Internetverbindung ausgestattet. Unser "
"Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig."
#: templates/datacenterlight/index.html:211
#: templates/datacenterlight/index.html:218
#: templates/datacenterlight/pricing.html:101
msgid "We are cutting down the costs significantly!"
msgstr "Wir sorgen dafür, dass die Kosten für Sie signifikant abnehmen"
#: templates/datacenterlight/index.html:212
#: templates/datacenterlight/index.html:219
msgid "Affordable VM hosting based in Switzerland"
msgstr "Bezahlbares VM Hosting in der Schweiz"
#: templates/datacenterlight/index.html:228
msgid "VM hosting"
msgstr ""
#: templates/datacenterlight/index.html:229
msgid "Based in Switzerland"
msgstr "Standort des Datacenters ist in der Schweiz"
#: templates/datacenterlight/index.html:232
msgid "15 GiB storage(SSD)"
msgstr ""
#: templates/datacenterlight/index.html:234
msgid "Buy Now!"
msgstr "Kaufe jetzt!"
#: templates/datacenterlight/index.html:234
#: templates/datacenterlight/index.html:220
msgid "More Info"
msgstr "Weitere Informationen"
#: templates/datacenterlight/index.html:256
msgid "I want to try!"
msgstr "Das möchte ich haben"
#: templates/datacenterlight/index.html:227
#: templates/datacenterlight/pricing.html:114
msgid "VM hosting"
msgstr ""
#: templates/datacenterlight/index.html:269
msgid "Email address"
msgstr "E-Mail Adresse"
#: templates/datacenterlight/index.html:234
msgid "Based in Switzerland"
msgstr "Standort des Datacenters ist in der Schweiz"
#: templates/datacenterlight/index.html:272
msgid "Request Beta Access"
msgstr "Beantrage Beta-Zugang"
#: templates/datacenterlight/index.html:243
msgid "15 GB Storage (SSD)"
msgstr "15 GB Storage (SSD)"
#: templates/datacenterlight/index.html:281
#, fuzzy
#| msgid "Request Beta Access"
msgid "Request Sent"
msgstr "Anfrage verschickt"
#: templates/datacenterlight/index.html:246
#: templates/datacenterlight/pricing.html:156
msgid "Order Now!"
msgstr "Bestelle jetzt!"
#: templates/datacenterlight/index.html:284
msgid "Thank you, we will contact you as soon as possible"
msgstr "Vielen Dank, wir werden Sie sobald als möglich kontaktieren."
#: templates/datacenterlight/index.html:262
msgid "Want to know more? Subscribe to our newsletter!"
msgstr "Willst du mehr wissen? Abonniere unseren Newsletter!"
#: templates/datacenterlight/index.html:314
msgid "QUESTIONS?"
msgstr "Fragen?"
#: templates/datacenterlight/index.html:315
msgid "CONTACT US!"
msgstr "Kontaktiere uns!"
#: templates/datacenterlight/index.html:319
#: templates/datacenterlight/index.html:289
msgid "Switzerland "
msgstr "Schweiz"
#: templates/datacenterlight/index.html:344
#: templates/datacenterlight/index.html:306
msgid "Questions?"
msgstr "Fragen?"
#: templates/datacenterlight/index.html:306
msgid "Contact us!"
msgstr "Kontaktiere uns!"
#: templates/datacenterlight/index.html:324
#: templates/datacenterlight/pricing.html:181
msgid "Home"
msgstr "Home"
#: templates/datacenterlight/index.html:348
msgid "How it works"
msgstr "Wie es funktioniert"
#: templates/datacenterlight/index.html:357
#: templates/datacenterlight/index.html:337
#: templates/datacenterlight/pricing.html:194
msgid "Pricing"
msgstr "Preise"
#: templates/datacenterlight/pricing.html:122
#, fuzzy
#| msgid "Based in Switzerland"
msgid "Hosted in Switzerland"
msgstr "Standort des Datacenters ist in der Schweiz"
#: templates/datacenterlight/pricing.html:136
msgid "GB Storage (SSD)"
msgstr "GB Storage (SSD)"
#: templates/datacenterlight/pricing.html:163
msgid "Simple and affordable: Try our virtual machine with featherlight price."
msgstr ""
#~ msgid "Buy Now!"
#~ msgstr "Kaufe jetzt!"
#~ msgid "I want to try!"
#~ msgstr "Das möchte ich haben"
#~ msgid "How it works:"
#~ msgstr "Warum können wir diese Leistung so günstig anbieten:"
#~ msgid "Email address"
#~ msgstr "E-Mail Adresse"
#~ msgid "Our promise"
#~ msgstr "Unser Versprechen"

View file

@ -3,7 +3,22 @@
* Code licensed under the Apache License v2.0.
* For details, see http://www.apache.org/licenses/LICENSE-2.0.
*/
@font-face {
font-family: 'Montserrat-Regular';
src: url('../fonts/Montserrat/Montserrat-Regular.ttf');
}
@font-face {
font-family: 'Montserrat-Bold';
src: url('../fonts/Montserrat/Montserrat-Bold.ttf');
}
@font-face {
font-family: 'Montserrat-Medium';
src: url('../fonts/Montserrat/Montserrat-Medium.ttf');
}
@font-face {
font-family: 'Montserrat-Light';
src: url('../fonts/Montserrat/Montserrat-Light.ttf');
}
body,
html {
width: 100%;
@ -17,10 +32,11 @@ h3,
h4,
h5,
h6 {
font-family: 'Raleway' , "Lato","Helvetica Neue",Helvetica,Arial,sans-serif;
font-family: 'Montserrat-Regular', sans-serif;
font-weight: 700;
}
/*blue light #5A74AF*/
/*blue dark #29427A*/
.topnav {
font-size: 14px;
}
@ -29,35 +45,167 @@ h6 {
font-size: 18px;
font-weight: 400;
}
.btn{
box-shadow: 0 1px 4px rgba(0, 0, 0, .6);
}
.fa-li.fa-lg {
color: #29427A;
margin-top: 6px;
}
.btn-transparent{
background: transparent;
border: 2px solid #fff;
color: #fff;
transition: all .2s ease-in;
}
.btn-primary{
background: #29427A;
border-color: #29427A;
color: #fff;
width: auto;
}
.btn-primary:hover{
background: rgba(41, 66, 122, 0.8);
border-color: #29427A;
}
.btn-transparent:hover{
background: #fff;
border: 2px solid #fff;
color: #000;
transition: all .2s ease-in;
}
.btn-info {
color: #fff;
background-color: #5A74AF;
border-color: #5A74AF;
}
.btn-info:hover {
color: #fff;
background-color: rgba(90, 116, 175, 0.8);
border-color: #5A74AF;
}
.btn-info:focus {
color: #fff;
background-color: rgba(90, 116, 175, 0.8);
border-color: #5A74AF;
}
.btn-lg{
min-width: 180px;
}
#logoWhite{
display: none;
}
#logoBlack{
display: block;
}
.navbar{
transition: all .3s ease-in;
}
.navbar-default{
background: #fff;
border: none;
padding: 5px;
}
.navbar-transparent{
background: transparent;
border: none;
padding: 20px;
}
.navbar-transparent #logoBlack{
display: none;
}
.navbar-transparent #logoWhite{
display: block;
width: 220px;
}
.navbar-default .navbar-nav>li>a {
cursor: pointer;
}
.navbar-transparent .navbar-nav>li>a {
color: #fff;
cursor: pointer;
}
.navbar-transparent .navbar-nav>li>a:hover {
color: #fff;
}
.navbar-default .btn-link {
box-shadow: none;
}
.navbar-brand {
padding: 10px 15px;
cursor: pointer;
}
.navbar-right {
margin-right: 0px;
}
.navbar-default .btn-link {
color: #fff;
}
.navbar-default .btn-link:hover {
color: #fff !important;
}
.intro-header {
padding-top: 50px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */
padding-bottom: 50px;
height: 100vh;
text-align: center;
color: #f8f8f8;
background: url(../img/intro-bg.jpg) no-repeat center center;
color: #fff;
background: url(../img/configure.jpg) no-repeat center center;
background-size: cover;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.intro-header::before{
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(90, 116, 175, 0.7);
}
.intro-header-1 {
padding-top: 50px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */
padding-bottom: 50px;
text-align: center;
color: #f8f8f8;
color: #fff;
background: url(../img/configure.jpg) no-repeat center center;
background-size: cover;
position: relative;
}
.intro-header-1::before{
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(90, 116, 175, 0.36);
}
.intro-header-2 {
padding-top: 50px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */
padding-bottom: 50px;
text-align: center;
color: #f8f8f8;
background: url(../img/configure.jpg) no-repeat center center;
background: url(../img/banner-bg.jpg) no-repeat center center;
background-size: cover;
position: relative;
}
.intro-header-2::before{
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(41, 66, 122, 0.59);
}
.intro-message {
position: relative;
padding-top: 20%;
padding-bottom: 20%;
width: 80%;
margin: 0 auto;
}
.intro-signup {
position: relative;
@ -68,7 +216,8 @@ h6 {
.intro-message > h1 {
margin: 0;
font-weight: 400;
font-size: 5em;
font-size: 6em;
font-family: 'Montserrat-Medium';
}
.intro-divider {
@ -79,9 +228,429 @@ h6 {
.intro-message > h3 {
font-weight: 300;
font-family: 'Montserrat-Light';
}
@media(max-width:767px) {
.intro-pricing{
text-align: center;
color: #fff;
background: url(../img/pattern.jpg) no-repeat center center;
background-size: cover;
height: 70vh;
max-height: 400px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.intro-pricing::before{
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(90, 116, 175, 0.7);
}
.intro-pricing .intro-message .section-heading{
font-size: 45px;
width: 80%;
margin: 0 auto;
}
.split-section {
padding: 70px 0;
}
.split-section .icon-section{
position: relative;
min-height: 330px;
}
.split-section .icon-section i{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 216px;
color: #5A74AF;
}
.split-section .split-text .lead{
font-size: 21px;
color: #3a3a3a;
}
.split-section .split-text .split-title{
position: relative;
margin-bottom: 25px;
}
.split-section .split-text .split-title h2{
font-family: 'Montserrat-Bold';
font-size: 50px;
line-height: 50px;
padding-bottom: 25px;
color: #3a3a3a;
letter-spacing: 3px;
}
.split-section.left{
background: -webkit-linear-gradient(#f0f4f7, #fff) no-repeat;
background: -o-linear-gradient(#f0f4f7, #fff) no-repeat;
background: linear-gradient(#f0f4f7, #fff) no-repeat;
}
.split-section.left .split-description{
width: 90%;
margin-right: auto;
}
.split-section.right .split-description{
width: 90%;
margin-left: auto;
}
.split-section.right .split-text {
text-align: right;
}
.split-section.right .split-text ul{
text-align: left;
}
.split-section.left .split-text {
text-align: left;
}
.split-section.right .split-text .split-title h2{
text-align: right;
}
.split-section.left .split-text .split-title h2{
text-align: left;
}
.split-section.right .split-text .split-title::before{
content: "";
position: absolute;
bottom: 0;
background: #29427A;
height: 7px;
width: 70px;
right: 0;
}
.split-section.left .split-text .split-title::before{
content: "";
position: absolute;
bottom: 0;
background: #29427A;
height: 7px;
width: 70px;
left: 0;
}
.pricing-section{
padding: 80px 0 !important;
background: -webkit-linear-gradient(top, #f0f4f7, #fff) no-repeat;
background: linear-gradient(to bottom, #f0f4f7, #fff) no-repeat;
}
.pricing-section .card{
width: 350px;
margin: 0 auto;
background: #fff;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
padding-bottom: 40px;
border-radius: 7px;
position: relative;
}
.pricing-section .card .img-beta{
position: absolute;
top: 5px;
width: 60px;
left: 3px;
}
.pricing-section .card .title{
padding: 15px 40px;
font-family: 'Montserrat-Medium';
}
.pricing-section .card .title h3{
font-family: 'Montserrat-Medium';
}
.pricing-section .card .price{
background: #5A74AF;
padding: 22px;
color: #fff;
font-size: 32px;
}
.pricing-section .card .description{
padding: 12px;
border-bottom: 1px solid rgba(128, 128, 128, 0.3);
}
.pricing-section .card .descriptions{
padding: 10px 30px;
}
.pricing-section .card .description p{
margin: 0;
}
.pricing-section .card .btn{
margin-top: 20px;
}
.pricing-section .text{
text-align: left;
}
.pricing-section .text .section-heading{
font-family: 'Montserrat-Bold';
font-size: 50px;
line-height: 50px;
padding-bottom: 25px;
color: #3a3a3a;
letter-spacing: 1px;
position: relative;
}
.pricing-section .text .section-heading::before{
content: "";
position: absolute;
bottom: 0;
background: #29427A;
height: 7px;
width: 70px;
left: 0;
}
.request-section{
background-image: -ms-linear-gradient(right, #29427A 50%, #4F6699 100%);
background-image: -moz-linear-gradient(right, #29427A 50%, #4F6699 100%);
background-image: -o-linear-gradient(right, #29427A 50%, #4F6699 100%);
background-image: -webkit-gradient(linear, right top, left top, color-stop(50, #29427A), color-stop(100, #4F6699));
background-image: -webkit-linear-gradient(right, #29427A 50%, #4F6699 100%);
background-image: linear-gradient(to left, #29427A 50%, #4F6699 100%);
padding: 70px 0;
}
.request-section .title h2{
font-family: 'Montserrat-Bold';
font-size: 45px;
margin: 0;
color: #fff;
padding-bottom: 25px;
position: relative;
}
.request-section .title h2::before{
content: "";
position: absolute;
bottom: 0;
background: #fff;
height: 7px;
width: 70px;
left: 0;
}
.request-section .form-beta{
width: 80%;
margin: 0 auto;
max-width: 350px;
text-align: center;
}
.request-section .form-beta input{
height: 50px;
}
.request-section .form-beta input{
height: 50px;
}
.request-section .form-beta .btn-lg{
width: 100%;
font-size: 16px;
}
.banner {
padding: 100px 0;
color: #fff;
background: url(../img/banner-bg.jpg) no-repeat center center;
background-size: cover;
position: relative;
}
.full-contact-section{
padding-top: 90px;
background-image: -ms-linear-gradient(right, #29427A 50%, #4F6699 100%);
background-image: -moz-linear-gradient(right, #29427A 50%, #4F6699 100%);
background-image: -o-linear-gradient(right, #29427A 50%, #4F6699 100%);
background-image: -webkit-gradient(linear, right top, left top, color-stop(50, #29427A), color-stop(100, #4F6699));
background-image: -webkit-linear-gradient(right, #29427A 50%, #4F6699 100%);
background-image: linear-gradient(to left, #29427A 50%, #4F6699 100%);
}
.contact-section {
padding: 60px 0;
color: #fff;
background-attachment: fixed;
}
.contact-section .card{
text-align: center;
width: 350px;
margin: 0 auto;
background: #fff;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
padding-bottom: 40px;
border-radius: 7px;
color: #4c4444;
box-sizing: border-box;
padding: 45px;
margin-top: -115px;
}
.contact-section .card .social a{
color: #29427A;
font-size: 45px;
}
.contact-section .card .subtitle h3{
font-size: 30px;
margin-bottom: 23px;
font-family: 'Montserrat-Medium';
}
.contact-section .card .social a:hover{
text-decoration: none;
}
.contact-section .title {
margin-right: auto;
width: 80%;
max-width: 468px;
}
.contact-section .title h2{
font-family: 'Montserrat-Bold';
font-size: 65px;
margin: 0;
color: #fff;
padding-bottom: 25px;
position: relative;
text-align: right;
}
.contact-section .title h2::before{
content: "";
position: absolute;
bottom: 0;
background: #fff;
height: 7px;
width: 70px;
right: 0;
}
/*Pricing page*/
.price-calc-section{
padding: 80px 40px !important;
background: -webkit-linear-gradient(top, #f0f4f7, #fff) no-repeat;
background: linear-gradient(to bottom, #f0f4f7, #fff) no-repeat;
display: flex;
}
.price-calc-section .text{
width: 50%;
}
.price-calc-section .text .section-heading{
font-family: 'Montserrat-Bold';
font-size: 50px;
line-height: 50px;
padding-bottom: 25px;
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: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
padding-bottom: 40px;
border-radius: 7px;
text-align: center;
/* margin-right: auto; */
max-width: 400px;
position: relative;
}
.price-calc-section .card .img-beta{
position: absolute;
top: 5px;
width: 60px;
left: 3px;
}
.price-calc-section .card .title{
padding: 15px 40px;
font-family: 'Montserrat-Medium';
}
.price-calc-section .card .title h3{
font-family: 'Montserrat-Medium';
}
.price-calc-section .card .price{
background: #5A74AF;
padding: 22px;
color: #fff;
font-size: 32px;
}
.price-calc-section .card .description{
padding: 12px;
border-bottom: 1px solid rgba(128, 128, 128, 0.3);
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.price-calc-section .card .description span{
font-size: 20px;
margin-left: 4px;
}
.price-calc-section .card .description i{
color: #29427A;
cursor: pointer;
font-size: 24px;
}
.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 30px;
}
.price-calc-section .card .description p{
margin: 0;
}
.price-calc-section .card .btn{
margin-top: 20px;
font-size: 20px;
width: 200px;
}
.price-calc-section .card .select-configuration select{
outline: none;
background: #fff;
border-color: #d0d0d0;
height: 40px;
width: 200px;
text-align: center;
font-size: 18px;
}
.price-calc-section .card .check-ip{
font-size: 18px;
}
.price-calc-section .card .check-ip input[type=checkbox]{
font-size: 17px;
margin: 0 8px;
}
@media(max-width:990px) {
.pricing-section .text {
text-align: center;
margin-bottom: 40px;
}
.navbar-transparent .navbar-nav>li>a {
font-size: 14px;
}
.pricing-section .text .section-heading::before {
left: 50%;
transform: translate(-50%, 0);
}
}
@media(max-width:768px) {
.intro-message {
padding-bottom: 15%;
}
@ -103,6 +672,81 @@ h6 {
.intro-divider {
width: 100%;
}
.navbar-transparent {
background: #fff;
border: none;
padding: 5px;
}
.navbar-transparent #logoBlack{
display: block;
}
.navbar-transparent #logoWhite{
display: none;
}
.navbar-transparent .navbar-nav>li>a {
font-size: 15px;
color: #777;
}
.split-section {
padding: 10px 0;
}
.split-section .icon-section i{
font-size: 120px;
}
.split-section .split-text .split-title h2{
font-size: 35px;
line-height: 35px;
}
.pricing-section .text .section-heading{
font-size: 35px;
line-height: 35px;
}
.pricing-section .text .section-heading::before {
left: 50%;
transform: translate(-50%, 0);
}
.request-section .title h2 {
font-size: 35px;
line-height: 35px;
text-align: center;
margin-bottom: 55px;
}
.request-section .title h2::before{
left: 50%;
transform: translate(-50%, 0);
}
.contact-section .title {
width: 300px;
margin: 0 auto;
}
.contact-section .title h2{
font-size: 35px;
line-height: 40px;
text-align: center;
margin-top: 35px;
}
.contact-section .title h2::before{
left: 50%;
transform: translate(-50%, 0);
}
}
@media(max-width:540px) {
.pricing-section .card {
width: 90%;
}
.contact-section .card {
width: 90%;
}
.form-beta {
width: 90%;
padding: 25px 10px;
}
.intro-message > h1 {
font-size: 2em;
}
}
.network-name {
@ -119,12 +763,11 @@ h6 {
.content-section-b {
padding: 50px 0;
border-top: 1px solid #e7e7e7;
border-bottom: 1px solid #e7e7e7;
}
.section-heading {
margin-bottom: 30px;
font-family: 'Montserrat-Medium';
}
.section-heading-spacer {
@ -133,27 +776,8 @@ h6 {
border-top: 3px solid #e7e7e7;
}
.banner {
padding: 100px 0;
color: #f8f8f8;
background: url(../img/banner-bg.jpg) no-repeat center center;
background-size: cover;
}
.banner h2 {
margin: 0;
font-weight: 400;
font-size: 3em;
}
.banner ul {
margin-bottom: 0;
}
.banner-social-buttons {
float: right;
margin-top: 0;
}
.btn-buynow {
background-color: #607D8B;
@ -167,27 +791,8 @@ h6 {
}
}
@media(max-width:767px) {
.banner h2 {
margin: 0;
text-shadow: 2px 2px 3px rgba(0,0,0,0.6);
font-size: 3em;
}
ul.banner-social-buttons > li {
display: block;
margin-bottom: 20px;
padding: 0;
}
ul.banner-social-buttons > li:last-child {
margin-bottom: 0;
}
.row {
margin-right: 0px;
margin-left: 0px;
}
}
footer {
padding: 50px 0;

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,92 @@
Copyright 2011 The Montserrat Project Authors (julieta.ulanovsky@gmail.com)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

View file

@ -0,0 +1,55 @@
(function($){
'use strict'; // Start of use strict
$(document).ready(function(){
verifiedUrl();
init_options_interested();
init_nav();
change_values();
});
function verifiedUrl(){
if(window.location.href.indexOf('#success') > -1){
form_success();
}
}
function init_options_interested(){
$('.row-vms').click(function(){
$('.row-vms').removeClass('row-vms__active');
$(this).addClass('row-vms__active');
var number = $('.row-vms__active input').val();
var price = $('.row-vms__active input').data('price');
_calculate(number, price);
});
}
function init_nav(){
$('.nav-local').click(function(){
$('html, body').animate({
scrollTop: $('#'+$(this).data('href')).offset().top
});
});
}
function change_values(){
$('.number-vms').keyup(function () {
var number = $(this).val();
var price = $(this).data('price');
_calculate(number, price);
});
}
function form_success(){
$('#sucessModal').modal('show');
}
function _calculate(numbers, price){
$('#valueTotal').text(numbers*price*31);
}
})(jQuery); // End of use strict

File diff suppressed because one or more lines are too long

View file

@ -1,48 +1,125 @@
(function($){
'use strict'; // Start of use strict
"use strict"; // Start of use strict
/* ---------------------------------------------
Scripts initialization
--------------------------------------------- */
var cardPricing ={
'cpu': {
'id': 'coreValue',
'value': 1,
'min':1,
'max': 48,
'interval': 1
},
'ram': {
'id': 'ramValue',
'value': 1,
'min':1,
'max': 200,
'interval': 1
},
'storage': {
'id': 'storageValue',
'value': 10,
'min': 10,
'max': 500,
'interval': 10
}
}
$(window).load(function(){
});
$(document).ready(function(){
verifiedUrl();
init_options_interested();
init_nav();
change_values();
_navScroll();
_initScroll();
_initNavUrl();
_initPricing();
});
$(window).resize(function(){
});
/* ---------------------------------------------
Nav panel classic
--------------------------------------------- */
function _initScroll(){
$(window).scroll(function(){
_navScroll();
});
}
function _navScroll(){
if($(window).scrollTop() > 10 ){
$(".navbar").removeClass("navbar-transparent");
$(".navbar-default .btn-link").css("color", "#777");
}else{
$(".navbar").addClass("navbar-transparent");
$(".navbar-default .btn-link").css("color", "#fff");
}
}
function _initNavUrl(){
$('.url').click(function(){
var href = $(this).attr('data-url');
console.log(href);
$('html, body').animate({
scrollTop: $(href).offset().top
}, 1000);
});
}
function verifiedUrl(){
if(window.location.href.indexOf('#success') > -1){
form_success();
console.log('epa');
}
}
function init_options_interested(){
$('.row-vms').click(function(){
$('.row-vms').removeClass('row-vms__active');
$(this).addClass('row-vms__active');
var number = $('.row-vms__active input').val();
var price = $('.row-vms__active input').data('price');
_calculate(number, price);
function _initPricing(){
_fetchPricing();
$('.fa-minus-circle.left').click(function(event){
var data = $(this).data('minus');
if(cardPricing[data].value > cardPricing[data].min){
cardPricing[data].value --;
}
_fetchPricing();
});
$('.fa-plus-circle.right').click(function(event){
var data = $(this).data('plus');
if(cardPricing[data].value < cardPricing[data].max){
cardPricing[data].value = cardPricing[data].value + cardPricing[data].interval;
}
_fetchPricing();
});
}
function init_nav(){
$('.nav-local').click(function(){
$('html, body').animate({
scrollTop: $('#'+$(this).data('href')).offset().top
function _fetchPricing(){
Object.keys(cardPricing).map(function(element){
$('#'+cardPricing[element].id).text(cardPricing[element].value);
$('input[name='+element+']').val(cardPricing[element].value);
});
});
_calcPricing();
}
function change_values(){
$('.number-vms').keyup(function () {
var number = $(this).val();
var price = $(this).data('price');
_calculate(number, price);
});
function _calcPricing(){
var total = (cardPricing['cpu'].value * 5) + (2* cardPricing['ram'].value) + (0.6* cardPricing['storage'].value)
total = parseFloat(total.toFixed(2));
$("#total").text(total);
$('input[name=total]').val(total);
}
function form_success(){
$('#sucessModal').modal('show');
@ -52,4 +129,61 @@
}
})(jQuery); // End of use strict
})(jQuery);
// (function($){
// 'use strict'; // Start of use strict
// $(document).ready(function(){
// verifiedUrl();
// init_options_interested();
// init_nav();
// change_values();
// });
// function verifiedUrl(){
// if(window.location.href.indexOf('#success') > -1){
// form_success();
// }
// }
// function init_options_interested(){
// $('.row-vms').click(function(){
// $('.row-vms').removeClass('row-vms__active');
// $(this).addClass('row-vms__active');
// var number = $('.row-vms__active input').val();
// var price = $('.row-vms__active input').data('price');
// _calculate(number, price);
// });
// }
// function init_nav(){
// $('.nav-local').click(function(){
// $('html, body').animate({
// scrollTop: $('#'+$(this).data('href')).offset().top
// });
// });
// }
// function change_values(){
// $('.number-vms').keyup(function () {
// var number = $(this).val();
// var price = $(this).data('price');
// _calculate(number, price);
// });
// }
// function form_success(){
// $('#sucessModal').modal('show');
// }
// function _calculate(numbers, price){
// $('#valueTotal').text(numbers*price*31);
// }
// })(jQuery); // End of use strict

View file

@ -101,7 +101,7 @@
</script>
<script src="{% static 'datacenterlight/js/vendor.js' %}"></script>
<script src="{% static 'datacenterlight/js/plugin.js' %}"></script>
<script src="{% static 'datacenterlight/js/main.js' %}"></script>
<script src="{% static 'datacenterlight/js/beta.js' %}"></script>
</body>
</html>

View file

@ -0,0 +1,27 @@
{% load i18n %}
<form novalidate id ="beta_access" class="form-beta" method="POST" action="{% url 'datacenterlight:beta_access'%}">
{% csrf_token %}
{{ form.non_field_errors }}
<div>
{% for message in messages %}
<strong>{{ message }}</strong>
{% endfor %}
</div>
<div class="inputs">
<div class="form-group">
<input type="text" name="name" class="form-control" id="name" placeholder="Enter name">
<span style="color: white">{{ form.name.errors|striptags}}</span>
</div>
<div class="form-group">
<input type="email" name="email" class="form-control" id="email" placeholder="Enter email">
<span style="color: white">{{ form.email.errors|striptags}}</span>
</div>
</div>
<button type="submit" class="btn btn-default btn-transparent btn-lg">{% trans "Request Beta Access" %}</button>
</form>
<script>
$('#beta_access').ajaxForm({
target: '#beta_access_form', success: function(response) { }
});
</script>

View file

@ -0,0 +1,47 @@
{% load i18n %}
<div class="modal fade bs-example-modal-sm" style="color:black;" id="successModal" tabindex="-1" role="dialog">
<div class="vertical-alignment-helper">
<div class="modal-dialog vertical-align-center">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{% trans "Request Sent" %}</h4>
</div>
<div class="modal-body">
<p>{% trans "Thank you, we will contact you as soon as possible" %}</p>
</div>
</div><!-- /.modal-content -->
</div>
</div>
</div><!-- /.modal -->
<script>
// Show modal
$('#successModal').modal('show');
// close the modal after 3 seconds
setTimeout(function() {
$('#successModal').modal('hide');
}, 5000);
</script>
<style>
.vertical-alignment-helper {
display:table;
height: 100%;
width: 100%;
pointer-events:none; /* This makes sure that we can still click outside of the modal to close it */
}
.vertical-align-center {
/* To center vertically */
display: table-cell;
vertical-align: middle;
pointer-events:none;
}
.modal-content {
/* Bootstrap sets the size of the modal in the modal-dialog class, we need to inherit it */
width:inherit;
height:inherit;
/* To center horizontally */
margin: 0 auto;
pointer-events: all;
}
</style>

View file

@ -20,7 +20,8 @@
<!-- Custom Fonts -->
<link href='//fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<!--Import Google Icon Font-->
<link href="//fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="{% static 'datacenterlight/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="{% static 'datacenterlight/img/favicon.ico' %}" type="image/x-icon" />
@ -42,7 +43,7 @@
<!-- Navigation -->
<nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation">
<div class="container topnav">
<div class="topnav">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
@ -51,27 +52,28 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand topnav" href="#"><img src="{% static 'datacenterlight/img/logo_black.svg' %}"></a>
<a id="logoBlack" class="navbar-brand topnav url" data-url="#home"><img src="{% static 'datacenterlight/img/logo_black.svg' %}"></a>
<a id="logoWhite" class="navbar-brand topnav url" data-url="#home"><img src="{% static 'datacenterlight/img/logo_white.svg' %}"></a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#how">{% trans "What is it" %}</a>
<a class="url" href="javascript:void(0)" data-url="#how" >{% trans "What is it" %}</a>
</li>
<li>
<a href="#your">{% trans "Scale out" %}</a>
<a class="url" href="javascript:void(0)" data-url="#your" >{% trans "Scale out" %}</a>
</li>
<li>
<a href="#our">{% trans "Reliable and light" %}</a>
<a class="url" href="javascript:void(0)" data-url="#our">{% trans "Reliable and light" %}</a>
</li>
<li>
<a href="#price">{% trans "Buy VM" %}</a>
<a class="url" href="javascript:void(0)" data-url="#price" >{% trans "Buy VM" %}</a>
</li>
<li>
<a href="#contact">{% trans "Contact" %}</a>
<a class="url" href="javascript:void(0)" data-url="#contact" >{% trans "Contact" %}</a>
</li>
<li>
<select class="selectpicker" data-width="fit" onchange="location = this.value;" style="margin-top:10px;">
{% if LANGUAGE_CODE == 'en-us'%}
<option selected="selected" value="{{base_url}}/en-us/datacenterlight/">English</option>
@ -85,7 +87,6 @@
{% endif %}
</select>
</li>
</ul>
@ -97,23 +98,22 @@
<!-- Header -->
<a name="about"></a>
<div class="intro-header">
<div class="intro-header" id="home">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="intro-message">
<h1>datacenterlight.ch</h1>
<h1>DataCenterLight</h1>
<h3>{% trans "Finally, an affordable VM hosting in Switzerland!" %}</h3>
<hr class="intro-divider">
<ul class="list-inline intro-social-buttons">
<li>
<a href="#how" class="btn btn-default btn-lg"><i class="#Services"></i> <span class="network-name">{% trans "What is it?" %}</span></a>
<a href="#how" class="btn btn-default btn-lg btn-transparent"><i class="#Services"></i> <span class="network-name">{% trans "What is it?" %}</span></a>
</li>
<li>
<a href="#requestform" class="btn btn-default btn-lg page-scroll"><span class="network-name">{% trans "I want it!" %}</span></a>
<a class="btn btn-primary btn-lg page-scroll url" href="javascript:void(0)" data-url="#request" ><span class="network-name">{% trans "I want it!" %}</span></a>
</li>
</ul>
</div>
@ -121,215 +121,195 @@
</div>
</div>
<div class="triangle-left"></div>
<div class="triangle-right"></div>
<!-- /.container -->
</div>
<!-- /.intro-header -->
<!-- Page Content -->
<a name="how"></a>
<div class="content-section-b">
<div class="split-section right" id="how">
<div class="container">
<div class="row">
<div class="col-lg-5 col-lg-offset-1 col-sm-push-6 col-sm-6">
<hr class="section-heading-spacer">
<div class="clearfix"></div>
<h2 class="section-heading">{% trans "How it works :" %}</h2> <ul class="fa-ul">
<div class="col-xs-12 col-sm-6 col-md-6 icon-section">
<i class="fa fa-cogs" aria-hidden="true"></i>
</div>
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="split-text">
<div class="split-title">
<h2>{% trans "How it works" %}</h2>
</div>
<div class="split-description">
<ul class="fa-ul">
<li><i class="fa-li fa fa-check-square-o fa-lg"></i>
<p class="lead">{% trans "Reuse existing factory halls intead of building an expensive building." %}</p>
</li>
<li><i class="fa-li fa fa-check-square-o fa-lg"></i>
<p class="lead">{% trans "Being creative, using modern and alternative design for a datacenter." %} </p></li>
<p class="lead">{% trans "Being creative, using modern and alternative design for a datacenter." %}</p></li>
<li><i class="fa-li fa fa-check-square-o fa-lg"></i>
<p class="lead">{% trans "Being open : Using FOSS exclusively, we can save money for licenses." %} </p></li>
<p class="lead">{% trans "Being open: Using FOSS exclusively, we can save money for licenses." %}</p></li>
</ul>
</div>
<div class="col-lg-5 col-sm-pull-6 col-sm-6">
<img class="img-responsive" src="{% static 'datacenterlight/img/how3.png' %}" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /.container -->
<!-- /.option 1 -->
</div>
<a name="your"></a>
<div class="content-section-a" id="own">
<div class="split-section left" id="your">
<div class="container">
<div class="row">
<div class="col-lg-5 col-sm-6">
<hr class="section-heading-spacer">
<div class="clearfix"></div>
<h2 class="section-heading">{% trans "Scale out" %}</h2>
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="split-text">
<div class="split-title">
<h2>{% trans "Scale out" %}</h2>
</div>
<div class="split-description">
<p class="lead">{% trans "We don't use special hardware. We use commodity hardware: we buy computers that you buy. Just many more and put them in a cozy home for computers called data center." %}</p>
</div>
<div class="col-lg-5 col-lg-offset-2 col-sm-6">
<img class="img-responsive" src="{% static 'datacenterlight/img/home.png' %}" alt="">
</div>
</div>
</div>
<!-- /.container -->
</div>
<!-- /.option 2 -->
<!-- /.content-section-a -->
<!-- / pricing -->
<a name="our"></a>
<div class="content-section-b">
</div>
<div class="col-xs-12 col-sm-6 col-md-6 icon-section">
<i class="fa fa-rocket" aria-hidden="true"></i>
</div>
</div>
</div>
<!-- /.container -->
<!-- /.option 1 -->
</div>
<div class="split-section right" id="our">
<div class="container">
<div class="row">
<div class="col-lg-5 col-lg-offset-1 col-sm-push-6 col-sm-6">
<hr class="section-heading-spacer">
<div class="clearfix"></div>
<h2 class="section-heading">{% trans "Reliable and light" %}</h2>
<div class="col-xs-12 col-sm-6 col-md-6 icon-section">
<i class="fa fa-handshake-o" aria-hidden="true"></i>
</div>
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="split-text">
<div class="split-title">
<h2>{% trans "Reliable and light" %}</h2>
</div>
<div class="split-description">
<p class="lead">{% trans "Our VMs are located in Switzerland, with reliable power supply and fast internet connection. Our VM costs less thanks to our featherlight infrastructure." %}</p>
</div>
<div class="col-lg-5 col-sm-pull-6 col-sm-6">
<img class="img-responsive" src="{% static 'datacenterlight/img/dog.png' %}" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /.container -->
<!-- /.option 1 -->
</div>
<!-- /.content-section-b -->
<a name="price"></a>
<div class="content-section-a">
<div class="content-section-a pricing-section" id="price">
<div class="container">
<div class="col-xs-12 col-lg-4 col-sm-3"></div>
<div class="row">
<div class="col-xs-12 col-lg-4 col-sm-3">
<hr class="section-heading-spacer">
<div class="clearfix"></div>
<h2 class="section-heading">{% trans "We are cutting down the costs significantly!" %}</h2>
<p class="lead">{% trans "Affordable VM hosting based in Switzerland" %}</p>
</div>
<div class="col-xs-12 col-lg-4 col-sm-3"></div>
<!-- Title -->
<div class="row">
<div class="col-lg-12"></div>
</div>
<!-- /.row -->
<!-- Page Features -->
<div class="row text-center">
<div class="col-xs-12 col-sm-3 col-lg-4 hero-feature"></div>
<div class="col-xs-12 col-sm-3 col-lg-4 hero-feature">
<div class="thumbnail">
<img class="relsonsive" src="{% static 'datacenterlight/img/economy.jpg' %}" alt="">
<div class="col-xs-12 col-md-6 text">
<h2 class="section-heading">{% trans "We are cutting down the costs significantly!" %}</h2>
<p class="lead">{% trans "Affordable VM hosting based in Switzerland" %}</p>
<a href="#" class="btn btn-info btn-lg">{% trans "More Info" %}</a>
</div>
<div class="col-xs-12 col-md-6 hero-feature">
<div class="card">
<div class="caption">
<div class="title">
<h3>{% trans "VM hosting" %} </h3>
</div>
<div class="price">
<span>15 CHF/month</span>
</div>
<div class="descriptions">
<div class="description">
<p>{% trans "Based in Switzerland" %}</p>
<p>1 core, </p>
<p>2 GiB RAM, </p>
<p>{% trans "15 GiB storage(SSD)" %}</p>
<p>
<a href="#" class="btn btn-primary btn-buynow">{% trans "Buy Now!" %}</a> <a href="#" class="btn btn-default">{% trans "More Info" %}</a> </p>
</div>
<div class="description">
<p>1 Core, </p>
</div>
<div class="description">
<p>2 GB RAM, </p>
</div>
<div class="description">
<p>{% trans "15 GB Storage (SSD)" %}</p>
</div>
</div>
<a href="{% url 'datacenterlight:pricing' %}" class="btn btn-primary">{% trans "Order Now!" %}</a>
</div>
<div class="col-xs-12 col-sm-3 col-lg-4 hero-feature"></div>
</div>
<!-- /.row -->
<img class="img-beta" src="{% static 'datacenterlight/img/beta.png' %}" alt="">
</div>
</div>
<!-- /.container -->
</div>
</div>
</div>
<!-- Configure -->
<a name="about"></a>
<div class="intro-header-1">
<div class="request-section" id="request">
<div class="container">
<a href="" id="requestform"></a>
<div class="row">
<div class="col-lg-12">
<div class="intro-message">
<h1>{% trans "I want to try!" %}</h1>
<p>&nbsp;</p>
<p>&nbsp;</p>
<form class="form-inline" method="POST" action="">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.email.errors|striptags}}
<div class="form-group">
<label class="sr-only" for="exampleInputEmail3">{% trans "Email address" %}</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail3" placeholder="Enter email">
<div class="col-sm-6 col-md-6">
<div class="title">
<h2>{% trans "Want to know more? Subscribe to our newsletter!" %}</h2>
</div>
<button type="submit" class="btn btn-default">{% trans "Request Beta Access" %}</button>
</form>
<div class="modal fade bs-example-modal-sm" style="color:black;" id="reques-success-message" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{% trans "Request Sent" %}</h4>
</div>
<div class="modal-body">
<p>{% trans "Thank you, we will contact you as soon as possible" %}</p>
</div>
<div class="modal-footer text-center">
<button type="submit" class="btn btn-primary" data-dismiss="modal">Ok</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</ul>
<div class="col-sm-6 col-md-6">
<!-- Beta access form, will be loaded via ajax -->
<div class="form" id="beta_access_form">
</div>
</div>
</div>
</div>
<!-- /.container -->
</div>
<!-- /.content-section-a -->
<!-- / contact section -->
<a name="contact"></a>
<div class="banner">
<div class="full-contact-section">
<div class="intro-header-2 contact-section" id="contact">
<div class="container">
<div class="row">
<div class="col-lg-6"></div>
<div class="col-lg-6">
<h2>{% trans "QUESTIONS?" %} </h2>
<h2>{% trans "CONTACT US!" %} </h2>
<div class="col-sm-6 col-md-6">
<div class="card">
<div class="subtitle">
<h3>ungleich GmbH </h3>
</div>
<div class="description">
<p><i class="fa fa-envelope-o"></i> info@datacenterlight.ch</p>
<p>In der Au 7, Schwanden 8762</p>
<p>{% trans "Switzerland " %}</p>
<button type="button" class="btn btn-default">
<a href="https://twitter.com/ungleich">
<i class="fa fa-twitter fa-fw"></i><span class="network-name">Twitter</span></a>
</button>
<button type="button" class="btn btn-default">
<a href="https://github.com/ungleich"><i class="fa fa-github fa-fw"></i><span class="network-name">Github</span></a></button>
</div>
<div class="social">
<a target="_blank" class="" href="https://twitter.com/datacenterlight">
<i class="fa fa-twitter fa-fw"></i>
</a>
<a target="_blank" class="" href="https://github.com/ungleich">
<i class="fa fa-github fa-fw"></i>
</a>
<a target="_blank" class="" href="https://www.facebook.com/ungleich.ch/">
<i class="fa fa-facebook fa-fw"></i>
</a>
</div>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="title">
<h2>{% trans "Questions?" %} {% trans "Contact us!" %}</h2>
</div>
</div>
</div>
</div>
</div>
<!-- /.container -->
</div>
<!-- /.banner -->
@ -377,12 +357,12 @@
windowPadding: 10,
});
var hash = window.location.hash.substr(1);
console.log(hash);
if (hash == 'requestform'){
$('#reques-success-message').modal('show');
}
$.ajax({
url: "{% url 'datacenterlight:beta_access' %}",
context: document.body
}).done(function(response) {
$('#beta_access_form').html(response);
});
};
</script>
@ -390,6 +370,9 @@
<!-- Bootstrap Core JavaScript -->
<script src="{% static 'datacenterlight/js/bootstrap.min.js' %}"></script>
<script src="{% static 'datacenterlight/js/main.js' %}"></script>
<!-- Load form js -->
<script src="{% static 'datacenterlight/js/form.js' %}"></script>
</body>

View file

@ -0,0 +1,234 @@
{% load staticfiles i18n%}
{% get_current_language as LANGUAGE_CODE %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>datacenterlight.ch - Featherlight Swiss VM</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'datacenterlight/css/bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<!--Import Google Icon Font-->
<link href="//fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="{% static 'datacenterlight/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="{% static 'datacenterlight/img/favicon.ico' %}" type="image/x-icon" />
<link href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.5.4/bootstrap-select.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="{% static 'datacenterlight/css/landing-page.css' %}" rel="stylesheet">
<!-- 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:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation">
<div class="topnav">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a id="logoBlack" class="navbar-brand topnav url" href="{% url 'datacenterlight:index' %}" ><img src="{% static 'datacenterlight/img/logo_black.svg' %}"></a>
<a id="logoWhite" class="navbar-brand topnav url" href="{% url 'datacenterlight:index' %}" ><img src="{% static 'datacenterlight/img/logo_white.svg' %}"></a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<!-- <li>
<a class="url" href="javascript:void(0)" data-url="#how" >{% trans "What is it" %}</a>
</li>
<li>
<a class="url" href="javascript:void(0)" data-url="#your" >{% trans "Scale out" %}</a>
</li>
<li>
<a class="url" href="javascript:void(0)" data-url="#our">{% trans "Reliable and light" %}</a>
</li>
<li>
<a class="url" href="javascript:void(0)" data-url="#price" >{% trans "Buy VM" %}</a>
</li>
<li>
<a class="url" href="javascript:void(0)" data-url="#contact" >{% trans "Contact" %}</a>
</li> -->
<select class="selectpicker" data-width="fit" onchange="location = this.value;" style="margin-top:10px;">
{% if LANGUAGE_CODE == 'en-us'%}
<option selected="selected" value="{{base_url}}/en-us/datacenterlight/">English</option>
{% else %}
<option value="{{base_url}}/en-us/datacenterlight/">English</option>
{% endif %}
{% if LANGUAGE_CODE == 'de'%}
<option selected="selected" value="{{base_url}}/de/datacenterlight/">Deutsch</option>
{% else %}
<option value="{{base_url}}/de/datacenterlight/">Deutsch</option>
{% endif %}
</select>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container -->
</nav>
<div class="intro-pricing">
<div class="intro-message">
<h2 class="section-heading">{% trans "We are cutting down the costs significantly!" %}</h2>
</div>
</div>
<div class="price-calc-section">
<div class="card">
<img class="img-beta" src="{% static 'datacenterlight/img/beta.png' %}" alt="">
<div class="caption">
<form method="POST" action="">
{% csrf_token %}
<div class="title">
<h3>{% trans "VM hosting" %} </h3>
</div>
<div class="price">
<span id="total">15</span>
<span>CHF</span>
</div>
<div class="descriptions">
<div class="description">
<p>{% trans "Hosted in Switzerland" %}</p>
</div>
<div class="description">
<i class="fa fa-minus-circle left" data-minus="cpu" aria-hidden="true"></i>
<span id="coreValue">1</span><span> Core</span>
<i class="fa fa-plus-circle right" data-plus="cpu" aria-hidden="true"></i>
</div>
<div class="description">
<i class="fa fa-minus-circle left" data-minus="ram" aria-hidden="true"></i>
<span id="ramValue">2</span><span> GiB RAM</span>
<i class="fa fa-plus-circle right" data-plus="ram" aria-hidden="true"></i>
</div>
<div class="description">
<i class="fa fa-minus-circle left" data-minus="storage" aria-hidden="true"></i>
<span id="storageValue">15</span><span>{% trans "GB Storage (SSD)" %}</span>
<i class="fa fa-plus-circle right" data-plus="storage" aria-hidden="true"></i>
</div>
<div class="description select-configuration">
<select name="config" id="">
{% for template in templates %}
<option value="{{template.id}}">{{template.name}} </option>
{% endfor %}
</select>
</div>
<input type="hidden" name="cpu">
<input type="hidden" name="ram">
<input type="hidden" name="storage">
<input type="hidden" name="total">
<!--<div class="description check-ip">
<input type="checkbox" name="ipv6"> Ipv6 Only<br>
</div>-->
</div>
<input type="submit" class="btn btn-primary" value="{% trans 'Order Now!' %}"></input>
</form>
</div>
</div>
<div class="text">
<h2 class="section-heading">{% trans "Simple and affordable: Try our virtual machine with featherlight price." %}</h2>
<div class="description">
<p>Our VMs are hosted in Glarus, Switzerland. WARNING: We are currently running in BETA mode, especially our website We hope you will not encounter any hiccups, but if you, please let us know at support@datacenterlight.ch</p>
</div>
</div>
</div>
<!-- /.banner -->
<!-- Footer -->
<footer>
<div class="container">
<div class="row">
<div class="col-lg-12">
<ul class="list-inline">
<li>
<a href="#">{% trans "Home" %}</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#about">{% trans "How it works" %}</a></li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#about">{% trans "Scale out" %}</a></li>
<li>&sdot;</li>
<li>
<a href="#about">{% trans "Reliable and light" %}</a></li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#services">{% trans "Pricing" %}</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#contact">{% trans "Contact" %}</a>
</li>
</ul>
<p class="copyright text-muted small">Copyright &copy; ungleich GmbH {% now "Y" %}. All Rights Reserved</p>
</div>
</div>
</div>
</footer>
<!-- jQuery -->
<script src="{% static 'datacenterlight/js/jquery.js' %}"></script>
<script type="text/javascript">
window.onload=function(){
$('.selectpicker').selectpicker({
style: 'btn-link',
windowPadding: 10,
});
var hash = window.location.hash.substr(1);
console.log(hash);
if (hash == 'requestform'){
$('#reques-success-message').modal('show');
}
};
</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.5.4/bootstrap-select.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="{% static 'datacenterlight/js/bootstrap.min.js' %}"></script>
<script src="{% static 'datacenterlight/js/main.js' %}"></script>
</body>
</html>

View file

@ -1,10 +1,12 @@
from django.conf.urls import url
from .views import IndexView, BetaProgramView, LandingProgramView
from .views import IndexView, BetaProgramView, LandingProgramView, BetaAccessView, PricingView
urlpatterns = [
url(r'^/?$', IndexView.as_view(), name='index'),
url(r'^/beta-program/?$', BetaProgramView.as_view(), name='beta'),
url(r'^/landing/?$', LandingProgramView.as_view(), name='landing'),
url(r'^/pricing/?$', PricingView.as_view(), name='pricing'),
url(r'^/beta_access?$', BetaAccessView.as_view(), name='beta_access'),
]

View file

@ -5,11 +5,102 @@ from .models import BetaAccess, BetaAccessVMType, BetaAccessVM
from django.contrib import messages
from django.core.urlresolvers import reverse_lazy, reverse
from utils.mailer import BaseEmail
from django.shortcuts import render
from django.shortcuts import redirect
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineTemplateSerializer
class LandingProgramView(TemplateView):
template_name = "datacenterlight/landing.html"
class PricingView(TemplateView):
template_name = "datacenterlight/pricing.html"
def get(self, request, *args, **kwargs):
try:
manager = OpenNebulaManager()
templates = manager.get_templates()
context = {
'templates': VirtualMachineTemplateSerializer(templates, many=True).data,
}
except:
messages.error( request,
'We 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)
def post(self, request):
cores = request.POST.get('cpu')
memory = request.POST.get('ram')
storage = request.POST.get('storage')
price = request.POST.get('total')
template_id = int(request.POST.get('config'))
manager = OpenNebulaManager()
template = manager.get_template(template_id)
request.session['template'] = VirtualMachineTemplateSerializer(template).data
if not request.user.is_authenticated():
request.session['next'] = reverse('hosting:payment')
request.session['specs'] = {
'cpu':cores,
'memory': memory,
'disk_size': storage,
'price': price,
}
return redirect(reverse('hosting:payment'))
class BetaAccessView(FormView):
template_name = "datacenterlight/beta_access.html"
form_class = BetaAccessForm
success_message = "Thank you, we will contact you as soon as possible"
def form_valid(self, form):
context = {
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host())
}
email_data = {
'subject': 'DatacenterLight Beta Access Request',
'to': form.cleaned_data.get('email'),
'context': context,
'template_name': 'request_access_confirmation',
'template_path': 'datacenterlight/emails/'
}
email = BaseEmail(**email_data)
email.send()
context.update({
'email': form.cleaned_data.get('email')
})
email_data = {
'subject': 'DatacenterLight Beta Access Request',
'to': 'info@ungleich.ch',
'context': context,
'template_name': 'request_access_notification',
'template_path': 'datacenterlight/emails/'
}
email = BaseEmail(**email_data)
email.send()
messages.add_message(self.request, messages.SUCCESS, self.success_message)
return render(self.request, 'datacenterlight/beta_success.html', {})
class BetaProgramView(CreateView):
template_name = "datacenterlight/beta.html"
@ -27,6 +118,10 @@ class BetaProgramView(CreateView):
def get_context_data(self, **kwargs):
vms = BetaAccessVMType.objects.all()
context = super(BetaProgramView, self).get_context_data(**kwargs)
# templates = OpenNebulaManager().get_templates()
# data = VirtualMachineTemplateSerializer(templates, many=True).data
context.update({
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()),
'vms': vms

View file

@ -111,6 +111,8 @@ INSTALLED_APPS = (
'nosystemd',
'datacenterlight',
'alplora',
'rest_framework',
'opennebula_api'
)
MIDDLEWARE_CLASSES = (
@ -474,3 +476,30 @@ else:
ANONYMOUS_USER_NAME = 'anonymous@ungleich.ch'
GUARDIAN_GET_INIT_ANONYMOUS_USER = 'membership.models.get_anonymous_user_instance'
#############################################
# configurations for opennebula-integration #
#############################################
# The oneadmin user name of the OpenNebula infrastructure
OPENNEBULA_USERNAME = env('OPENNEBULA_USERNAME')
# The oneadmin password of the OpenNebula infrastructure
# The default credentials of the Sandbox OpenNebula VM is
# oneadmin:opennebula
OPENNEBULA_PASSWORD = env('OPENNEBULA_PASSWORD')
# The protocol is generally http or https
OPENNEBULA_PROTOCOL = env('OPENNEBULA_PROTOCOL')
# The ip address or the domain name of the opennebula infrastructure
OPENNEBULA_DOMAIN = env('OPENNEBULA_DOMAIN')
# The port to connect in order to send an xmlrpc request. The default
# port is 2633
OPENNEBULA_PORT = env('OPENNEBULA_PORT')
# The endpoint to which the XML RPC request needs to be sent to. The
# default value is /RPC2
OPENNEBULA_ENDPOINT = env('OPENNEBULA_ENDPOINT')

View file

@ -2,7 +2,8 @@ from .base import *
ADMINS = (
('Nico Schottelius', 'nico.schottelius@ungleich.ch'),
('Tomislav Rupcic','tmslav@gmail.com'),
('Raul Ascencio', 'raul.ascencio@yandex.com'),
('Web team', 'web-team@ungleich.ch')
)
# ('Sanghee Kim', 'sanghee.kim@ungleich.ch'),

View file

@ -8,10 +8,14 @@ from django.conf import settings
from hosting.views import RailsHostingView, DjangoHostingView, NodeJSHostingView
from membership import urls as membership_urls
from ungleich_page.views import LandingView
from django.views.generic import RedirectView
from django.core.urlresolvers import reverse_lazy
import debug_toolbar
urlpatterns = [ url(r'^index.html$', LandingView.as_view()),
url(r'^hosting/', include('hosting.urls', namespace="hosting")),
url(r'^open_api/', include('opennebula_api.urls',
namespace='opennebula_api')),
url(r'^railshosting/', RailsHostingView.as_view(), name="rails.hosting"),
url(r'^nodehosting/', NodeJSHostingView.as_view(), name="node.hosting"),
url(r'^djangohosting/', DjangoHostingView.as_view(), name="django.hosting"),
@ -26,6 +30,7 @@ urlpatterns += i18n_patterns('',
url(r'^/?$', LandingView.as_view()),
url(r'^admin/', include(admin.site.urls)),
url(r'^datacenterlight', include('datacenterlight.urls', namespace="datacenterlight")),
url(r'^hosting/', RedirectView.as_view(url=reverse_lazy('hosting:login')), name='redirect_hosting_login'),
url(r'^alplora', include('alplora.urls', namespace="alplora")),
url(r'^membership/', include(membership_urls)),
url(r'^digitalglarus/', include('digitalglarus.urls',

View file

@ -0,0 +1,41 @@
Here are the steps to follow for running opennebula-integration correctly.
1. Install [python-oca](https://github.com/python-oca/python-oca)
This is the library that allows sending XMLRPC commands to OpenNebula. Unfortunately, the latest version of oca available in Python package index is not compatible with python 3.5. Hence, one would need to download the latest version from the above github link and install it from there.
Assuming virtualenv is located at ~/python/env
```
~/python/env/bin/python setup.py build
sudo ~/python/env/bin/python setup.py install
```
2. Setup opennebula parameters in the `.env` file.
```
#############################################
# configurations for opennebula-integration #
#############################################
# The oneadmin user name of the OpenNebula infrastructure
OPENNEBULA_USERNAME='oneadmin'
# The oneadmin password of the OpenNebula infrastructure
# The default credentials of the Sandbox OpenNebula VM is
# oneadmin:opennebula
OPENNEBULA_PASSWORD='opennebula'
# The protocol is generally http or https
OPENNEBULA_PROTOCOL='http'
# The ip address or the domain name of the opennebula infrastructure
OPENNEBULA_DOMAIN='192.168.182.124'
# The port to connect in order to send an xmlrpc request. The default
# port is 2633
OPENNEBULA_PORT='2633'
# The endpoint to which the XML RPC request needs to be sent to. The
# default value is /RPC2
OPENNEBULA_ENDPOINT='/RPC2'
```

View file

@ -4,95 +4,9 @@ from django.core.urlresolvers import reverse
from utils.mailer import BaseEmail
from .forms import HostingOrderAdminForm
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder
from .models import HostingOrder, HostingBill, HostingPlan
class HostingOrderAdmin(admin.ModelAdmin):
# fields = ('slug', 'imdb_link', 'start', 'finish', 'added_by')
list_display = ('id', 'created_at', 'plan', 'user')
search_fields = ['vm_plan__id', 'customer__user__email']
def save_model(self, request, obj, form, change):
if not change:
customer = form.cleaned_data.get('customer')
# Get and set billing address from the lastest charged order
last_order = HostingOrder.objects.filter(customer=customer).latest('id')
billing_address = last_order.billing_address
obj.billing_address = billing_address
charge = form.cleaned_data.get('charge')
# Associate an order with a stripe payment
obj.set_stripe_charge(charge)
# If the Stripe payment was successed, set order status approved
obj.set_approved()
# Assigning permissions
obj.assign_permissions(customer.user)
context = {
'order': obj,
'vm': obj.vm_plan,
'base_url': "{0}://{1}".format(request.scheme, request.get_host())
}
email_data = {
'subject': 'Your VM plan has been charged',
'to': obj.customer.user.email,
'context': context,
'template_name': 'vm_charged',
'template_path': 'hosting/emails/'
}
email = BaseEmail(**email_data)
email.send()
obj.save()
return obj
def get_form(self, request, obj=None, **kwargs):
if obj is None:
kwargs['form'] = HostingOrderAdminForm
return super(HostingOrderAdmin, self).get_form(request, obj, **kwargs)
def user(self, obj):
email = obj.customer.user.email
user_url = reverse("admin:membership_customuser_change", args=[obj.customer.user.id])
return format_html("<a href='{url}'>{email}</a>", url=user_url, email=email)
def plan(self, obj):
vm_name = obj.vm_plan.name
vm_url = reverse("admin:hosting_virtualmachineplan_change", args=[obj.vm_plan.id])
return format_html("<a href='{url}'>{vm_name}</a>", url=vm_url, vm_name=vm_name)
plan.short_description = "Virtual Machine Plan"
class VirtualMachinePlanAdmin(admin.ModelAdmin):
list_display = ('name', 'id', 'email')
def email(self, obj):
return obj.hosting_orders.latest('id').customer.user.email
def save_model(self, request, obj, form, change):
email = self.email(obj)
if 'status' in form.changed_data:
context = {
'vm': obj,
'base_url': "{0}://{1}".format(request.scheme, request.get_host())
}
email_data = {
'subject': 'Your VM has been activated',
'to': email,
'context': context,
'template_name': 'vm_status_changed',
'template_path': 'hosting/emails/'
}
email = BaseEmail(**email_data)
email.send()
obj.save()
admin.site.register(HostingOrder, HostingOrderAdmin)
admin.site.register(VirtualMachineType)
admin.site.register(VirtualMachinePlan, VirtualMachinePlanAdmin)
admin.site.register(HostingOrder)
admin.site.register(HostingBill)
admin.site.register(HostingPlan)

View file

@ -1,42 +1,13 @@
import random
import string
from django import forms
from membership.models import CustomUser
from django.contrib.auth import authenticate
from utils.stripe_utils import StripeUtils
from .models import HostingOrder, VirtualMachinePlan
class HostingOrderAdminForm(forms.ModelForm):
class Meta:
model = HostingOrder
fields = ['vm_plan', 'customer']
def clean(self):
customer = self.cleaned_data.get('customer')
vm_plan = self.cleaned_data.get('vm_plan')
if vm_plan.status == VirtualMachinePlan.CANCELED_STATUS:
raise forms.ValidationError("""You can't make a charge over
a canceled virtual machine plan""")
if not customer:
raise forms.ValidationError("""You need select a costumer""")
# Make a charge to the customer
stripe_utils = StripeUtils()
charge_response = stripe_utils.make_charge(customer=customer.stripe_id,
amount=vm_plan.price)
charge = charge_response.get('response_object')
if not charge:
raise forms.ValidationError(charge_response.get('error'))
self.cleaned_data.update({
'charge': charge
})
return self.cleaned_data
from .models import HostingOrder, UserHostingKey
class HostingUserLoginForm(forms.Form):
@ -83,3 +54,40 @@ class HostingUserSignupForm(forms.ModelForm):
if not confirm_password == password:
raise forms.ValidationError("Passwords don't match")
return confirm_password
class UserHostingKeyForm(forms.ModelForm):
private_key = forms.CharField(widget=forms.PasswordInput(), required=False)
public_key = forms.CharField(widget=forms.PasswordInput(), required=False)
user = forms.models.ModelChoiceField(queryset=CustomUser.objects.all(), required=False)
name = forms.CharField(required=False)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super(UserHostingKeyForm, self).__init__(*args, **kwargs)
# self.initial['user'].initial = self.request.user.id
# print(self.fields)
def clean_name(self):
return "dcl-priv-key-%s" % (
''.join(random.choice(string.ascii_lowercase) for i in range(7))
)
def clean_user(self):
return self.request.user
def clean(self):
cleaned_data = self.cleaned_data
if not cleaned_data.get('public_key'):
private_key, public_key = UserHostingKey.generate_keys()
cleaned_data.update({
'private_key': private_key,
'public_key': public_key
})
return cleaned_data
class Meta:
model = UserHostingKey
fields = ['user', 'public_key', 'name']

View file

@ -7,51 +7,100 @@ class Command(BaseCommand):
def get_data(self):
return [
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 1,
'memory': 2,
'disk_size': 10
},
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 1,
'memory': 2,
'disk_size': 100
},
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 2,
'memory': 4,
'disk_size': 20
},
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 4,
'memory': 8,
'disk_size': 40
},
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 16,
'memory': 8,
'disk_size': 40
},
]
hetzner = {
'base_price': 10,
'core_price': 10,
'memory_price': 5,
'disk_size_price': 1,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'description': 'VM auf einzelner HW, Raid1, kein HA',
'location': 'DE'
}
return {
# 'hetzner_nug': {
# 'base_price': 5,
# 'memory_price': 2,
# 'core_price': 2,
# 'disk_size_price': 0.5,
# 'description': 'VM ohne Uptime Garantie'
# },
'hetzner': hetzner,
# 'hetzner_raid6': {
# 'base_price': hetzner['base_price']*1.2,
# 'core_price': hetzner['core_price']*1.2,
# 'memory_price': hetzner['memory_price']*1.2,
# 'disk_size_price': hetzner['disk_size_price']*1.2,
# 'description': 'VM auf einzelner HW, Raid1, kein HA'
# return {
# # 'hetzner_nug': {
# # 'base_price': 5,
# # 'memory_price': 2,
# # 'core_price': 2,
# # 'disk_size_price': 0.5,
# # 'description': 'VM ohne Uptime Garantie'
# # },
# 'hetzner': hetzner,
# # 'hetzner_raid6': {
# # 'base_price': hetzner['base_price']*1.2,
# # 'core_price': hetzner['core_price']*1.2,
# # 'memory_price': hetzner['memory_price']*1.2,
# # 'disk_size_price': hetzner['disk_size_price']*1.2,
# # 'description': 'VM auf einzelner HW, Raid1, kein HA'
# },
# 'hetzner_glusterfs': {
# 'base_price': hetzner['base_price']*1.4,
# 'core_price': hetzner['core_price']*1.4,
# 'memory_price': hetzner['memory_price']*1.4,
# 'disk_size_price': hetzner['disk_size_price']*1.4,
# 'description': 'VM auf einzelner HW, Raid1, kein HA'
# },
'bern': {
'base_price': 12,
'core_price': 25,
'memory_price': 7,
'disk_size_price': 0.70,
'description': "VM in Bern, HA Setup ohne HA Garantie",
'location': 'CH',
}
}
# # },
# # 'hetzner_glusterfs': {
# # 'base_price': hetzner['base_price']*1.4,
# # 'core_price': hetzner['core_price']*1.4,
# # 'memory_price': hetzner['memory_price']*1.4,
# # 'disk_size_price': hetzner['disk_size_price']*1.4,
# # 'description': 'VM auf einzelner HW, Raid1, kein HA'
# # },
# 'bern': {
# 'base_price': 12,
# 'core_price': 25,
# 'memory_price': 7,
# 'disk_size_price': 0.70,
# 'description': "VM in Bern, HA Setup ohne HA Garantie",
# 'location': 'CH',
# }
# }
def handle(self, *args, **options):
data = self.get_data()
[VirtualMachineType.objects.create(hosting_company=key, **data[key])
for key in data.keys()]
vm_data = self.get_data()
for vm in vm_data:
VirtualMachineType.objects.create(**vm)

View file

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-29 18:28
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hosting', '0027_auto_20160711_0210'),
]
operations = [
migrations.CreateModel(
name='ManageVM',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'managed': False,
},
),
migrations.CreateModel(
name='UserHostingKey',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('public_key', models.TextField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-24 04:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0027_auto_20160711_0210'),
]
operations = [
migrations.CreateModel(
name='ManageVMs',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'managed': False,
},
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-24 04:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0028_managevms'),
]
operations = [
migrations.CreateModel(
name='ManageVM',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'managed': False,
},
),
]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-30 19:04
from __future__ import unicode_literals
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('hosting', '0028_managevm_userhostingkey'),
]
operations = [
migrations.AddField(
model_name='userhostingkey',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2017, 4, 30, 19, 4, 20, 780173, tzinfo=utc)),
preserve_default=False,
),
]

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-05 11:50
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import utils.mixins
class Migration(migrations.Migration):
dependencies = [
('utils', '0005_auto_20170322_1443'),
('membership', '0006_auto_20160526_0445'),
('hosting', '0029_managevm'),
]
operations = [
migrations.CreateModel(
name='HostingBill',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('billing_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress')),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer')),
],
options={
'permissions': (('view_hostingbill', 'View Hosting Bill'),),
},
bases=(utils.mixins.AssignPermissionsMixin, models.Model),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-30 19:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0029_userhostingkey_created_at'),
]
operations = [
migrations.AddField(
model_name='userhostingkey',
name='name',
field=models.CharField(default='', max_length=100),
preserve_default=False,
),
]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-03 05:54
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hosting', '0030_userhostingkey_name'),
]
operations = [
migrations.RemoveField(
model_name='virtualmachinetype',
name='hosting_company',
),
migrations.RemoveField(
model_name='virtualmachinetype',
name='location',
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-06 12:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0030_hostingbill'),
]
operations = [
migrations.AddField(
model_name='hostingbill',
name='total_price',
field=models.FloatField(default=0.0),
),
]

View file

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-04 03:15
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0031_auto_20170503_0554'),
]
operations = [
migrations.AddField(
model_name='virtualmachinetype',
name='cores',
field=models.IntegerField(default=0),
preserve_default=False,
),
migrations.AddField(
model_name='virtualmachinetype',
name='disk_size',
field=models.IntegerField(default=0),
preserve_default=False,
),
migrations.AddField(
model_name='virtualmachinetype',
name='memory',
field=models.IntegerField(default=0),
preserve_default=False,
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-04 03:23
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0032_auto_20170504_0315'),
]
operations = [
migrations.AddField(
model_name='virtualmachinetype',
name='configuration',
field=models.CharField(choices=[('debian', 'Debian 8'), ('ubuntu', 'Ubuntu 16.06'), ('devuan', 'Devuan 1'), ('centos', 'CentOS 7')], default='ubuntu', max_length=10),
),
]

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-04 03:31
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('hosting', '0033_virtualmachinetype_configuration'),
]
operations = [
migrations.RemoveField(
model_name='virtualmachinetype',
name='configuration',
),
migrations.AlterField(
model_name='virtualmachineplan',
name='configuration',
field=models.CharField(choices=[('debian', 'Debian 8'), ('ubuntu', 'Ubuntu 16.06'), ('devuan', 'Devuan 1'), ('centos', 'CentOS 7')], max_length=20),
),
migrations.AlterField(
model_name='virtualmachineplan',
name='vm_type',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='hosting.VirtualMachineType'),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-06 23:02
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0034_auto_20170504_0331'),
]
operations = [
migrations.AddField(
model_name='virtualmachineplan',
name='opennebula_id',
field=models.IntegerField(default=0),
preserve_default=False,
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-06 23:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0035_virtualmachineplan_opennebula_id'),
]
operations = [
migrations.AlterField(
model_name='virtualmachineplan',
name='opennebula_id',
field=models.IntegerField(null=True),
),
]

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-07 04:49
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hosting', '0031_hostingbill_total_price'),
('hosting', '0036_auto_20170506_2312'),
]
operations = [
]

View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-12 10:06
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0037_merge'),
]
operations = [
migrations.RemoveField(
model_name='virtualmachineplan',
name='vm_type',
),
migrations.RemoveField(
model_name='hostingorder',
name='vm_plan',
),
migrations.AddField(
model_name='hostingorder',
name='vm_id',
field=models.IntegerField(default=0),
),
migrations.DeleteModel(
name='VirtualMachinePlan',
),
migrations.DeleteModel(
name='VirtualMachineType',
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-12 16:37
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0038_auto_20170512_1006'),
]
operations = [
migrations.AddField(
model_name='hostingorder',
name='price',
field=models.FloatField(default=0),
preserve_default=False,
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-13 11:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0039_hostingorder_price'),
]
operations = [
migrations.CreateModel(
name='HostingPlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('disk_size', models.FloatField(default=0.0)),
('cpu_cores', models.FloatField(default=0.0)),
('memory', models.FloatField(default=0.0)),
],
),
]

View file

@ -1,26 +1,26 @@
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from .models import VirtualMachinePlan
from opennebula_api.serializers import VirtualMachineTemplateSerializer
from opennebula_api.models import OpenNebulaManager
from .models import HostingPlan
class ProcessVMSelectionMixin(object):
def post(self, request, *args, **kwargs):
hosting = request.POST.get('configuration')
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(hosting)
vm_specs = {
'cores': request.POST.get('cores'),
'memory': request.POST.get('memory'),
'disk_size': request.POST.get('disk_space'),
'hosting_company': request.POST.get('hosting_company'),
'location_code': request.POST.get('location_code'),
'configuration': hosting,
'configuration_detail': configuration_detail,
'final_price': request.POST.get('final_price')
}
request.session['vm_specs'] = vm_specs
template_id = int(request.POST.get('vm_template_id'))
configuration_id = int(request.POST.get('configuration'))
template = OpenNebulaManager().get_template(template_id)
data = VirtualMachineTemplateSerializer(template).data
configuration = HostingPlan.objects.get(id=configuration_id)
request.session['template'] = data
request.session['specs'] = configuration.serialize()
if not request.user.is_authenticated():
request.session['vm_specs'] = vm_specs
request.session['next'] = reverse('hosting:payment')
return redirect(reverse('hosting:login'))
return redirect(reverse('hosting:payment'))

View file

@ -1,189 +1,57 @@
import os
import socket
import logging
import oca
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.functional import cached_property
from django.conf import settings
from Crypto.PublicKey import RSA
from stored_messages.settings import stored_messages_settings
from membership.models import StripeCustomer
from membership.models import StripeCustomer, CustomUser
from utils.models import BillingAddress
from utils.mixins import AssignPermissionsMixin
from .managers import VMPlansManager
logger = logging.getLogger(__name__)
class VirtualMachineType(models.Model):
HETZNER_NUG = 'hetzner_nug'
HETZNER = 'hetzner'
HETZNER_R6 = 'hetzner_raid6'
HETZNER_G = 'hetzner_glusterfs'
BERN = 'bern'
DE_LOCATION = 'DE'
CH_LOCATION = 'CH'
class HostingPlan(models.Model):
disk_size = models.FloatField(default=0.0)
cpu_cores = models.FloatField(default=0.0)
memory = models.FloatField(default=0.0)
HOSTING_TYPES = (
(HETZNER_NUG, 'Hetzner No Uptime Guarantee'),
(HETZNER, 'Hetzner'),
(HETZNER_R6, 'Hetzner Raid6'),
(HETZNER_G, 'Hetzner Glusterfs'),
(BERN, 'Bern'),
)
LOCATIONS_CHOICES = (
(DE_LOCATION, 'Germany'),
(CH_LOCATION, 'Switzerland'),
)
description = models.TextField()
base_price = models.FloatField()
memory_price = models.FloatField()
core_price = models.FloatField()
disk_size_price = models.FloatField()
hosting_company = models.CharField(max_length=30, choices=HOSTING_TYPES)
location = models.CharField(max_length=3, choices=LOCATIONS_CHOICES)
def __str__(self):
return "%s" % (self.get_hosting_company_display())
@classmethod
def get_serialized_vm_types(cls):
return [vm.get_serialized_data()
for vm in cls.objects.all()]
def calculate_price(self, specifications):
price = float(specifications['cores']) * self.core_price
price += float(specifications['memory']) * self.memory_price
price += float(specifications['disk_size']) * self.disk_size_price
price += self.base_price
return price
def defeault_price(self):
price = self.base_price
price += self.core_price
price += self.memory_price
price += self.disk_size_price * 10
return price
def get_serialized_data(self):
def serialize(self):
return {
'description': self.description,
'base_price': self.base_price,
'core_price': self.core_price,
'disk_size_price': self.disk_size_price,
'memory_price': self.memory_price,
'hosting_company_name': self.get_hosting_company_display(),
'hosting_company': self.hosting_company,
'default_price': self.defeault_price(),
'location_code': self.location,
'location': self.get_location_display(),
'id': self.id,
'cpu':self.cpu_cores,
'memory': self.memory,
'disk_size': self.disk_size,
'price': self.price(),
}
class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
PENDING_STATUS = 'pending'
ONLINE_STATUS = 'online'
CANCELED_STATUS = 'canceled'
VM_STATUS_CHOICES = (
(PENDING_STATUS, 'Pending for activation'),
(ONLINE_STATUS, 'Online'),
(CANCELED_STATUS, 'Canceled')
)
DJANGO = 'django'
RAILS = 'rails'
NODEJS = 'nodejs'
VM_CONFIGURATION = (
(DJANGO, 'Ubuntu 14.04, Django'),
(RAILS, 'Ubuntu 14.04, Rails'),
(NODEJS, 'Debian, NodeJS'),
)
permissions = ('view_virtualmachineplan',
'cancel_virtualmachineplan',
'change_virtualmachineplan')
cores = models.IntegerField()
memory = models.IntegerField()
disk_size = models.IntegerField()
vm_type = models.ForeignKey(VirtualMachineType)
price = models.FloatField()
public_key = models.TextField(blank=True)
status = models.CharField(max_length=20, choices=VM_STATUS_CHOICES, default=PENDING_STATUS)
ip = models.CharField(max_length=50, blank=True)
configuration = models.CharField(max_length=20, choices=VM_CONFIGURATION)
objects = VMPlansManager()
class Meta:
permissions = (
('view_virtualmachineplan', 'View Virtual Machine Plan'),
('cancel_virtualmachineplan', 'Cancel Virtual Machine Plan'),
)
def __str__(self):
return self.name
@cached_property
def hosting_company_name(self):
return self.vm_type.get_hosting_company_display()
@cached_property
def location(self):
return self.vm_type.get_location_display()
@cached_property
def name(self):
name = 'vm-%s' % self.id
return name
@cached_property
def notifications(self):
stripe_customer = StripeCustomer.objects.get(hostingorder__vm_plan=self)
backend = stored_messages_settings.STORAGE_BACKEND()
messages = backend.inbox_list(stripe_customer.user)
return messages
@classmethod
def create(cls, data, user):
instance = cls.objects.create(**data)
instance.assign_permissions(user)
return instance
@staticmethod
def generate_RSA(bits=2048):
'''
Generate an RSA keypair with an exponent of 65537 in PEM format
param: bits The key length in bits
Return private key and public key
'''
new_key = RSA.generate(2048, os.urandom)
public_key = new_key.publickey().exportKey("OpenSSH")
private_key = new_key.exportKey("PEM")
return private_key, public_key
def generate_keys(self):
private_key, public_key = self.generate_RSA()
self.public_key = public_key
self.save(update_fields=['public_key'])
return private_key, public_key
def cancel_plan(self):
self.status = self.CANCELED_STATUS
self.save(update_fields=['status'])
def get_serialized_configs(cls):
return [cfg.serialize()
for cfg in cls.objects.all()]
def price(self):
price = self.disk_size * 0.6
price += self.cpu_cores * 5
price += self.memory * 2
return price
class HostingOrder(AssignPermissionsMixin, models.Model):
ORDER_APPROVED_STATUS = 'Approved'
ORDER_DECLINED_STATUS = 'Declined'
vm_plan = models.ForeignKey(VirtualMachinePlan, related_name='hosting_orders')
vm_id = models.IntegerField(default=0)
customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress)
created_at = models.DateTimeField(auto_now_add=True)
@ -191,6 +59,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
last4 = models.CharField(max_length=4)
cc_brand = models.CharField(max_length=10)
stripe_charge_id = models.CharField(max_length=100, null=True)
price = models.FloatField()
permissions = ('view_hostingorder',)
@ -207,9 +76,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
@classmethod
def create(cls, vm_plan=None, customer=None, billing_address=None):
instance = cls.objects.create(vm_plan=vm_plan, customer=customer,
billing_address=billing_address)
def create(cls, price=None, vm_id=None, customer=None, billing_address=None):
instance = cls.objects.create(
price=price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
instance.assign_permissions(customer.user)
return instance
@ -223,14 +96,55 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
self.cc_brand = stripe_charge.source.brand
self.save()
def get_cc_data(self):
return {
'last4': self.last4,
'cc_brand': self.cc_brand,
} if self.last4 and self.cc_brand else None
class UserHostingKey(models.Model):
user = models.ForeignKey(CustomUser)
public_key = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100)
@staticmethod
def generate_RSA(bits=2048):
'''
Generate an RSA keypair with an exponent of 65537 in PEM format
param: bits The key length in bits
Return private key and public key
'''
new_key = RSA.generate(2048, os.urandom)
public_key = new_key.publickey().exportKey("OpenSSH")
private_key = new_key.exportKey("PEM")
return private_key, public_key
@classmethod
def generate_keys(cls):
private_key, public_key = cls.generate_RSA()
# self.public_key = public_key
# self.save(update_fields=['public_key'])
return private_key, public_key
class HostingBill(AssignPermissionsMixin, models.Model):
customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress)
total_price = models.FloatField(default=0.0)
permissions = ('view_hostingbill',)
class Meta:
permissions = (
('view_hostingbill', 'View Hosting Bill'),
)
def __str__(self):
return "%s" % (self.customer.user.email)
@classmethod
def create(cls, customer=None, billing_address=None):
instance = cls.objects.create(customer=customer, billing_address=billing_address)
return instance

View file

@ -25,6 +25,27 @@ $( document ).ready(function() {
});
var hasCreditcard = window.hasCreditcard || false;
console.log("has creditcard", hasCreditcard);
// hasCreditcard= true;
var submit_form_btn = $('#payment_button_with_creditcard');
submit_form_btn.on('click', submit_payment);
function submit_payment(e){
e.preventDefault();
console.log("creditcard sdasd");
// if (hasCreditcard) {
$('#billing-form').submit();
console.log("has creditcard2");
// }
// $form.submit();
}
var $form = $('#payment-form');
$form.submit(payWithStripe);

View file

@ -72,6 +72,11 @@
<i class="fa fa-credit-card"></i> {% trans "My Orders"%}
</a>
</li>
<li>
<a href="{% url 'hosting:key_pair' %}">
<i class="fa fa-key" aria-hidden="true"></i> {% trans "Keys"%}
</a>
</li>
<li>
<a href="{% url 'hosting:notifications' %}">
<i class="fa fa-bell"></i> {% trans "Notifications "%}

View file

@ -0,0 +1,91 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% load i18n %}
{% block content %}
<div class="container">
<div class="orders-container" style="padding-bottom: 15%">
{# Adress bar #}
<div class="row">
<div class="invoice-title">
<h2>{% trans "Invoice"%}</h2><h3 class="pull-right">{% trans "Order #"%} {{bill.id}}</h3>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-6">
<address>
{{bill.customer.user.name}}<br>
{{bill.billing_address.street_address}},{{bill.billing_address.postal_code}}<br>
{{bill.billing_address.city}}, {{bill.billing_address.country}}.
</address>
</div>
<div class="col-sm-6 text-right">
<address>
{% trans "ungleich GmbH" %}<br>
{% trans "buchhaltung@ungleich.ch" %}<br>
{% trans "Hauptstrasse 14"%}<br>
{% trans "CH-8775 Luchsingen"%}<br>
{% trans "Mwst-Nummer: CHE-109.549.333 MWST"%}<br>
</address>
</div>
</div>
<hr>
<table class="table table-bordered">
{# Bill header #}
<thead>
<tr>
<th>Name</th>
<th>Cores</th>
<th>Memory</th>
<th>Disk Size</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{# Bill items#}
{% for vm in vms %}
<tr>
<td>{{ vm.name }}</td>
<td><span class="pull-right">{{ vm.cores }}</span></td>
<td><span class="pull-right">{{ vm.memory|floatformat }} GiB </span></td>
<td><span class="pull-right">{{ vm.disk_size|floatformat }} GiB </span></td>
<td><span class="pull-right">{{ vm.price|floatformat }} CHF</span></td>
</tr>
{% endfor %}
{# Bill total#}
<tr>
<td colspan=4> {% trans "Total:" %} </td>
<td> <span class="pull-right">{{ bill.total_price|floatformat}} CHF </span></td>
</tr>
</tbody>
</table>
<hr>
{# Bill Footer #}
<div class="row">
{% trans "Alles Preise in CHF mit 8% Mehrwertsteuer." %}
{% trans "Betrag zahlbar innerhalb von 30 Tagen ab Rechnungseingang." %}
{% trans "Kontoverbindung:" %}
<div class="row">
<div class="col-sm-6">
{% trans "IBAN:" %}
</div>
<div class="col-sm-6">
{% trans "BIC:" %}
</div>
</div>
<div class="row">
<div class="col-sm-6">
{% trans "CH02 0900 0000 6071 8848 8" %}
</div>
<div class="col-sm-6">
{% trans "POFICHBEXXX" %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,14 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% load i18n %}
{% block content %}
<div class="container">
<div class="container orders-container">
<h1>Error</h1>
<p> Could not get HostingBill object for client. </p>
<p> Please create a HostingBill object via the admin page </p>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,60 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% load i18n %}
{% block content %}
<div>
<div class="container orders-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<table class="table borderless table-hover">
<h3>{% trans "Customers"%}</h3>
<br/>
<thead>
<tr>
<th>{% trans "Name"%}</th>
<th>{% trans "Email"%}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.user.name}}</td>
<td>{{ user.user.email}}</td>
<td>
<button type="button" class="btn btn-default"><a
href="{% url 'hosting:bills' user.id %}">{% trans "View Bill"%}</a>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{{ request.path }}?page={{ page_obj.previous_page_number }}">{% trans "previous"%}</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{{ request.path }}?page={{ page_obj.next_page_number }}">{% trans "next"%}</a>
{% endif %}
</span>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,57 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 i18n %}
{% block content %}
<div>
<div class="container dashboard-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="col-md-12">
<br/>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% if not error %}
<h3><i class="fa fa-server" aria-hidden="true"></i> {% trans "New Virtual Machine"%} </h3>
<hr/>
<form method="POST" action="">
{% csrf_token %}
<div class="form-group">
Select VM Template:
<select name="vm_template_id">
{% for template in templates %}
<option value="{{template.id}}">{{template.name}} </option>
{% endfor %}
</select>
</div>
<div class="form-group">
Select VM Configuration:
<select name="configuration">
{% for config in configuration_options %}
<option value="{{config.id}}">
CORE: {{config.cpu|floatformat}},
RAM: {{config.memory|floatformat}} GiB,
SSD: {{config.disk_size|floatformat}} GiB,
PRICE: {{config.price|floatformat}} CHF/Month
</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<button class="btn btn-success" >{% trans "Start VM"%} </button>
</div>
</form>
{% endif %}
</div>
</div>
</div>
</div>
{%endblock%}

View file

@ -18,64 +18,23 @@
<div class="row text-center">
<div class="block col-md-offset-3">
{% for vm in vm_types %}
{% for vm in configuration_options %}
<div class="col-xs-12 col-sm-6 col-md-4">
<form class="form-inline" method="POST" action="{{request.path}}">
{% csrf_token %}
<input type="hidden" name="hosting_company" value="{{vm.hosting_company}}">
<input type="hidden" name="location_code" value="{{vm.location_code}}">
<input type="hidden" name="configuration" value="{{vm.id}}">
<ul class="pricing {% cycle 'p-red' 'p-black' 'p-red' 'p-yel' %}">
<li class="type">
<!-- <img src="http://bread.pp.ua/n/settings_g.svg" alt=""> -->
<h3 >{{vm.location_code}}</h3>
<br/>
<img class="img-responsive" src="{{ STATIC_URL }}hosting/img/{{vm.location_code}}_flag.png" alt="">
</li>
<li>
<!-- Single button -->
<div class="btn-group">
<div class="form-group">
<label for="cores">Location: </label>
{{vm.location}}
</div>
</div>
</li>
<li>
<label for="configuration">Configuration: </label>
{% if select_configuration %}
<select class="form-control" name="configuration" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
{% for key,value in configuration_options.items %}
<option value="{{key}}">{{ value }}</option>
{% endfor %}
</select>
{% else %}
<input type="hidden" name="configuration_detail" value="{{configuration_detail}}">
<input type="hidden" name="configuration" value="{{hosting}}">
<!-- Single button -->
<div class="btn-group">
<div class="form-group">
<label>Configuration: </label>
{{configuration_detail}}
</div>
</div>
{% endif %}
</li>
<li>
<!-- Single button -->
<div class="btn-group">
<div class="form-group">
<label for="cores">Cores: </label>
<select class="form-control cores-selector" name="cores" id="{{vm.hosting_company}}-cores" data-vm-type="{{vm.hosting_company}}">
{% with ''|center:10 as range %}
{% for _ in range %}
<option>{{ forloop.counter }}</option>
{% endfor %}
{% endwith %}
</select>
<label for="cores">Cores: {{vm.cores}}</label>
</div>
</div>
@ -83,30 +42,28 @@
<li>
<div class="form-group">
<div class="btn-group">
<label for="memory">Memory: </label>
<select class="form-control memory-selector" name="memory" id="{{vm.hosting_company}}-memory" data-vm-type="{{vm.hosting_company}}">
{% with ''|center:50 as range %}
{% for _ in range %}
<option>{{ forloop.counter }}</option>
{% endfor %}
{% endwith %}
</select>
<span>GiB</span>
<label for="memory">Memory: {{vm.memory}} GiB</label>
</div>
</div>
</li>
<li>
<div class="form-group row">
<div class="col-xs-offset-1 col-xs-9 col-sm-12 col-md-12 col-md-offset-0">
<label for="Disk Size">Disk Size: </label>
<input class="form-control short-input text-center disk-space-selector" name="disk_space" type="number" id="{{vm.hosting_company}}-disk_space" min="10" value="10" step="10" data-vm-type="{{vm.hosting_company}}"/>
<span>GiB</span>
<label for="Disk Size">Disk Size: {{vm.disk_size}} GiB</label>
</div>
</div>
</li>
<li>
<input id="{{vm.hosting_company}}-final-price-input" type="hidden" name="final_price" value="{{vm.default_price|floatformat}}">
<h3 id="{{vm.hosting_company}}-final-price">{{vm.default_price|floatformat}}CHF</h3>
<label for="configuration">Configuration: </label>
<select class="form-control" name="vm_template_id" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
{% for template in templates %}
<option value="{{template.id}}">{{ template.name }}</option>
{% endfor %}
</select>
</li>
<li>
<input type="hidden" name="final_price" value="{{vm.final_price|floatformat}}">
<h3 id="{{vm.hosting_company}}-final-price">{{vm.price|floatformat}} CHF</h3>
<span>per month</span>
</li>
<li>

View file

@ -38,6 +38,8 @@
{% trans "Login"%}
</button>
{% endbuttons %}
<input type='hidden' name='next' value='{{request.GET.next}}'/>
</form>
<span>{% trans "Don't have an account yet ? "%}<a class="unlink" href="{% url 'hosting:signup' %}">{% trans "Sign up"%}</a></span>
<br/>

View file

@ -0,0 +1,48 @@
{% extends "admin/base_site.html" %}
{% block content %}
<form action="{% url 'admin:createvm' %}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" name="create_vm" value="Create VM" />
</form>
{% if vms %}
<section>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Memory</th>
<th>Status</th>
<th>User Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for vm in vms %}
<tr>
<td>{{vm.id}}</td>
<td>{{vm.name}}</td>
<td>{{vm.template.memory}}</td>
<td>{{vm.str_state}}</td>
<td>{{vm.uname}}</td>
<td>
{% if vm.str_state == 'ACTIVE' %}
<a href="{% url 'admin:stopvm' vm.id %}">Stop VM</a>
{% elif vm.str_state == 'STOPPED' %}
&nbsp;&nbsp;&nbsp;<a href="{% url 'admin:startvm' vm.id %}">Start VM</a>
{% endif %}
&nbsp;&nbsp;&nbsp;<a href="{% url 'admin:deletevm' vm.id %}">Delete VM</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
{% else %}
<h4>You do not have any VM.</h4>
{% endif %}
{% endblock %}

View file

@ -49,23 +49,19 @@
<h3><b>{% trans "Order summary"%}</b></h3>
<hr>
<div class="content">
<p><b>{% trans "Type"%}</b> <span class="pull-right">{{order.vm_plan.hosting_company_name}}</span></p>
<p><b>{% trans "Cores"%}</b> <span class="pull-right">{{vm.cores}}</span></p>
<hr>
<p><b>{% trans "Configuration"%}</b> <span class="pull-right">{{order.vm_plan.get_configuration_display}}</span></p>
<p><b>{% trans "Memory"%}</b> <span class="pull-right">{{vm.memory}} GiB</span></p>
<hr>
<p><b>{% trans "Cores"%}</b> <span class="pull-right">{{order.vm_plan.cores}}</span></p>
<p><b>{% trans "Disk space"%}</b> <span class="pull-right">{{vm.disk_size}} GiB</span></p>
<hr>
<p><b>{% trans "Memory"%}</b> <span class="pull-right">{{order.vm_plan.memory}} GiB</span></p>
<hr>
<p><b>{% trans "Disk space"%}</b> <span class="pull-right">{{order.vm_plan.disk_size}} GiB</span></p>
<hr>
<h4>{% trans "Total"%}<p class="pull-right"><b>{{order.vm_plan.price}} CHF</b></p></h4>
<h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}} CHF</b></p></h4>
</div>
<br/>
{% url 'hosting:payment' as payment_url %}
{% if payment_url in request.META.HTTP_REFERER %}
<div class=" content pull-right">
<a href="{% url 'hosting:virtual_machine_key' order.vm_plan.id %}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a>
<a href="{% url 'hosting:virtual_machines'%}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a>
</div>
{% endif %}
</div>

View file

@ -25,7 +25,7 @@
<tr>
<td scope="row">{{ order.id }}</td>
<td>{{ order.created_at }}</td>
<td>{{ order.vm_plan.price }} CHF</td>
<td>{{ order.price }} CHF</td>
<td>{% if order.approved %}
<span class="text-success strong">{% trans "Approved"%}</span>
{% else %}

View file

@ -8,7 +8,7 @@
<div class="col-xs-12 col-md-4 col-md-offset-2 billing">
<h3><b>Billing Address</b></h3>
<hr>
<form role="form" id="billing-form" method="post" action="{% url 'hosting:payment' %}" novalidate>
<form role="form" id="billing-form" method="post" action="" novalidate>
{% for field in form %}
{% csrf_token %}
{% bootstrap_field field show_label=False type='fields'%}
@ -23,6 +23,17 @@
<hr>
<div>
<div>
{% if credit_card_data.last4 %}
<form role="form" id="payment-form-with-creditcard"novalidate>
<h5 class="billing-head">Credit Card</h5>
<h5 class="membership-lead">Last 4: *****{{credit_card_data.last4}}</h5>
<h5 class="membership-lead">Type: {{credit_card_data.cc_brand}}</h5>
<input type="hidden" name="credit_card_needed" value="false"/>
</form>
<button id="payment_button_with_creditcard" class="btn btn-success btn-lg btn-block" type="submit">Submit Payment</button>
{% else %}
<form role="form" id="payment-form" novalidate>
<div class="row">
<div class="col-xs-9 col-md-12">
@ -76,6 +87,7 @@
</form>
{% endif %}
</div>
</div>
</div>
@ -86,17 +98,19 @@
<h3><b>Billing Amount</b></h3>
<hr>
<div class="content">
<p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.location_code}}</span></p>
<!-- <p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.location_code}}</span></p> -->
<!-- <hr> -->
<p><b>Cores</b> <span
class="pull-right">{{request.session.specs.cpu|floatformat}}</span></p>
<hr>
<p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p>
<p><b>Memory</b> <span
class="pull-right">{{request.session.specs.memory|floatformat}} GiB</span></p>
<hr>
<p><b>Configuration</b> <span class="pull-right">{{request.session.vm_specs.configuration_detail}}</span></p>
<p><b>Disk space</b> <span
class="pull-right">{{request.session.specs.disk_size|floatformat}} GiB</span></p>
<hr>
<p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p>
<hr>
<p><b>Disk space</b> <span class="pull-right">{{request.session.vm_specs.disk_size}} GiB</span></p>
<hr>
<h4>Total<p class="pull-right"><b>{{request.session.vm_specs.final_price}} CHF</b></p></h4>
<h4>Total<p
class="pull-right"><b>{{request.session.specs.price }} CHF</b></p></h4>
</div>
</div>
</div>
@ -110,6 +124,7 @@
</div>
</div>
<!-- stripe key data -->
{% if stripe_key %}
<script type="text/javascript">
@ -117,6 +132,13 @@
</script>
{%endif%}
{% if credit_card_data.last4 and credit_card_data.cc_brand %}
<script type="text/javascript">
(function () {window.hasCreditcard = true;})();
</script>
{%endif%}
{%endblock%}

View file

@ -25,12 +25,6 @@
{% trans "Billing"%}
</a>
</li>
<li>
<a href="#orders-v" data-toggle="tab">
<i class="fa fa-credit-card"></i>
{% trans "Orders"%}
</a>
</li>
<li>
<a href="#status-v" data-toggle="tab">
<i class="fa fa-signal" aria-hidden="true"></i> {% trans "Status"%}
@ -47,11 +41,17 @@
<div class="col-md-12 inline-headers">
<h3>{{virtual_machine.hosting_company_name}}</h3>
{% if virtual_machine.ip %}
{% if virtual_machine.ipv6 %}
<div class="pull-right right-place">
<button type="link" data-clipboard-text="{{virtual_machine.ip}}" id="copy_vm_id" class="to_copy btn btn-link"
<button type="link"
data-clipboard-text="{{virtual_machine.ipv4}}" id="copy_vm_id" class="to_copy btn btn-link"
data-toggle="tooltip" data-placement="bottom" title="Copied" data-trigger="click">
Ip: {{virtual_machine.ip}} <i class="fa fa-files-o" aria-hidden="true"></i>
Ipv4: {{virtual_machine.ipv4}} <i class="fa fa-files-o" aria-hidden="true"></i>
</button>
<button type="link"
data-clipboard-text="{{virtual_machine.ipv6}}" id="copy_vm_id" class="to_copy btn btn-link"
data-toggle="tooltip" data-placement="bottom" title="Copied" data-trigger="click">
Ipv6: {{virtual_machine.ipv6}} <i class="fa fa-files-o" aria-hidden="true"></i>
</button>
</div>
{% else %}
@ -85,7 +85,7 @@
<div class="col-md-3">
<div class="well text-center">
<i class="fa fa-hdd-o" aria-hidden="true"></i> {% trans "Disk"%} <br/>
<span class="label label-success">{{virtual_machine.disk_size}} GiB</span>
<span class="label label-success">{{virtual_machine.disk_size|floatformat:2}} GiB</span>
</div>
</div>
</div><!--/row-->
@ -93,7 +93,7 @@
</div><!--/row-->
<div class="row">
<div class="col-md-12">
{% trans "Configuration"%}: {{virtual_machine.get_configuration_display}}
{% trans "Configuration"%}: {{virtual_machine.configuration}}
</div>
</div>
@ -104,59 +104,26 @@
<div class="row ">
<div class="col-md-12 inline-headers">
<h3>{% trans "Current pricing"%}</h3>
<span class="h3 pull-right"><strong>{{virtual_machine.price|floatformat}} CHF</strong>/mo</span>
<span class="h3 pull-right"><strong>{{virtual_machine.price|floatformat}} CHF</strong>/month</span>
<hr>
</div>
</div>
</div>
<div class="tab-pane" id="orders-v">
<div class="row">
<div class="col-md-12">
<table class="table borderless table-hover">
<h3>Orders</h3>
<br/>
<thead>
<tr>
<th>#</th>
<th>{% trans "Date"%}</th>
<th>{% trans "Amount"%}</th>
<th>{% trans "Status"%}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for order in virtual_machine.hosting_orders.all %}
<tr>
<td scope="row">{{order.id}}</td>
<td>{{order.created_at}}</td>
<td>{{order.vm_plan.price}} CHF</td>
<td>{% if order.approved %}
<span class="text-success strong">{% trans "Approved"%}</span>
{% else%}
<span class="text-danger strong">{% trans "Declined"%}</span>
{% endif%}
</td>
<td>
<button type="button" class="btn btn-default"><a href="{% url 'hosting:orders' order.id %}">{% trans "View Detail"%}</a></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div><!--/col-12-->
</div><!--/row-->
</div>
<div class="tab-pane" id="status-v">
<div class="row ">
<div class="col-md-12 inline-headers">
<h3>{% trans "Current status"%}</h3>
<div class="pull-right space-above">
{% if virtual_machine.status == 'pending' %}
<span class="label label-warning"><strong>{{virtual_machine.get_status_display}}</strong></span>
{% elif virtual_machine.status == 'online' %}
<span class="label label-success"><strong>{{virtual_machine.get_status_display}}</strong></span>
{% elif virtual_machine.status == 'canceled'%}
<span class="label label-danger"><strong>{{virtual_machine.get_status_display}}</strong></span>
{% if virtual_machine.state == 'PENDING' %}
<span class="label
label-warning"><strong>Pending</strong></span>
{% elif virtual_machine.state == 'ACTIVE' %}
<span class="label
label-success"><strong>Online</strong></span>
{% elif virtual_machine.state == 'FAILED'%}
<span class="label
label-danger"><strong>Failed</strong></span>
{% endif %}
</div>
</div>
@ -165,23 +132,36 @@
<div class="row">
<div class="col-md-12 space-above-big">
<div class="pull-right">
<form method="POST" id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.id %}">
<form method="POST"
id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}">
{% csrf_token %}
</form>
<button type="text" data-href="{% url 'hosting:virtual_machines' virtual_machine.id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-danger">{% trans "Cancel Virtual Machine"%}</button>
<button type="text" data-href="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-danger">{% trans "Terminate Virtual Machine"%}</button>
</div>
</div>
<div class="col-md-12">
<br/>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Cancel Modal -->
<div class="modal fade" id="confirm-cancel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
{% trans "Cancel your Virtual Machine"%}
{% trans "Terminate your Virtual Machine"%}
</div>
<div class="modal-body">
{% trans "Are you sure do you want to cancel your Virtual Machine "%} {{vm.virtual_machine}} {% trans "plan?"%}
{% trans "Are you sure do you want to cancel your Virtual Machine "%} {{virtual_machine.name}} {% trans "plan?"%}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel"%}</button>
@ -207,12 +187,3 @@
</div>
{%endblock%}

View file

@ -6,30 +6,82 @@
<div class="row">
<div class="col-md-9 col-md-offset-2">
<div class="col-sm-12">
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "SSH Private Key"%} </h3>
<form method="POST" action="" >
{% csrf_token %}
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
<hr/>
{% if not user_key %}
<h3>
{% trans "Upload your own key. "%}
</h3>
<div class="form-group">
<label for="comment">Paste here your public key</label>
<textarea class="form-control" rows="6" name="public_key"></textarea>
</div>
<div class="form-group">
<button class="btn btn-success">{% trans "Upload Key"%} </a>
</div>
<h3>
{% trans "Or generate a new key pair."%}
</h3>
<div class="form-group">
<button class="btn btn-success">{% trans "Generate Key Pair"%} </a>
</div>
{% else %}
<h5> Use your created key to access to the machine. If you lost it, contact us. </h5>
<table class="table borderless table-hover">
<br/>
<thead>
<tr>
<th>{% trans "Name"%}</th>
<th>{% trans "Created at"%} </th>
<th>{% trans "Status"%} </th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">{{user_key.name}}</td>
<td>{{user_key.created_at}}</td>
<td>
<span class="h3 label label-success"><strong>Active</strong></span>
</td>
</tr>
</tbody>
</table>
{% endif %}
</form>
{% if private_key %}
<div class="alert alert-warning">
<strong>{% trans "Warning!"%}</strong>{% trans "You can view your SSH private key once. Copy it or if it wasn't downloaded automatically, just click on Download to start it."%}
<strong>{% trans "Warning!"%}</strong>{% trans "You can download your SSH private key once. Don't lost your key"%}
</div>
<div class="form-group">
<label for="comment">private_key.pem</label>
<textarea class="form-control" rows="6" id="ssh_key">{{private_key}}</textarea>
<textarea class="form-control" rows="6" id="ssh_key" type="hidden" style="display:none">{{private_key}}</textarea>
</div>
<div class="form-group pull-right">
<!-- <div class="form-group pull-right">
<button type="button" id="copy_to_clipboard" data-clipboard-target="#ssh_key" class="btn btn-warning"
data-toggle="tooltip" data-placement="bottom" title="Copied" data-trigger="click">{% trans "Copy to Clipboard"%}</button>
<button type="button" id="download_ssh_key" class="btn btn-warning">{% trans "Download"%}</button>
</div>
</div> -->
{% else %}
<div class="alert alert-warning">
<!-- <div class="alert alert-warning">
<strong>{% trans "Warning!"%}</strong>{% trans "Your SSH private key was already generated and downloaded, if you lost it, contact us. "%}
</div>
{% endif %}
<a class="btn btn-success" href="{% url 'hosting:virtual_machines' virtual_machine.id %}">{% trans "Go to my Virtual Machine Dashboard"%} </a>
--> {% endif %}
<!-- <a class="btn btn-success" href="{% url 'hosting:virtual_machines' %}">{% trans "Generate my key"%} </a> -->
<div class="clearfix"></div>
</div>
</div>
@ -43,10 +95,11 @@
<script type="text/javascript">
var key = window.document.getElementById('ssh_key');
var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(new Blob(['key'], {type: 'text'}));
a.download = 'private_key.pem';
a.href = window.URL.createObjectURL(new Blob([key.value], {type: 'text'}));
a.download = '{{key_name}}.pem';
// Append anchor to body.
document.body.appendChild(a);
@ -55,9 +108,15 @@
// Remove anchor from body
document.body.removeChild(a);
</script>
{%endif%}
{% if next_url %}
<script type="text/javascript">
window.location.href = '{{next_url}}';
</script>
{% endif %}
{%endblock%}

View file

@ -4,15 +4,30 @@
<div>
<div class="container dashboard-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="col-md-8 col-md-offset-2" style="margin-top: 35px;">
<table class="table borderless table-hover">
<h3><i class="fa fa-server" aria-hidden="true"></i>{% trans "Virtual Machines"%} </h3>
<h3 class="pull-left"><i class="fa fa-server" aria-hidden="true"></i> {% trans "Virtual Machines"%} </h3>
<div class="col-md-12">
<br/>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% if not error %}
<p class="pull-right">
<a class="btn btn-success" href="{% url 'hosting:create_virtual_machine' %}" >{% trans "Create VM"%} </a>
</p>
<br/>
<thead>
<tr>
<th>{% trans "ID"%}</th>
<th>{% trans "Location"%} </th>
<th>{% trans "Amount"%}</th>
<th>{% trans "Ipv4"%}</th>
<th>{% trans "Ipv6"%}</th>
<th>{% trans "Status"%}</th>
<th></th>
</tr>
@ -20,27 +35,33 @@
<tbody>
{% for vm in vms %}
<tr>
<td scope="row">{{vm.name}}</td>
<td>{{vm.location}}</td>
<td>{{vm.price}} CHF</td>
<td scope="row">{{vm.vm_id}}</td>
{% if vm.ipv6 %}
<td>{{vm.ipv4}}</td>
<td>{{vm.ipv6}}</td>
{% endif %}
<td>
{% if vm.status == 'pending' %}
<span class="h3 label label-warning"><strong>{{vm.get_status_display}}</strong></span>
{% elif vm.status == 'online' %}
<span class="h3 label label-success"><strong>{{vm.get_status_display}}</strong></span>
{% if vm.state == 'ACTIVE' %}
<span class="h3 label label-success"><strong> {{vm.state}}</strong></span>
{% elif vm.state == 'FAILED' %}
<span class="h3 label label-danger"><strong>{{vm.state}}</strong></span>
{% else %}
<span class="h3 label label-danger"><strong>{{vm.get_status_display}}</strong></span>
<span class="h3 label label-warning"><strong>{{vm.state}}</strong></span>
{% endif %}
</td>
<td>
<button type="button" class="btn btn-default"><a href="{% url 'hosting:virtual_machines' vm.id %}">{% trans "View Detail"%}</a></button>
<button type="button" class="btn btn-default"><a
href="{% url 'hosting:virtual_machines' vm.vm_id %}">{% trans "View Detail"%}</a></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if is_paginated %}
<div class="pagination">

View file

@ -4,7 +4,8 @@ from .views import DjangoHostingView, RailsHostingView, PaymentVMView,\
NodeJSHostingView, LoginView, SignupView, IndexView, \
OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView,\
CreateVirtualMachinesView, HostingBillListView, HostingBillDetailView
urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'),
@ -15,14 +16,17 @@ urlpatterns = [
url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
url(r'bills/?$', HostingBillListView.as_view(), name='bills'),
url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(), name='bills'),
url(r'cancel_order/(?P<pk>\d+)/?$', OrdersHostingDeleteView.as_view(), name='delete_order'),
url(r'create_virtual_machine/?$', CreateVirtualMachinesView.as_view(), name='create_virtual_machine'),
url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'),
url(r'my-virtual-machines/(?P<pk>\d+)/?$', VirtualMachineView.as_view(),
name='virtual_machines'),
# url(r'my-virtual-machines/(?P<pk>\d+)/delete/?$', VirtualMachineCancelView.as_view(),
# name='virtual_machines_cancel'),
url(r'my-virtual-machines/(?P<pk>\d+)/key/?$', GenerateVMSSHKeysView.as_view(),
name='virtual_machine_key'),
url(r'vm-key-pair/?$', GenerateVMSSHKeysView.as_view(),
name='key_pair'),
url(r'^notifications/$', NotificationsView.as_view(), name='notifications'),
url(r'^notifications/(?P<pk>\d+)/?$', MarkAsReadNotificationView.as_view(),
name='read_notification'),

View file

@ -1,12 +1,18 @@
from collections import namedtuple
from django.shortcuts import render
from django.http import Http404
from django.core.urlresolvers import reverse_lazy, reverse
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import View, CreateView, FormView, ListView, DetailView,\
DeleteView, TemplateView, UpdateView
from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login
from django.contrib import messages
from django.conf import settings
from django.shortcuts import redirect
from django.utils.http import urlsafe_base64_decode
from django.contrib.auth.tokens import default_token_generator
from guardian.mixins import PermissionRequiredMixin
from stored_messages.settings import stored_messages_settings
@ -16,28 +22,45 @@ from stored_messages.api import mark_read
from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm, PasswordResetRequestForm
from utils.forms import BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
from utils.mailer import BaseEmail
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder
from .forms import HostingUserSignupForm, HostingUserLoginForm
from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey
from .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm
from .mixins import ProcessVMSelectionMixin
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer,\
VirtualMachineTemplateSerializer
from oca.exceptions import OpenNebulaException
from oca.pool import WrongNameError
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a backend \
connection error. please try again in a few minutes."
class DjangoHostingView(ProcessVMSelectionMixin, View):
template_name = "hosting/django.html"
def get_context_data(self, **kwargs):
HOSTING = 'django'
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING)
templates = OpenNebulaManager().get_templates()
data = VirtualMachineTemplateSerializer(templates, many=True).data
configuration_options = HostingPlan.get_serialized_configs()
# configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING)
context = {
'hosting': HOSTING,
'hosting_long': "Django",
'configuration_detail': configuration_detail,
# 'configuration_detail': configuration_detail,
'domain': "django-hosting.ch",
'google_analytics': "UA-62285904-6",
'vm_types': data,
'email': "info@django-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
'configuration_options': configuration_options,
'templates': templates,
}
return context
@ -54,15 +77,18 @@ class RailsHostingView(ProcessVMSelectionMixin, View):
def get_context_data(self, **kwargs):
HOSTING = 'rails'
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING)
templates = OpenNebulaManager().get_templates()
configuration_options = HostingPlan.get_serialized_configs()
context = {
'hosting': HOSTING,
'configuration_detail': configuration_detail,
'hosting_long': "Ruby On Rails",
'domain': "rails-hosting.ch",
'google_analytics': "UA-62285904-5",
'email': "info@rails-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
'configuration_options': configuration_options,
'templates': templates,
}
return context
@ -77,15 +103,20 @@ class NodeJSHostingView(ProcessVMSelectionMixin, View):
def get_context_data(self, **kwargs):
HOSTING = 'nodejs'
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING)
# configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING)
templates = OpenNebulaManager().get_templates()
configuration_options = HostingPlan.get_serialized_configs()
context = {
'hosting': "nodejs",
'hosting': HOSTING,
'hosting_long': "NodeJS",
'configuration_detail': configuration_detail,
# 'configuration_detail': configuration_detail,
'domain': "node-hosting.ch",
'google_analytics': "UA-62285904-7",
'email': "info@node-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
'templates': templates,
'configuration_options': configuration_options,
}
return context
@ -100,11 +131,17 @@ class HostingPricingView(ProcessVMSelectionMixin, View):
template_name = "hosting/hosting_pricing.html"
def get_context_data(self, **kwargs):
configuration_options = dict(VirtualMachinePlan.VM_CONFIGURATION)
# configuration_options = dict(VirtualMachinePlan.VM_CONFIGURATION)
templates = OpenNebulaManager().get_templates()
configuration_options = HostingPlan.get_serialized_configs()
context = {
'configuration_options': configuration_options,
# 'configuration_options': configuration_options,
'email': "info@django-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
'templates': templates,
'configuration_options': configuration_options,
}
return context
@ -120,13 +157,17 @@ class IndexView(View):
template_name = "hosting/index.html"
def get_context_data(self, **kwargs):
templates = OpenNebulaManager().get_templates()
data = VirtualMachineTemplateSerializer(templates, many=True).data
context = {
'hosting': "nodejs",
'hosting_long': "NodeJS",
'domain': "node-hosting.ch",
'google_analytics': "UA-62285904-7",
'email': "info@node-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
'vm_types': data
# 'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
return context
@ -140,16 +181,18 @@ class IndexView(View):
class LoginView(LoginViewMixin):
template_name = "hosting/login.html"
form_class = HostingUserLoginForm
success_url = reverse_lazy('hosting:orders')
success_url = reverse_lazy('hosting:virtual_machines')
class SignupView(CreateView):
template_name = 'hosting/signup.html'
form_class = HostingUserSignupForm
model = CustomUser
success_url = reverse_lazy('hosting:key_pair')
def get_success_url(self):
next_url = self.request.session.get('next', reverse_lazy('hosting:signup'))
next_url = self.request.session.get(
'next', self.success_url)
return next_url
def form_valid(self, form):
@ -175,6 +218,45 @@ class PasswordResetConfirmView(PasswordResetConfirmViewMixin):
template_name = 'hosting/confirm_reset_password.html'
success_url = reverse_lazy('hosting:login')
def post(self, request, uidb64=None, token=None, *arg, **kwargs):
try:
uid = urlsafe_base64_decode(uidb64)
user = CustomUser.objects.get(pk=uid)
opennebula_client = OpenNebulaManager(
email=user.email,
password=user.password,
)
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
user = None
opennebula_client = None
form = self.form_class(request.POST)
if user is not None and default_token_generator.check_token(user, token):
if form.is_valid():
new_password = form.cleaned_data['new_password2']
user.set_password(new_password)
user.save()
messages.success(request, 'Password has been reset.')
# Change opennebula password
opennebula_client.change_user_password(new_password)
return self.form_valid(form)
else:
messages.error(
request, 'Password reset has not been successful.')
form.add_error(None, 'Password reset has not been successful.')
return self.form_invalid(form)
else:
messages.error(
request, 'The reset password link is no longer valid.')
form.add_error(None, 'The reset password link is no longer valid.')
return self.form_invalid(form)
class NotificationsView(LoginRequiredMixin, TemplateView):
template_name = 'hosting/notifications.html'
@ -206,25 +288,92 @@ class MarkAsReadNotificationView(LoginRequiredMixin, UpdateView):
return HttpResponseRedirect(reverse('hosting:notifications'))
class GenerateVMSSHKeysView(LoginRequiredMixin, DetailView):
model = VirtualMachinePlan
class GenerateVMSSHKeysView(LoginRequiredMixin, FormView):
form_class = UserHostingKeyForm
model = UserHostingKey
template_name = 'hosting/virtual_machine_key.html'
success_url = reverse_lazy('hosting:orders')
login_url = reverse_lazy('hosting:login')
context_object_name = "virtual_machine"
def get_context_data(self, **kwargs):
context = super(
GenerateVMSSHKeysView,
self
).get_context_data(**kwargs)
try:
user_key = UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
user_key = None
context = super(GenerateVMSSHKeysView, self).get_context_data(**kwargs)
vm = self.get_object()
if not vm.public_key:
private_key, public_key = vm.generate_keys()
context.update({
'private_key': private_key,
'public_key': public_key
'user_key': user_key
})
return context
return context
def get_form_kwargs(self):
kwargs = super(GenerateVMSSHKeysView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def form_valid(self, form):
form.save()
context = self.get_context_data()
next_url = self.request.session.get(
'next',
reverse('hosting:create_virtual_machine')
)
if 'next' in self.request.session:
context.update({
'next_url': next_url
})
del (self.request.session['next'])
if form.cleaned_data.get('private_key'):
context.update({
'private_key': form.cleaned_data.get('private_key'),
'key_name': form.cleaned_data.get('name'),
'form': UserHostingKeyForm(request=self.request),
})
owner = self.request.user
# Create OpenNebulaManager
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
# Get OpenNebula user id
user_pool = manager._get_user_pool()
opennebula_user = user_pool.get_by_name(owner.email)
# Get user ssh key
user_key = UserHostingKey.objects.get(user=owner)
# Add ssh key to user
manager.oneadmin_client.call('user.update', opennebula_user.id,
'<CONTEXT><SSH_PUBLIC_KEY>{ssh_key}</SSH_PUBLIC_KEY></CONTEXT>'.format(ssh_key=user_key.public_key))
return render(self.request, self.template_name, context)
def post(self, request, *args, **kwargs):
try:
UserHostingKey.objects.get(
user=self.request.user
)
return HttpResponseRedirect(reverse('hosting:key_pair'))
except UserHostingKey.DoesNotExist:
pass
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
class PaymentVMView(LoginRequiredMixin, FormView):
@ -232,51 +381,92 @@ class PaymentVMView(LoginRequiredMixin, FormView):
login_url = reverse_lazy('hosting:login')
form_class = BillingAddressForm
def get_form_kwargs(self):
current_billing_address = self.request.user.billing_addresses.first()
form_kwargs = super(PaymentVMView, self).get_form_kwargs()
if not current_billing_address:
return form_kwargs
form_kwargs.update({
'initial': {
'street_address': current_billing_address.street_address,
'city': current_billing_address.city,
'postal_code': current_billing_address.postal_code,
'country': current_billing_address.country,
}
})
return form_kwargs
def get_context_data(self, **kwargs):
context = super(PaymentVMView, self).get_context_data(**kwargs)
# Get user
user = self.request.user
# Get user last order
last_hosting_order = HostingOrder.objects.filter(
customer__user=user).last()
# If user has already an hosting order, get the credit card data from
# it
if last_hosting_order:
credit_card_data = last_hosting_order.get_cc_data()
context.update({
'credit_card_data': credit_card_data if credit_card_data else None,
})
context.update({
'stripe_key': settings.STRIPE_API_PUBLIC_KEY
})
return context
def get(self, request, *args, **kwargs):
try:
UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
messages.success(
request,
'In order to create a VM, you create/upload your SSH KEY first.'
)
return HttpResponseRedirect(reverse('hosting:key_pair'))
if 'next' in request.session:
del request.session['next']
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
context = self.get_context_data()
specifications = request.session.get('vm_specs')
vm_type = specifications.get('hosting_company')
vm = VirtualMachineType.objects.get(hosting_company=vm_type)
final_price = vm.calculate_price(specifications)
plan_data = {
'vm_type': vm,
'cores': specifications.get('cores'),
'memory': specifications.get('memory'),
'disk_size': specifications.get('disk_size'),
'configuration': specifications.get('configuration'),
'price': final_price
}
# Get billing address data
billing_address_data = form.cleaned_data
context = self.get_context_data()
template = request.session.get('template')
specs = request.session.get('specs')
vm_template_id = template.get('id', 1)
final_price = specs.get('price')
token = form.cleaned_data.get('token')
owner = self.request.user
# Get or create stripe customer
customer = StripeCustomer.get_or_create(email=self.request.user.email,
customer = StripeCustomer.get_or_create(email=owner.email,
token=token)
if not customer:
form.add_error("__all__", "Invalid credit card")
return self.render_to_response(self.get_context_data(form=form))
# Create Virtual Machine Plan
plan = VirtualMachinePlan.create(plan_data, request.user)
# Create Billing Address
billing_address = form.save()
# Create a Hosting Order
order = HostingOrder.create(vm_plan=plan, customer=customer,
billing_address=billing_address)
# Make stripe charge to a customer
stripe_utils = StripeUtils()
charge_response = stripe_utils.make_charge(amount=final_price,
@ -293,15 +483,59 @@ class PaymentVMView(LoginRequiredMixin, FormView):
charge = charge_response.get('response_object')
# Create OpenNebulaManager
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
# Get user ssh key
try:
user_key = UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
pass
# Create a vm using logged user
vm_id = manager.create_vm(
template_id=vm_template_id,
# XXX: Confi
specs=specs,
ssh_key=user_key.public_key,
)
# Create a Hosting Order
order = HostingOrder.create(
price=final_price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
# Create a Hosting Bill
bill = HostingBill.create(
customer=customer, billing_address=billing_address)
# Create Billing Address for User if he does not have one
if not customer.user.billing_addresses.count():
billing_address_data.update({
'user': customer.user.id
})
billing_address_user_form = UserBillingAddressForm(
billing_address_data)
billing_address_user_form.is_valid()
billing_address_user_form.save()
# Associate an order with a stripe payment
order.set_stripe_charge(charge)
# If the Stripe payment was successed, set order status approved
order.set_approved()
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
# Send notification to ungleich as soon as VM has been booked
context = {
'vm': plan,
'vm': vm,
'order': order,
'base_url': "{0}://{1}".format(request.scheme, request.get_host())
@ -328,6 +562,22 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin, Detai
permission_required = ['view_hostingorder']
model = HostingOrder
def get_context_data(self, **kwargs):
# Get context
context = super(DetailView, self).get_context_data(**kwargs)
obj = self.get_object()
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
try:
vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data
except ConnectionRefusedError:
messages.error(request,
'In order to create a VM, you need to create/upload your SSH KEY first.'
)
return context
class OrdersHostingListView(LoginRequiredMixin, ListView):
template_name = "hosting/orders.html"
@ -353,33 +603,149 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
template_name = "hosting/virtual_machines.html"
login_url = reverse_lazy('hosting:login')
context_object_name = "vms"
model = VirtualMachinePlan
paginate_by = 10
ordering = '-id'
def get_queryset(self):
user = self.request.user
self.queryset = VirtualMachinePlan.objects.active(user)
return super(VirtualMachinesPlanListView, self).get_queryset()
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
try:
queryset = manager.get_vms()
serializer = VirtualMachineSerializer(queryset, many=True)
return serializer.data
except ConnectionRefusedError:
messages.error(self.request,
'We could not load your VMs due to a backend connection \
error. Please try again in a few minutes'
)
self.kwargs['error'] = 'connection'
return []
def get_context_data(self, **kwargs):
error = self.kwargs.get('error')
if error is not None:
print(error)
context = {'error': 'connection'}
else:
context = super(ListView, self).get_context_data(**kwargs)
return context
class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, UpdateView):
class CreateVirtualMachinesView(LoginRequiredMixin, View):
template_name = "hosting/create_virtual_machine.html"
login_url = reverse_lazy('hosting:login')
def get(self, request, *args, **kwargs):
try:
UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
messages.success(
request,
'In order to create a VM, you need to create/upload your SSH KEY first.'
)
return HttpResponseRedirect(reverse('hosting:key_pair'))
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)
def post(self, request):
manager = OpenNebulaManager()
template_id = request.POST.get('vm_template_id')
template = manager.get_template(template_id)
configuration_id = int(request.POST.get('configuration'))
configuration = HostingPlan.objects.get(id=configuration_id)
request.session['template'] = VirtualMachineTemplateSerializer(
template).data
request.session['specs'] = configuration.serialize()
return redirect(reverse('hosting:payment'))
class VirtualMachineView(LoginRequiredMixin, View):
template_name = "hosting/virtual_machine_detail.html"
login_url = reverse_lazy('hosting:login')
model = VirtualMachinePlan
context_object_name = "virtual_machine"
permission_required = ['view_virtualmachineplan', 'cancel_virtualmachineplan']
fields = '__all__'
def get_object(self):
owner = self.request.user
vm = None
manager = OpenNebulaManager(
email=owner.email,
password=owner.password
)
vm_id = self.kwargs.get('pk')
try:
vm = manager.get_vm(vm_id)
return vm
except ConnectionRefusedError:
messages.error(self.request,
'We could not load your VM due to a backend connection \
error. Please try again in a few minutes'
)
return None
except Exception as error:
print(error)
raise Http404()
def get_success_url(self):
vm = self.get_object()
final_url = "%s%s" % (reverse('hosting:virtual_machines', kwargs={'pk': vm.id}),
'#status-v')
final_url = reverse('hosting:virtual_machines')
return final_url
def post(self, *args, **kwargs):
def get(self, request, *args, **kwargs):
vm = self.get_object()
vm.cancel_plan()
try:
serializer = VirtualMachineSerializer(vm)
context = {
'virtual_machine': serializer.data,
}
except:
pass
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
owner = self.request.user
vm = self.get_object()
opennebula_vm_id = self.kwargs.get('pk')
manager = OpenNebulaManager(
email=owner.email,
password=owner.password
)
terminated = manager.delete_vm(
vm.id
)
if not terminated:
messages.error(
request,
'Error terminating VM %s' % (opennebula_vm_id)
)
return HttpResponseRedirect(self.get_success_url())
context = {
'vm': vm,
@ -395,4 +761,53 @@ class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, UpdateView
email = BaseEmail(**email_data)
email.send()
messages.error(
request,
'VM %s terminated successfully' % (opennebula_vm_id)
)
return HttpResponseRedirect(self.get_success_url())
class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin, ListView):
template_name = "hosting/bills.html"
login_url = reverse_lazy('hosting:login')
permission_required = ['view_hostingview']
context_object_name = "users"
model = StripeCustomer
paginate_by = 10
ordering = '-id'
class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailView):
template_name = "hosting/bill_detail.html"
login_url = reverse_lazy('hosting:login')
permission_required = ['view_hostingview']
context_object_name = "bill"
model = HostingBill
def get_object(self, queryset=None):
# Get HostingBill for primary key (Select from customer users)
pk = self.kwargs['pk']
object = HostingBill.objects.filter(customer__id=pk).first()
if object is None:
self.template_name = 'hosting/bill_error.html'
return object
def get_context_data(self, **kwargs):
# Get context
context = super(DetailView, self).get_context_data(**kwargs)
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
# Get vms
queryset = manager.get_vms()
vms = VirtualMachineSerializer(queryset, many=True).data
# Set total price
bill = context['bill']
bill.total_price = 0.0
for vm in vms:
bill.total_price += vm['price']
context['vms'] = vms
return context

View file

@ -9,6 +9,9 @@ class StripePayment(object):
@classmethod
def make_payment(cls,user,amount,token,time):
try:
print(amount)
print(amount)
print(amount)
# Use Stripe's library to make requests...
charge = stripe.Charge.create(
amount=amount,

View file

3
opennebula_api/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
opennebula_api/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class OpennebulaApiConfig(AppConfig):
name = 'opennebula_api'

View file

352
opennebula_api/models.py Normal file
View file

@ -0,0 +1,352 @@
import oca
import socket
import logging
from django.conf import settings
from django.utils.functional import cached_property
from oca.pool import WrongNameError
from oca.exceptions import OpenNebulaException
logger = logging.getLogger(__name__)
class OpenNebulaManager():
"""This class represents an opennebula manager."""
def __init__(self, email=None, password=None):
# Get oneadmin client
self.oneadmin_client = self._get_opennebula_client(
settings.OPENNEBULA_USERNAME,
settings.OPENNEBULA_PASSWORD
)
# Get or create oppenebula user using given credentials
try:
self.opennebula_user = self._get_or_create_user(
email,
password
)
# If opennebula user was created/obtained, get his client
self.client = self._get_opennebula_client(
email,
password
)
except:
pass
def _get_opennebula_client(self, username, password):
return oca.Client("{0}:{1}".format(
username,
password),
"{protocol}://{domain}:{port}{endpoint}".format(
protocol=settings.OPENNEBULA_PROTOCOL,
domain=settings.OPENNEBULA_DOMAIN,
port=settings.OPENNEBULA_PORT,
endpoint=settings.OPENNEBULA_ENDPOINT
))
def _get_or_create_user(self, email, password):
try:
user_pool = self._get_user_pool()
opennebula_user = user_pool.get_by_name(email)
return opennebula_user
except WrongNameError as wrong_name_err:
opennebula_user = self.oneadmin_client.call(oca.User.METHODS['allocate'], email,
password, 'core')
logger.debug(
"User {0} does not exist. Created the user. User id = {1}",
email,
opennebula_user
)
return opennebula_user
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
def _get_user_pool(self):
try:
user_pool = oca.UserPool(self.oneadmin_client)
user_pool.info()
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
return user_pool
def _get_vm_pool(self):
try:
vm_pool = oca.VirtualMachinePool(self.client)
vm_pool.info()
return vm_pool
except AttributeError:
logger.info('Could not connect via client, using oneadmin instead')
try:
vm_pool = oca.VirtualMachinePool(self.oneadmin_client)
vm_pool.info(filter=-2)
return vm_pool
except:
raise ConnectionRefusedError
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
# For now we'll just handle all other errors as connection errors
except:
raise ConnectionRefusedError
def get_vms(self):
try:
return self._get_vm_pool()
except ConnectionRefusedError:
raise ConnectionRefusedError
def get_vm(self, vm_id):
vm_id = int(vm_id)
try:
vm_pool = self._get_vm_pool()
return vm_pool.get_by_id(vm_id)
except:
raise ConnectionRefusedError
def create_template(self, name, cores, memory, disk_size, core_price, memory_price,
disk_size_price, ssh=''):
"""Create and add a new template to opennebula.
:param name: A string representation describing the template.
Used as label in view.
:param cores: Amount of virtual cpu cores for the VM.
:param memory: Amount of RAM for the VM (GB)
:param disk_size: Amount of disk space for VM (GB)
:param core_price: Price of virtual cpu for the VM per core.
:param memory_price: Price of RAM for the VM per GB
:param disk_size_price: Price of disk space for VM per GB
:param ssh: User public ssh key
"""
template_id = oca.VmTemplate.allocate(
self.oneadmin_client,
template_string_formatter.format(
name=name,
vcpu=cores,
cpu=0.1 * cores,
size=1024 * disk_size,
memory=1024 * memory,
# * 10 because we set cpu to *0.1
cpu_cost=10 * core_price,
memory_cost=memory_price,
disk_cost=disk_size_price,
ssh=ssh
)
)
def create_vm(self, template_id, specs, ssh_key=None):
template = self.get_template(template_id)
vm_specs_formatter = """<TEMPLATE>
<MEMORY>{memory}</MEMORY>
<VCPU>{vcpu}</VCPU>
<CPU>{cpu}</CPU>
"""
try:
disk = template.template.disks[0]
image_id = disk.image_id
vm_specs = vm_specs_formatter.format(
vcpu=int(specs['cpu']),
cpu=0.1 * int(specs['cpu']),
memory=1024 * int(specs['memory']),
)
vm_specs += """<DISK>
<TYPE>fs</TYPE>
<SIZE>{size}</SIZE>
<DEV_PREFIX>vd</DEV_PREFIX>
<IMAGE_ID>{image_id}</IMAGE_ID>
</DISK>
</TEMPLATE>
""".format(size=1024 * int(specs['disk_size']),
image_id=image_id)
except:
disk = template.template.disks[0]
image = disk.image
image_uname = disk.image_uname
vm_specs = vm_specs_formatter.format(
vcpu=int(specs['cpu']),
cpu=0.1 * int(specs['cpu']),
memory=1024 * int(specs['memory']),
)
vm_specs += """<DISK>
<TYPE>fs</TYPE>
<SIZE>{size}</SIZE>
<DEV_PREFIX>vd</DEV_PREFIX>
<IMAGE>{image}</IMAGE>
<IMAGE_UNAME>{image_uname}</IMAGE_UNAME>
</DISK>
</TEMPLATE>
""".format(size=1024 * int(specs['disk_size']),
image=image,
image_uname=image_uname)
vm_id = self.client.call(oca.VmTemplate.METHODS['instantiate'],
template.id,
'',
True,
vm_specs,
False)
self.oneadmin_client.call(
'vm.update',
vm_id,
"""<CONTEXT>
<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>
</CONTEXT>
""".format(ssh=ssh_key)
)
try:
self.oneadmin_client.call(
oca.VirtualMachine.METHODS['chown'],
vm_id,
self.opennebula_user.id,
self.opennebula_user.group_ids[0]
)
except AttributeError:
logger.info(
'Could not change owner for vm with id: {}.'.format(vm_id))
self.oneadmin_client.call(
oca.VirtualMachine.METHODS['action'],
'release',
vm_id
)
return vm_id
def delete_vm(self, vm_id):
TERMINATE_ACTION = 'terminate'
vm_terminated = False
try:
self.oneadmin_client.call(
oca.VirtualMachine.METHODS['action'],
TERMINATE_ACTION,
int(vm_id),
)
vm_terminated = True
except socket.timeout as socket_err:
logger.info("Socket timeout error: {0}".format(socket_err))
except OpenNebulaException as opennebula_err:
logger.info(
"OpenNebulaException error: {0}".format(opennebula_err))
except OSError as os_err:
logger.info("OSError : {0}".format(os_err))
except ValueError as value_err:
logger.info("ValueError : {0}".format(value_err))
return vm_terminated
def _get_template_pool(self):
try:
template_pool = oca.VmTemplatePool(self.oneadmin_client)
template_pool.info()
return template_pool
except ConnectionRefusedError:
logger.info('Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL)
)
raise ConnectionRefusedError
except:
raise ConnectionRefusedError
def get_templates(self):
try:
public_templates = [
template
for template in self._get_template_pool()
if 'public-' in template.name
]
return public_templates
except ConnectionRefusedError:
raise ConnectionRefusedError
except:
raise ConnectionRefusedError
def try_get_templates(self):
try:
return self.get_templates()
except:
return []
def get_template(self, template_id):
template_id = int(template_id)
try:
template_pool = self._get_template_pool()
return template_pool.get_by_id(template_id)
except:
raise ConnectionRefusedError
def create_template(self, name, cores, memory, disk_size, core_price, memory_price,
disk_size_price, ssh=''):
"""Create and add a new template to opennebula.
:param name: A string representation describing the template.
Used as label in view.
:param cores: Amount of virtual cpu cores for the VM.
:param memory: Amount of RAM for the VM (GB)
:param disk_size: Amount of disk space for VM (GB)
:param core_price: Price of virtual cpu for the VM per core.
:param memory_price: Price of RAM for the VM per GB
:param disk_size_price: Price of disk space for VM per GB
:param ssh: User public ssh key
"""
template_string_formatter = """<TEMPLATE>
<NAME>{name}</NAME>
<MEMORY>{memory}</MEMORY>
<VCPU>{vcpu}</VCPU>
<CPU>{cpu}</CPU>
<DISK>
<TYPE>fs</TYPE>
<SIZE>{size}</SIZE>
<DEV_PREFIX>vd</DEV_PREFIX>
</DISK>
<CPU_COST>{cpu_cost}</CPU_COST>
<MEMORY_COST>{memory_cost}</MEMORY_COST>
<DISK_COST>{disk_cost}</DISK_COST>
<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>
</TEMPLATE>
"""
template_id = oca.VmTemplate.allocate(
self.oneadmin_client,
template_string_formatter.format(
name=name,
vcpu=cores,
cpu=0.1 * cores,
size=1024 * disk_size,
memory=1024 * memory,
# * 10 because we set cpu to *0.1
cpu_cost=10 * core_price,
memory_cost=memory_price,
disk_cost=disk_size_price,
ssh=ssh
)
)
return template_id
def delete_template(self, template_id):
self.oneadmin_client.call(oca.VmTemplate.METHODS[
'delete'], template_id, False)
def change_user_password(self, new_password):
self.oneadmin_client.call(
oca.User.METHODS['passwd'],
self.opennebula_user.id,
new_password
)

View file

@ -0,0 +1,184 @@
import oca
import ipaddress
from rest_framework import serializers
from oca import OpenNebulaException
from oca.template import VmTemplate
from .models import OpenNebulaManager
class VirtualMachineTemplateSerializer(serializers.Serializer):
"""Serializer to map the virtual machine template instance into JSON format."""
id = serializers.IntegerField(read_only=True)
set_name = serializers.CharField(read_only=True, label='Name')
name = serializers.SerializerMethodField()
cores = serializers.SerializerMethodField()
disk = serializers.IntegerField(write_only=True)
disk_size = serializers.SerializerMethodField()
set_memory = serializers.IntegerField(write_only=True, label='Memory')
memory = serializers.SerializerMethodField()
price = serializers.SerializerMethodField()
def create(self, validated_data):
data = validated_data
template = data.pop('template')
cores = template.pop('vcpu')
name = data.pop('name')
disk_size = data.pop('disk')
memory = template.pop('memory')
manager = OpenNebulaManager()
try:
opennebula_id = manager.create_template(name=name, cores=cores,
memory=memory,
disk_size=disk_size,
core_price=core_price,
disk_size_price=disk_size_price,
memory_price=memory_price)
except OpenNebulaException as err:
raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err))
return manager.get_template(template_id=opennebula_id)
def get_cores(self, obj):
if hasattr(obj.template, 'vcpu'):
return obj.template.vcpu
return ''
def get_disk_size(self, obj):
template = obj.template
disk_size = 0
try:
for disk in template.disks:
disk_size += int(disk.size)
return disk_size / 1024
except:
return 0
def get_price(self, obj):
template = obj.template
price = float(template.cpu) * 5.0
price += (int(template.memory)/1024 * 2.0)
try:
for disk in template.disks:
price += int(disk.size)/1024 * 0.6
except:
pass
return price
def get_memory(self, obj):
return int(obj.template.memory)/1024
def get_name(self, obj):
return obj.name.strip('public-')
class VirtualMachineSerializer(serializers.Serializer):
"""Serializer to map the virtual machine instance into JSON format."""
name = serializers.CharField(read_only=True)
cores = serializers.IntegerField(source='template.vcpu')
disk = serializers.IntegerField(write_only=True)
set_memory = serializers.IntegerField(write_only=True, label='Memory')
memory = serializers.SerializerMethodField()
disk_size = serializers.SerializerMethodField()
ipv4 = serializers.SerializerMethodField()
ipv6 = serializers.SerializerMethodField()
vm_id = serializers.IntegerField(read_only=True, source='id')
state = serializers.CharField(read_only=True, source='str_state')
price = serializers.SerializerMethodField()
ssh_key = serializers.CharField(write_only=True)
configuration = serializers.SerializerMethodField()
template_id = serializers.ChoiceField(
choices=[(key.id, key.name) for key in
OpenNebulaManager().try_get_templates()
],
source='template.template_id',
write_only=True,
default=[]
)
def create(self, validated_data):
owner = validated_data['owner']
ssh_key = validated_data['ssh_key']
cores = validated_data['template']['vcpu']
memory = validated_data['set_memory']
disk = validated_data['disk']
template_id = validated_data['template']['template_id']
specs = {
'cpu' : cores,
'disk_size' : disk,
'memory' : memory,
}
try:
manager = OpenNebulaManager(email=owner.email,
password=owner.password,
)
opennebula_id = manager.create_vm(template_id=template_id,
ssh_key=ssh_key,
specs=specs)
except OpenNebulaException as err:
raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err))
return manager.get_vm(opennebula_id)
def get_memory(self, obj):
return int(obj.template.memory)/1024
def get_disk_size(self, obj):
template = obj.template
disk_size = 0
for disk in template.disks:
disk_size += int(disk.size)
return disk_size / 1024
def get_price(self, obj):
template = obj.template
price = float(template.vcpu) * 5.0
price += (int(template.memory)/1024 * 2.0)
for disk in template.disks:
price += int(disk.size)/1024 * 0.6
return price
def get_configuration(self, obj):
template_id = obj.template.template_id
template = OpenNebulaManager().get_template(template_id)
return template.name.strip('public-')
def get_ipv4(self, obj):
nic = obj.template.nics[0]
if 'vm-ipv6-nat64-ipv4' in nic.network and is_in_v4_range(nic.mac):
return str(v4_from_mac(nic.mac))
else:
return '-'
def get_ipv6(self, obj):
nic = obj.template.nics[0]
return nic.ip6_global
def hexstr2int(string):
return int(string.replace(':', ''), 16)
FIRST_MAC = hexstr2int('02:00:b3:39:79:4d')
FIRST_V4 = ipaddress.ip_address('185.203.112.2')
COUNT = 1000
def v4_from_mac(mac):
"""Calculates the IPv4 address from a MAC address.
mac: string (the colon-separated representation)
returns: ipaddress.ip_address object with the v4 address
"""
return FIRST_V4 + (hexstr2int(mac) - FIRST_MAC)
def is_in_v4_range(mac):
return FIRST_MAC <= hexstr2int(mac) < FIRST_MAC + 1000

133
opennebula_api/tests.py Normal file
View file

@ -0,0 +1,133 @@
from django.test import TestCase
from .models import VirtualMachine, VirtualMachineTemplate, OpenNebulaManager
class OpenNebulaManagerTestCases(TestCase):
"""This class defines the test suite for the opennebula manager model."""
def setUp(self):
"""Define the test client and other test variables."""
self.cores = 1
self.memory = 1
self.disk_size = 10.0
self.email = 'test@test.com'
self.password = 'testtest'
self.manager = OpenNebulaManager(email=None, password=None, create_user=False)
def test_model_can_connect_to_server(self):
"""Test the opennebula manager model can connect to a server."""
try:
user_pool = self.manager._get_user_pool()
except:
user_pool = None
self.assertFalse(user_pool is None)
def test_model_can_create_user(self):
"""Test the opennebula manager model can create a new user."""
old_count = len(self.manager._get_user_pool())
self.manager = OpenNebulaManager(email=self.email,
password=self.password,
create_user=True)
user_pool = self.manager._get_user_pool()
new_count = len(user_pool)
# Remove the user afterwards
user = user_pool.get_by_name(self.email)
user.delete()
self.assertNotEqual(old_count, new_count)
class VirtualMachineTemplateTestCase(TestCase):
"""This class defines the test suite for the virtualmachine template model."""
def setUp(self):
"""Define the test client and other test variables."""
self.template_name = "Standard"
self.base_price = 0.0
self.core_price = 5.0
self.memory_price = 2.0
self.disk_size_price = 0.6
self.cores = 1
self.memory = 1
self.disk_size = 10.0
self.manager = OpenNebulaManager(email=None, password=None, create_user=False)
self.opennebula_id = self.manager.create_template(name=self.template_name,
cores=self.cores,
memory=self.memory,
disk_size=self.disk_size)
self.template = VirtualMachineTemplate(opennebula_id=self.opennebula_id,
base_price=self.base_price,
memory_price=self.memory_price,
core_price=self.core_price,
disk_size_price=self.disk_size_price)
def test_model_can_create_a_virtualmachine_template(self):
"""Test the virtualmachine template model can create a template."""
old_count = VirtualMachineTemplate.objects.count()
self.template.save()
new_count = VirtualMachineTemplate.objects.count()
# Remove the template afterwards
template = self.manager._get_template(self.template.opennebula_id)
template.delete()
self.assertNotEqual(old_count, new_count)
def test_model_can_calculate_price(self):
price = self.cores * self.core_price
price += self.memory * self.memory_price
price += self.disk_size * self.disk_size_price
self.assertEqual(price, self.template.calculate_price())
class VirtualMachineTestCase(TestCase):
def setUp(self):
"""Define the test client and other test variables."""
self.template_name = "Standard"
self.base_price = 0.0
self.core_price = 5.0
self.memory_price = 2.0
self.disk_size_price = 0.6
self.cores = 1
self.memory = 1
self.disk_size = 10.0
self.manager = OpenNebulaManager(email=None, password=None, create_user=False)
self.opennebula_id = self.manager.create_template(name=self.template_name,
cores=self.cores,
memory=self.memory,
disk_size=self.disk_size)
self.template = VirtualMachineTemplate(opennebula_id=self.opennebula_id,
base_price=self.base_price,
memory_price=self.memory_price,
core_price=self.core_price,
disk_size_price=self.disk_size_price)
self.template_id = self.template.opennebula_id()
self.opennebula_id = self.manager.create_virtualmachine(template_id=self.template_id)
self.virtualmachine = VirtualMachine(opennebula_id=self.opennebula_id,
template=self.template)
def test_model_can_create_a_virtualmachine(self):
"""Test the virtualmachine model can create a virtualmachine."""
old_count = VirtualMachine.objects.count()
self.virtualmachine.save()
new_count = VirtualMachine.objects.count()
self.assertNotEqual(old_count, new_count)
def test_model_can_create_a_virtualmachine_for_user(self):
pass
def test_model_can_delete_a_virtualmachine(self):
"""Test the virtualmachine model can delete a virtualmachine."""
self.virtualmachine.save()
old_count = VirtualMachine.objects.count()
VirtualMachine.objects.first().delete()
new_count = VirtualMachine.objects.count()
self.assertNotEqual(old_count, new_count)

18
opennebula_api/urls.py Normal file
View file

@ -0,0 +1,18 @@
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from .views import TemplateCreateView, TemplateDetailsView,\
VmCreateView, VmDetailsView
urlpatterns = {
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^templates/$', TemplateCreateView.as_view(), name="template_create"),
url(r'^templates/(?P<pk>[0-9]+)/$', TemplateDetailsView.as_view(),
name="templates_details"),
url(r'^vms/$', VmCreateView.as_view(), name="vm_create"),
url(r'^vms/(?P<pk>[0-9]+)/$', VmDetailsView.as_view(),
name="vm_details"),
}
urlpatterns = format_suffix_patterns(urlpatterns)

116
opennebula_api/views.py Normal file
View file

@ -0,0 +1,116 @@
from rest_framework import generics
from rest_framework import permissions
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth import authenticate, login
from utils.views import LoginViewMixin
from membership.models import CustomUser, StripeCustomer
from guardian.mixins import PermissionRequiredMixin
from .serializers import VirtualMachineTemplateSerializer, \
VirtualMachineSerializer
from .models import OpenNebulaManager
from rest_framework.exceptions import APIException
class ServiceUnavailable(APIException):
status_code = 503
default_detail = 'Service temporarily unavailable, try again later.'
default_code = 'service_unavailable'
class TemplateCreateView(generics.ListCreateAPIView):
"""This class handles the GET and POST requests."""
serializer_class = VirtualMachineTemplateSerializer
permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser)
def get_queryset(self):
manager = OpenNebulaManager()
return manager.get_templates()
def perform_create(self, serializer):
"""Save the post data when creating a new template."""
serializer.save()
class TemplateDetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests."""
serializer_class = VirtualMachineTemplateSerializer
permission_classes = (permissions.IsAuthenticated)
def get_queryset(self):
manager = OpenNebulaManager()
# We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable
try:
templates = manager.get_templates()
except ConnectionRefusedError:
raise ServiceUnavailable
return templates
class VmCreateView(generics.ListCreateAPIView):
"""This class handles the GET and POST requests."""
serializer_class = VirtualMachineSerializer
permission_classes = (permissions.IsAuthenticated, )
def get_queryset(self):
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
# We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable
try:
vms = manager.get_vms()
except ConnectionRefusedError:
raise ServiceUnavailable
return vms
def perform_create(self, serializer):
"""Save the post data when creating a new template."""
serializer.save(owner=self.request.user)
class VmDetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests."""
permission_classes = (permissions.IsAuthenticated, )
serializer_class = VirtualMachineSerializer
def get_queryset(self):
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
# We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable
try:
vms = manager.get_vms()
except ConnectionRefusedError:
raise ServiceUnavailable
return vms
def get_object(self):
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
# We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable
try:
vm = manager.get_vm(self.kwargs.get('pk'))
except ConnectionRefusedError:
raise ServiceUnavailable
return vm
def perform_destroy(self, instance):
owner = self.request.user
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
# We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable
try:
manager.delete_vm(instance.id)
except ConnectionRefusedError:
raise ServiceUnavailable

View file

@ -82,7 +82,7 @@ stripe==1.33.0
wheel==0.29.0
django-admin-honeypot==1.0.0
coverage==4.3.4
git+https://github.com/ungleich/python-oca.git#egg=python-oca
djangorestframework

View file

@ -100,7 +100,7 @@ class EditCreditCardForm(forms.Form):
class BillingAddressForm(forms.ModelForm):
token = forms.CharField(widget=forms.HiddenInput())
token = forms.CharField(widget=forms.HiddenInput(), required=False)
class Meta:
model = BillingAddress

View file

@ -107,7 +107,8 @@ class StripeUtils(object):
@handleStripeError
def make_charge(self, amount=None, customer=None):
amount = int(amount * 100) # stripe amount unit, in cents
_amount = float(amount)
amount = int(_amount * 100) # stripe amount unit, in cents
charge = self.stripe.Charge.create(
amount=amount, # in cents
currency=self.CURRENCY,

Some files were not shown because too many files have changed in this diff Show more