adding integration opennebula-hosting app

This commit is contained in:
Levi 2017-05-03 23:19:32 -05:00
parent 2658205008
commit ed806910e6
20 changed files with 641 additions and 239 deletions

View File

@ -1,10 +1,13 @@
import random
import string
from django import forms from django import forms
from membership.models import CustomUser from membership.models import CustomUser
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from .models import HostingOrder, VirtualMachinePlan from .models import HostingOrder, VirtualMachinePlan, UserHostingKey
class HostingOrderAdminForm(forms.ModelForm): class HostingOrderAdminForm(forms.ModelForm):
@ -83,3 +86,40 @@ class HostingUserSignupForm(forms.ModelForm):
if not confirm_password == password: if not confirm_password == password:
raise forms.ValidationError("Passwords don't match") raise forms.ValidationError("Passwords don't match")
return confirm_password 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 ''.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
print(cleaned_data)
if not cleaned_data.get('public_key'):
private_key, public_key = UserHostingKey.generate_keys()
cleaned_data.update({
'private_key': private_key,
'public_key': public_key
})
return cleaned_data
class Meta:
model = UserHostingKey
fields = ['user', 'public_key', 'name']

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +1,32 @@
from django.shortcuts import redirect from django.shortcuts import redirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from .models import VirtualMachinePlan from .models import VirtualMachinePlan, VirtualMachineType
class ProcessVMSelectionMixin(object): class ProcessVMSelectionMixin(object):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
hosting = request.POST.get('configuration') configuration = request.POST.get('configuration')
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(hosting) configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration)
vm_specs = { vm_template = request.POST.get('vm_template')
'cores': request.POST.get('cores'), vm_type = VirtualMachineType.objects.get(id=vm_template)
'memory': request.POST.get('memory'), vm_specs = vm_type.get_specs()
'disk_size': request.POST.get('disk_space'), vm_specs.update({
'hosting_company': request.POST.get('hosting_company'), 'configuration_display': configuration_display,
'location_code': request.POST.get('location_code'), 'configuration': configuration,
'configuration': hosting, 'final_price': vm_type.final_price,
'configuration_detail': configuration_detail, 'vm_template': vm_template
'final_price': request.POST.get('final_price') })
} # 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 request.session['vm_specs'] = vm_specs
if not request.user.is_authenticated(): if not request.user.is_authenticated():
request.session['vm_specs'] = vm_specs request.session['vm_specs'] = vm_specs

View File

@ -7,7 +7,7 @@ from django.utils.functional import cached_property
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from stored_messages.settings import stored_messages_settings 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.models import BillingAddress
from utils.mixins import AssignPermissionsMixin from utils.mixins import AssignPermissionsMixin
from .managers import VMPlansManager from .managers import VMPlansManager
@ -15,88 +15,71 @@ from .managers import VMPlansManager
class VirtualMachineType(models.Model): class VirtualMachineType(models.Model):
BASE_PRICE = 0.5
HETZNER_NUG = 'hetzner_nug'
HETZNER = 'hetzner'
HETZNER_R6 = 'hetzner_raid6'
HETZNER_G = 'hetzner_glusterfs'
BERN = 'bern'
DE_LOCATION = 'DE'
CH_LOCATION = 'CH'
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() description = models.TextField()
base_price = models.FloatField() base_price = models.FloatField()
memory_price = models.FloatField() memory_price = models.FloatField()
core_price = models.FloatField() core_price = models.FloatField()
disk_size_price = models.FloatField() disk_size_price = models.FloatField()
hosting_company = models.CharField(max_length=30, choices=HOSTING_TYPES) cores = models.IntegerField()
location = models.CharField(max_length=3, choices=LOCATIONS_CHOICES) memory = models.IntegerField()
disk_size = models.IntegerField()
def __str__(self): def __str__(self):
return "%s" % (self.get_hosting_company_display()) return "VM Type %s" % (self.id)
@cached_property
def final_price(self):
price = self.cores * self.core_price
price += self.memory * self.memory_price
price += self.disk_size * self.disk_size_price
return price
@classmethod @classmethod
def get_serialized_vm_types(cls): def get_serialized_vm_types(cls):
return [vm.get_serialized_data() return [vm.get_serialized_data()
for vm in cls.objects.all()] for vm in cls.objects.all()]
# def calculate_price(self, specifications): def calculate_price(self):
# price = float(specifications['cores']) * self.core_price price = self.cores * self.core_price
# price += float(specifications['memory']) * self.memory_price price += self.memory * self.memory_price
# price += float(specifications['disk_size']) * self.disk_size_price price += self.disk_size * self.disk_size_price
# price += self.base_price # price += self.base_price
# return price return price
@classmethod # @classmethod
def get_price(cls, vm_template): # def get_price(cls, vm_template):
return cls.BASE_PRICE * vm_template # return cls.BASE_PRICE * vm_template
@classmethod def get_specs(self):
def get_specs(cls, vm_template):
return { return {
'memory': 1024 * vm_template, 'memory': self.memory,
'cores': 0.1 * vm_template, 'cores': self.cores,
'disk_size': 10000 * vm_template 'disk_size': self.disk_size
} }
def calculate_price(self, vm_template): # def calculate_price(self, vm_template):
price = self.base_price * vm_template # price = self.base_price * vm_template
return price # return price
def defeault_price(self): # def defeault_price(self):
price = self.base_price # price = self.base_price
price += self.core_price # price += self.core_price
price += self.memory_price # price += self.memory_price
price += self.disk_size_price * 10 # price += self.disk_size_price * 10
return price # return price
def get_serialized_data(self): def get_serialized_data(self):
return { return {
'description': self.description, 'description': self.description,
'base_price': self.base_price,
'core_price': self.core_price, 'core_price': self.core_price,
'disk_size_price': self.disk_size_price, 'disk_size_price': self.disk_size_price,
'memory_price': self.memory_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, 'id': self.id,
'final_price': self.final_price,
'cores': self.cores,
'memory': self.memory,
'disk_size': self.disk_size
} }
@ -112,14 +95,21 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
(CANCELED_STATUS, 'Canceled') (CANCELED_STATUS, 'Canceled')
) )
DJANGO = 'django' # DJANGO = 'django'
RAILS = 'rails' # RAILS = 'rails'
NODEJS = 'nodejs' # NODEJS = 'nodejs'
# VM_CONFIGURATION = (
# (DJANGO, 'Ubuntu 14.04, Django'),
# (RAILS, 'Ubuntu 14.04, Rails'),
# (NODEJS, 'Debian, NodeJS'),
# )
VM_CONFIGURATION = ( VM_CONFIGURATION = (
(DJANGO, 'Ubuntu 14.04, Django'), ('debian', 'Debian 8'),
(RAILS, 'Ubuntu 14.04, Rails'), ('ubuntu', 'Ubuntu 16.06'),
(NODEJS, 'Debian, NodeJS'), ('devuan', 'Devuan 1'),
('centos', 'CentOS 7')
) )
permissions = ('view_virtualmachineplan', permissions = ('view_virtualmachineplan',
@ -129,7 +119,7 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
cores = models.IntegerField() cores = models.IntegerField()
memory = models.IntegerField() memory = models.IntegerField()
disk_size = models.IntegerField() disk_size = models.IntegerField()
vm_type = models.ForeignKey(VirtualMachineType) vm_type = models.ForeignKey(VirtualMachineType, null=True)
price = models.FloatField() price = models.FloatField()
public_key = models.TextField(blank=True) public_key = models.TextField(blank=True)
status = models.CharField(max_length=20, choices=VM_STATUS_CHOICES, default=PENDING_STATUS) status = models.CharField(max_length=20, choices=VM_STATUS_CHOICES, default=PENDING_STATUS)
@ -147,13 +137,13 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
@cached_property # @cached_property
def hosting_company_name(self): # def hosting_company_name(self):
return self.vm_type.get_hosting_company_display() # return self.vm_type.get_hosting_company_display()
@cached_property # @cached_property
def location(self): # def location(self):
return self.vm_type.get_location_display() # return self.vm_type.get_location_display()
@cached_property @cached_property
def name(self): def name(self):
@ -173,24 +163,6 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
instance.assign_permissions(user) instance.assign_permissions(user)
return instance 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): def cancel_plan(self):
self.status = self.CANCELED_STATUS self.status = self.CANCELED_STATUS
self.save(update_fields=['status']) self.save(update_fields=['status'])
@ -242,6 +214,32 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
self.save() self.save()
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 ManageVM(models.Model): class ManageVM(models.Model):
def has_add_permission(self, request): def has_add_permission(self, request):
return False return False

View File

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

View File

@ -0,0 +1,45 @@
{% 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">
<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 Type:
<select name="vm_template">
{% for vm in vm_types %}
<option value="{{vm.id}}">CORE: {{vm.cores}}- RAM: {{vm.memory}} - SSD: {{vm.disk_size}} </option>
{% endfor %}
</select>
</div>
<div class="form-group">
Select VM Configuration:
<select name="configuration">
{% for config in configuration_options %}
<option value="{{config.0}}">{{config.1}} </option>
{% endfor %}
</select>
</div>
<div class="form-group">
<button class="btn btn-success" >{% trans "Start VM"%} </button>
</div>
</form>
</div>
</div>
</div>
</div>
{%endblock%}

View File

@ -24,59 +24,17 @@
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="hosting_company" value="{{vm.hosting_company}}"> <input type="hidden" name="hosting_company" value="{{vm.hosting_company}}">
<input type="hidden" name="location_code" value="{{vm.location_code}}"> <input type="hidden" name="location_code" value="{{vm.location_code}}">
<input type="hidden" name="vm_template" value="1"> <input type="hidden" name="vm_template" value="{{vm.id}}">
<ul class="pricing {% cycle 'p-red' 'p-black' 'p-red' 'p-yel' %}"> <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> <li>
<!-- Single button --> <!-- Single button -->
<div class="btn-group"> <div class="btn-group">
<div class="form-group"> <div class="form-group">
<label for="cores">Location: </label> <label for="cores">Cores: {{vm.cores}}</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>
</div> </div>
</div> </div>
@ -84,30 +42,28 @@
<li> <li>
<div class="form-group"> <div class="form-group">
<div class="btn-group"> <div class="btn-group">
<label for="memory">Memory: </label> <label for="memory">Memory: {{vm.memory}} GiB</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>
</div> </div>
</div> </div>
</li> </li>
<li> <li>
<div class="form-group row"> <div class="form-group row">
<div class="col-xs-offset-1 col-xs-9 col-sm-12 col-md-12 col-md-offset-0"> <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> <label for="Disk Size">Disk Size: {{vm.disk_size}} GiB</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>
</div> </div>
</div> </div>
</li> </li>
<li> <li>
<input id="{{vm.hosting_company}}-final-price-input" type="hidden" name="final_price" value="{{vm.default_price|floatformat}}"> <label for="configuration">Configuration: </label>
<h3 id="{{vm.hosting_company}}-final-price">{{vm.default_price|floatformat}}CHF</h3> <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>
</li>
<li>
<input type="hidden" name="final_price" value="{{vm.final_price|floatformat}}">
<h3 id="{{vm.hosting_company}}-final-price">{{vm.final_price|floatformat}}CHF</h3>
<span>per month</span> <span>per month</span>
</li> </li>
<li> <li>

View File

@ -65,7 +65,7 @@
{% url 'hosting:payment' as payment_url %} {% url 'hosting:payment' as payment_url %}
{% if payment_url in request.META.HTTP_REFERER %} {% if payment_url in request.META.HTTP_REFERER %}
<div class=" content pull-right"> <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:key_pair'%}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@ -86,11 +86,11 @@
<h3><b>Billing Amount</b></h3> <h3><b>Billing Amount</b></h3>
<hr> <hr>
<div class="content"> <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> <!-- <hr> -->
<p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p> <p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p>
<hr> <hr>
<p><b>Configuration</b> <span class="pull-right">{{request.session.vm_specs.configuration_detail}}</span></p> <p><b>Configuration</b> <span class="pull-right">{{request.session.vm_specs.configuration_display}}</span></p>
<hr> <hr>
<p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p> <p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p>
<hr> <hr>

View File

@ -6,30 +6,75 @@
<div class="row"> <div class="row">
<div class="col-md-9 col-md-offset-2"> <div class="col-md-9 col-md-offset-2">
<div class="col-sm-12"> <div class="col-sm-12">
<form method="POST" action="" >
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "SSH Private Key"%} </h3> {% csrf_token %}
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3>
<hr/> <hr/>
{% if not user_key %}
<div class="alert alert-warning">
{% trans "Upload your own key. "%}
</div>
<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>
<div class="alert alert-warning">
{% trans "Or generate a new key pair."%}
</div>
<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 %} {% if private_key %}
<div class="alert alert-warning"> <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 view your SSH private key once. Copy it or if it wasn't downloaded automatically, just click on Download to start it."%}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="comment">private_key.pem</label> <textarea class="form-control" rows="6" id="ssh_key" type="hidden" style="display:none">{{private_key}}</textarea>
<textarea class="form-control" rows="6" id="ssh_key">{{private_key}}</textarea>
</div> </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" <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> 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> <button type="button" id="download_ssh_key" class="btn btn-warning">{% trans "Download"%}</button>
</div> </div> -->
{% else %} {% 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. "%} <strong>{% trans "Warning!"%}</strong>{% trans "Your SSH private key was already generated and downloaded, if you lost it, contact us. "%}
</div> </div>
{% endif %} --> {% endif %}
<a class="btn btn-success" href="{% url 'hosting:virtual_machines' virtual_machine.id %}">{% trans "Go to my Virtual Machine Dashboard"%} </a> <!-- <a class="btn btn-success" href="{% url 'hosting:virtual_machines' %}">{% trans "Generate my key"%} </a> -->
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
</div> </div>
@ -42,11 +87,12 @@
<!-- Force to download ssh key on page load --> <!-- Force to download ssh key on page load -->
<script type="text/javascript"> <script type="text/javascript">
var key = window.document.getElementById('ssh_key'); var key = window.document.getElementById('ssh_key');
var a = window.document.createElement('a'); var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(new Blob(['key'], {type: 'text'})); a.href = window.URL.createObjectURL(new Blob([key.value], {type: 'text'}));
a.download = 'private_key.pem'; a.download = '{{key_name}}.pem';
// Append anchor to body. // Append anchor to body.
document.body.appendChild(a); document.body.appendChild(a);

View File

@ -6,7 +6,10 @@
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">
<table class="table borderless table-hover"> <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>
<p class="pull-right">
<a class="btn btn-success" href="{% url 'hosting:create-virtual-machine' %}" >{% trans "Create VM"%} </a>
</p>
<br/> <br/>
<thead> <thead>
<tr> <tr>

View File

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

View File

@ -8,6 +8,8 @@ from django.views.generic import View, CreateView, FormView, ListView, DetailVie
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.conf import settings from django.conf import settings
from django.shortcuts import redirect
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from stored_messages.settings import stored_messages_settings from stored_messages.settings import stored_messages_settings
@ -15,13 +17,14 @@ from stored_messages.models import Message
from stored_messages.api import mark_read from stored_messages.api import mark_read
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm, PasswordResetRequestForm from utils.forms import BillingAddressForm, PasswordResetRequestForm
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, UserHostingKey
from .forms import HostingUserSignupForm, HostingUserLoginForm from .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
from .opennebula_functions import HostingManageVMAdmin from .opennebula_functions import HostingManageVMAdmin
@ -40,6 +43,7 @@ class DjangoHostingView(ProcessVMSelectionMixin, View):
'google_analytics': "UA-62285904-6", 'google_analytics': "UA-62285904-6",
'email': "info@django-hosting.ch", 'email': "info@django-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(), 'vm_types': VirtualMachineType.get_serialized_vm_types(),
'configuration_options': dict(VirtualMachinePlan.VM_CONFIGURATION)
} }
return context return context
@ -208,26 +212,52 @@ class MarkAsReadNotificationView(LoginRequiredMixin, UpdateView):
return HttpResponseRedirect(reverse('hosting:notifications')) return HttpResponseRedirect(reverse('hosting:notifications'))
class GenerateVMSSHKeysView(LoginRequiredMixin, DetailView): class GenerateVMSSHKeysView(LoginRequiredMixin, FormView):
model = VirtualMachinePlan form_class = UserHostingKeyForm
model = UserHostingKey
template_name = 'hosting/virtual_machine_key.html' template_name = 'hosting/virtual_machine_key.html'
success_url = reverse_lazy('hosting:orders') success_url = reverse_lazy('hosting:orders')
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
context_object_name = "virtual_machine" context_object_name = "virtual_machine"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
try:
user_key = UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
user_key = None
context = super(
GenerateVMSSHKeysView,
self
).get_context_data(**kwargs)
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 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()
if form.cleaned_data.get('private_key'):
context.update({
'private_key': form.cleaned_data.get('private_key'),
'key_name': form.cleaned_data.get('name')
})
# print("form", form.cleaned_data)
return render(self.request, self.template_name, context)
class PaymentVMView(LoginRequiredMixin, FormView): class PaymentVMView(LoginRequiredMixin, FormView):
template_name = 'hosting/payment.html' template_name = 'hosting/payment.html'
@ -251,11 +281,11 @@ class PaymentVMView(LoginRequiredMixin, FormView):
vm_template = specifications.get('vm_template', 1) vm_template = specifications.get('vm_template', 1)
vm_type = VirtualMachineType.objects.first() vm_type = VirtualMachineType.objects.get(id=vm_template)
final_price = VirtualMachineType.get_price(vm_template) specs = vm_type.get_specs()
specs = VirtualMachineType.get_specs(vm_template) final_price = vm_type.calculate_price()
plan_data = { plan_data = {
'vm_type': vm_type, 'vm_type': vm_type,
@ -318,9 +348,9 @@ class PaymentVMView(LoginRequiredMixin, FormView):
# 'vm_template': vm_template # 'vm_template': vm_template
# } # }
hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin) # hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin)
hosting_admin.init_opennebula_client(request) # hosting_admin.init_opennebula_client(request)
hosting_admin.create(request) # hosting_admin.create(request)
# Send notification to ungleich as soon as VM has been booked # Send notification to ungleich as soon as VM has been booked
context = { context = {
@ -381,13 +411,47 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
ordering = '-id' ordering = '-id'
def get_queryset(self): def get_queryset(self):
hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin) # hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin)
print(hosting_admin.show_vms(self.request)) # print(hosting_admin.show_vms(self.request))
user = self.request.user user = self.request.user
self.queryset = VirtualMachinePlan.objects.active(user) self.queryset = VirtualMachinePlan.objects.active(user)
return super(VirtualMachinesPlanListView, self).get_queryset() return super(VirtualMachinesPlanListView, self).get_queryset()
class CreateVirtualMachinesView(LoginRequiredMixin, View):
template_name = "hosting/create_virtual_machine.html"
login_url = reverse_lazy('hosting:login')
def get(self, request, *args, **kwargs):
context = {
'vm_types': VirtualMachineType.get_serialized_vm_types(),
'configuration_options': VirtualMachinePlan.VM_CONFIGURATION
}
# context = {}
return render(request, self.template_name, context)
def post(self, request):
configuration = request.POST.get('configuration')
configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration)
vm_template = request.POST.get('vm_template')
vm_type = VirtualMachineType.objects.get(id=vm_template)
vm_specs = vm_type.get_specs()
vm_specs.update({
'configuration_display': configuration_display,
'configuration': configuration,
'final_price': vm_type.final_price,
'vm_template': vm_template
})
return redirect(reverse('hosting:payment'))
# def get_queryset(self):
# # hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin)
# # print(hosting_admin.show_vms(self.request))
# user = self.request.user
# self.queryset = VirtualMachinePlan.objects.active(user)
# return super(VirtualMachinesPlanListView, self).get_queryset()
class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, UpdateView): class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, UpdateView):
template_name = "hosting/virtual_machine_detail.html" template_name = "hosting/virtual_machine_detail.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')