Merge branch 'develop'
This commit is contained in:
commit
879d0efb9a
101 changed files with 6875 additions and 3206 deletions
2
Changelog
Normal file
2
Changelog
Normal file
|
@ -0,0 +1,2 @@
|
|||
1.0.0: 2017-05-25
|
||||
* Initial stable release
|
|
@ -7,7 +7,7 @@ class BetaAccessForm(forms.ModelForm):
|
|||
email = forms.CharField(widget=forms.EmailInput())
|
||||
|
||||
class Meta:
|
||||
fields = ['email']
|
||||
fields = ['name', 'email']
|
||||
model = BetaAccess
|
||||
|
||||
|
||||
|
|
Binary file not shown.
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -67,8 +215,9 @@ h6 {
|
|||
|
||||
.intro-message > h1 {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
font-size: 5em;
|
||||
font-weight: 400;
|
||||
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;
|
||||
|
@ -200,8 +805,8 @@ p.copyright {
|
|||
|
||||
|
||||
a#forgotpassword {
|
||||
color: #ffffff;
|
||||
|
||||
color: #ffffff;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -234,4 +839,4 @@ a#forgotpassword {
|
|||
}
|
||||
.form-300{
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
4
datacenterlight/static/datacenterlight/font-awesome/css/font-awesome.min.css
vendored
Executable file → Normal file
4
datacenterlight/static/datacenterlight/font-awesome/css/font-awesome.min.css
vendored
Executable file → Normal file
File diff suppressed because one or more lines are too long
BIN
datacenterlight/static/datacenterlight/font-awesome/fonts/FontAwesome.otf
Executable file → Normal file
BIN
datacenterlight/static/datacenterlight/font-awesome/fonts/FontAwesome.otf
Executable file → Normal file
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/font-awesome/fonts/fontawesome-webfont.eot
Executable file → Normal file
BIN
datacenterlight/static/datacenterlight/font-awesome/fonts/fontawesome-webfont.eot
Executable file → Normal file
Binary file not shown.
3185
datacenterlight/static/datacenterlight/font-awesome/fonts/fontawesome-webfont.svg
Executable file → Normal file
3185
datacenterlight/static/datacenterlight/font-awesome/fonts/fontawesome-webfont.svg
Executable file → Normal file
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 434 KiB |
BIN
datacenterlight/static/datacenterlight/font-awesome/fonts/fontawesome-webfont.ttf
Executable file → Normal file
BIN
datacenterlight/static/datacenterlight/font-awesome/fonts/fontawesome-webfont.ttf
Executable file → Normal file
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/font-awesome/fonts/fontawesome-webfont.woff
Executable file → Normal file
BIN
datacenterlight/static/datacenterlight/font-awesome/fonts/fontawesome-webfont.woff
Executable file → Normal file
Binary file not shown.
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Black.ttf
Executable file
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Black.ttf
Executable file
Binary file not shown.
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Bold.ttf
Executable file
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Bold.ttf
Executable file
Binary file not shown.
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-ExtraBold.ttf
Executable file
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-ExtraBold.ttf
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Italic.ttf
Executable file
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Italic.ttf
Executable file
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Light.ttf
Executable file
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Light.ttf
Executable file
Binary file not shown.
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Medium.ttf
Executable file
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Medium.ttf
Executable file
Binary file not shown.
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Regular.ttf
Executable file
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Regular.ttf
Executable file
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-SemiBold.ttf
Executable file
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-SemiBold.ttf
Executable file
Binary file not shown.
Binary file not shown.
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Thin.ttf
Executable file
BIN
datacenterlight/static/datacenterlight/fonts/Montserrat/Montserrat-Thin.ttf
Executable file
Binary file not shown.
Binary file not shown.
92
datacenterlight/static/datacenterlight/fonts/Montserrat/OFL.txt
Executable file
92
datacenterlight/static/datacenterlight/fonts/Montserrat/OFL.txt
Executable 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 |
BIN
datacenterlight/static/datacenterlight/img/pattern.jpg
Executable file
BIN
datacenterlight/static/datacenterlight/img/pattern.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 181 KiB |
55
datacenterlight/static/datacenterlight/js/beta.js
Normal file
55
datacenterlight/static/datacenterlight/js/beta.js
Normal 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
|
12
datacenterlight/static/datacenterlight/js/form.js
Normal file
12
datacenterlight/static/datacenterlight/js/form.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -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');
|
||||
|
@ -51,5 +128,62 @@
|
|||
$('#valueTotal').text(numbers*price*31);
|
||||
}
|
||||
|
||||
|
||||
|
||||
})(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
|
||||
|
||||
|
|
|
@ -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>
|
27
datacenterlight/templates/datacenterlight/beta_access.html
Normal file
27
datacenterlight/templates/datacenterlight/beta_access.html
Normal 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>
|
47
datacenterlight/templates/datacenterlight/beta_success.html
Normal file
47
datacenterlight/templates/datacenterlight/beta_success.html
Normal 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">×</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>
|
|
@ -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,28 +52,29 @@
|
|||
<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>
|
||||
</li>
|
||||
<li>
|
||||
<select class="selectpicker" data-width="fit" onchange="location = this.value;" style="margin-top:10px;">
|
||||
<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 %}
|
||||
|
@ -84,8 +86,7 @@
|
|||
<option value="{{base_url}}/de/datacenterlight/">Deutsch</option>
|
||||
{% endif %}
|
||||
|
||||
</select>
|
||||
</li>
|
||||
</select>
|
||||
</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,216 +121,196 @@
|
|||
</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">
|
||||
<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>
|
||||
<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>
|
||||
</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 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>
|
||||
<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>
|
||||
</ul>
|
||||
|
||||
</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>
|
||||
<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 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>
|
||||
</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 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>
|
||||
<!-- /.option 2 -->
|
||||
<!-- /.content-section-a -->
|
||||
<!-- / pricing -->
|
||||
<a name="our"></a>
|
||||
<div class="content-section-b">
|
||||
<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>
|
||||
<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 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-lg-5 col-sm-pull-6 col-sm-6">
|
||||
<img class="img-responsive" src="{% static 'datacenterlight/img/dog.png' %}" alt="">
|
||||
<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>
|
||||
</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="caption">
|
||||
<h3>{% trans "VM hosting" %} </h3>
|
||||
<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="row text-center">
|
||||
<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>
|
||||
</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>
|
||||
<img class="img-beta" src="{% static 'datacenterlight/img/beta.png' %}" alt="">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-lg-4 hero-feature"></div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- /.container -->
|
||||
</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> </p>
|
||||
<p> </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>
|
||||
<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">×</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>
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="title">
|
||||
<h2>{% trans "Want to know more? Subscribe to our newsletter!" %}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<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="container">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6"></div>
|
||||
<div class="col-lg-6">
|
||||
<h2>{% trans "QUESTIONS?" %} </h2>
|
||||
<h2>{% trans "CONTACT US!" %} </h2>
|
||||
<h3>ungleich GmbH </h3>
|
||||
<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 class="full-contact-section">
|
||||
<div class="intro-header-2 contact-section" id="contact">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<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>
|
||||
</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>
|
||||
|
||||
|
|
234
datacenterlight/templates/datacenterlight/pricing.html
Normal file
234
datacenterlight/templates/datacenterlight/pricing.html
Normal 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">⋅</li>
|
||||
<li>
|
||||
<a href="#about">{% trans "How it works" %}</a></li>
|
||||
<li class="footer-menu-divider">⋅</li>
|
||||
<li>
|
||||
<a href="#about">{% trans "Scale out" %}</a></li>
|
||||
<li>⋅</li>
|
||||
<li>
|
||||
<a href="#about">{% trans "Reliable and light" %}</a></li>
|
||||
<li class="footer-menu-divider">⋅</li>
|
||||
<li>
|
||||
<a href="#services">{% trans "Pricing" %}</a>
|
||||
</li>
|
||||
<li class="footer-menu-divider">⋅</li>
|
||||
<li>
|
||||
<a href="#contact">{% trans "Contact" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="copyright text-muted small">Copyright © 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>
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -111,6 +111,8 @@ INSTALLED_APPS = (
|
|||
'nosystemd',
|
||||
'datacenterlight',
|
||||
'alplora',
|
||||
'rest_framework',
|
||||
'opennebula_api'
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
|
@ -473,4 +475,31 @@ else:
|
|||
|
||||
|
||||
ANONYMOUS_USER_NAME = 'anonymous@ungleich.ch'
|
||||
GUARDIAN_GET_INIT_ANONYMOUS_USER = 'membership.models.get_anonymous_user_instance'
|
||||
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')
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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',
|
||||
|
|
41
hosting/README-opennebula-integration.md
Normal file
41
hosting/README-opennebula-integration.md
Normal 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'
|
||||
```
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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)
|
||||
|
|
35
hosting/migrations/0028_managevm_userhostingkey.py
Normal file
35
hosting/migrations/0028_managevm_userhostingkey.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
24
hosting/migrations/0028_managevms.py
Normal file
24
hosting/migrations/0028_managevms.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
24
hosting/migrations/0029_managevm.py
Normal file
24
hosting/migrations/0029_managevm.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
23
hosting/migrations/0029_userhostingkey_created_at.py
Normal file
23
hosting/migrations/0029_userhostingkey_created_at.py
Normal 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,
|
||||
),
|
||||
]
|
31
hosting/migrations/0030_hostingbill.py
Normal file
31
hosting/migrations/0030_hostingbill.py
Normal 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),
|
||||
),
|
||||
]
|
21
hosting/migrations/0030_userhostingkey_name.py
Normal file
21
hosting/migrations/0030_userhostingkey_name.py
Normal 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,
|
||||
),
|
||||
]
|
23
hosting/migrations/0031_auto_20170503_0554.py
Normal file
23
hosting/migrations/0031_auto_20170503_0554.py
Normal 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',
|
||||
),
|
||||
]
|
20
hosting/migrations/0031_hostingbill_total_price.py
Normal file
20
hosting/migrations/0031_hostingbill_total_price.py
Normal 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),
|
||||
),
|
||||
]
|
33
hosting/migrations/0032_auto_20170504_0315.py
Normal file
33
hosting/migrations/0032_auto_20170504_0315.py
Normal 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,
|
||||
),
|
||||
]
|
20
hosting/migrations/0033_virtualmachinetype_configuration.py
Normal file
20
hosting/migrations/0033_virtualmachinetype_configuration.py
Normal 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),
|
||||
),
|
||||
]
|
30
hosting/migrations/0034_auto_20170504_0331.py
Normal file
30
hosting/migrations/0034_auto_20170504_0331.py
Normal 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'),
|
||||
),
|
||||
]
|
21
hosting/migrations/0035_virtualmachineplan_opennebula_id.py
Normal file
21
hosting/migrations/0035_virtualmachineplan_opennebula_id.py
Normal 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,
|
||||
),
|
||||
]
|
20
hosting/migrations/0036_auto_20170506_2312.py
Normal file
20
hosting/migrations/0036_auto_20170506_2312.py
Normal 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),
|
||||
),
|
||||
]
|
16
hosting/migrations/0037_merge.py
Normal file
16
hosting/migrations/0037_merge.py
Normal 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 = [
|
||||
]
|
34
hosting/migrations/0038_auto_20170512_1006.py
Normal file
34
hosting/migrations/0038_auto_20170512_1006.py
Normal 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',
|
||||
),
|
||||
]
|
21
hosting/migrations/0039_hostingorder_price.py
Normal file
21
hosting/migrations/0039_hostingorder_price.py
Normal 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,
|
||||
),
|
||||
]
|
24
hosting/migrations/0040_hostingplan.py
Normal file
24
hosting/migrations/0040_hostingplan.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -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'))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 "%}
|
||||
|
|
91
hosting/templates/hosting/bill_detail.html
Normal file
91
hosting/templates/hosting/bill_detail.html
Normal 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 %}
|
||||
|
14
hosting/templates/hosting/bill_error.html
Normal file
14
hosting/templates/hosting/bill_error.html
Normal 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 %}
|
||||
|
60
hosting/templates/hosting/bills.html
Normal file
60
hosting/templates/hosting/bills.html
Normal 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 %}
|
57
hosting/templates/hosting/create_virtual_machine.html
Normal file
57
hosting/templates/hosting/create_virtual_machine.html
Normal 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%}
|
|
@ -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>
|
||||
|
|
|
@ -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/>
|
||||
|
|
48
hosting/templates/hosting/managevms.html
Normal file
48
hosting/templates/hosting/managevms.html
Normal 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' %}
|
||||
<a href="{% url 'admin:startvm' vm.id %}">Start VM</a>
|
||||
{% endif %}
|
||||
<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 %}
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
@ -99,4 +99,4 @@
|
|||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -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>
|
||||
<hr>
|
||||
<p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p>
|
||||
<p><b>Disk space</b> <span
|
||||
class="pull-right">{{request.session.specs.disk_size|floatformat}} 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%}
|
||||
|
||||
|
||||
|
|
|
@ -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%}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
@ -42,11 +94,12 @@
|
|||
<!-- Force to download ssh key on page load -->
|
||||
<script type="text/javascript">
|
||||
|
||||
var key = window.document.getElementById('ssh_key');
|
||||
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%}
|
||||
|
||||
|
|
|
@ -4,43 +4,64 @@
|
|||
<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>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for vm in vms %}
|
||||
<tr>
|
||||
<td scope="row">{{vm.name}}</td>
|
||||
<td>{{vm.location}}</td>
|
||||
<td>{{vm.price}} CHF</td>
|
||||
<td>
|
||||
<tr>
|
||||
<td scope="row">{{vm.vm_id}}</td>
|
||||
{% if vm.ipv6 %}
|
||||
<td>{{vm.ipv4}}</td>
|
||||
|
||||
<td>{{vm.ipv6}}</td>
|
||||
{% endif %}
|
||||
|
||||
{% 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>
|
||||
<td>
|
||||
|
||||
{% 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>
|
||||
</td>
|
||||
<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">
|
||||
|
@ -65,4 +86,4 @@
|
|||
|
||||
</div>
|
||||
|
||||
{%endblock%}
|
||||
{%endblock%}
|
||||
|
|
|
@ -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'),
|
||||
|
|
551
hosting/views.py
551
hosting/views.py
|
@ -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,77 +288,185 @@ 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.update({
|
||||
'user_key': user_key
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
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):
|
||||
template_name = 'hosting/payment.html'
|
||||
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
|
||||
|
|
|
@ -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,
|
||||
|
|
0
opennebula_api/__init__.py
Normal file
0
opennebula_api/__init__.py
Normal file
3
opennebula_api/admin.py
Normal file
3
opennebula_api/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
5
opennebula_api/apps.py
Normal file
5
opennebula_api/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OpennebulaApiConfig(AppConfig):
|
||||
name = 'opennebula_api'
|
0
opennebula_api/migrations/__init__.py
Normal file
0
opennebula_api/migrations/__init__.py
Normal file
352
opennebula_api/models.py
Normal file
352
opennebula_api/models.py
Normal 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
|
||||
)
|
184
opennebula_api/serializers.py
Normal file
184
opennebula_api/serializers.py
Normal 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
133
opennebula_api/tests.py
Normal 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
18
opennebula_api/urls.py
Normal 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
116
opennebula_api/views.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue