commit
9b3c65d28a
58 changed files with 2475 additions and 649 deletions
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-20 11:12-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"
|
||||
|
@ -51,28 +51,28 @@ msgstr ""
|
|||
msgid "Thank you!"
|
||||
msgstr "Vielen Dank!"
|
||||
|
||||
#: templates/datacenterlight/index.html:60
|
||||
#: templates/datacenterlight/index.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:362
|
||||
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:365
|
||||
msgid "Reliable and light"
|
||||
msgstr "Zuverlässig und leicht"
|
||||
|
||||
#: templates/datacenterlight/index.html:69
|
||||
#: templates/datacenterlight/index.html:71
|
||||
msgid "Buy VM"
|
||||
msgstr "VM Kaufen"
|
||||
|
||||
#: templates/datacenterlight/index.html:72
|
||||
#: templates/datacenterlight/index.html:361
|
||||
#: templates/datacenterlight/index.html:74
|
||||
#: templates/datacenterlight/index.html:372
|
||||
msgid "Contact"
|
||||
msgstr "Kontakt"
|
||||
|
||||
|
@ -88,27 +88,30 @@ 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:359
|
||||
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 ""
|
||||
"Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der Errichtung eines 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 ""
|
||||
"Kreativität: Verwendung eines modernen und alternativen Designs für unser Datencenter"
|
||||
"Kreativität: Verwendung eines modernen und alternativen Designs für unser "
|
||||
"Datencenter"
|
||||
|
||||
#: templates/datacenterlight/index.html:146
|
||||
#: 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 "
|
||||
|
@ -118,90 +121,88 @@ msgstr ""
|
|||
"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 und ist mit zuverlässiger "
|
||||
"Energieversorgung sowie schneller Internetverbindung ausgestattet. "
|
||||
"Unser Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig."
|
||||
"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
|
||||
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
|
||||
#: templates/datacenterlight/index.html:226
|
||||
msgid "VM hosting"
|
||||
msgstr ""
|
||||
|
||||
#: templates/datacenterlight/index.html:233
|
||||
msgid "Based in Switzerland"
|
||||
msgstr "Standort des Datacenters ist in der Schweiz"
|
||||
|
||||
#: templates/datacenterlight/index.html:242
|
||||
msgid "15 GiB storage(SSD)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/datacenterlight/index.html:245
|
||||
msgid "Buy Now!"
|
||||
msgstr "Kaufe jetzt!"
|
||||
|
||||
#: templates/datacenterlight/index.html:259
|
||||
msgid "I want to try!"
|
||||
msgstr "Das möchte ich haben"
|
||||
|
||||
#: templates/datacenterlight/index.html:269
|
||||
msgid "Email address"
|
||||
msgstr "E-Mail Adresse"
|
||||
|
||||
#: templates/datacenterlight/index.html:272
|
||||
#: templates/datacenterlight/index.html:281
|
||||
msgid "Request Beta Access"
|
||||
msgstr "Beantrage Beta-Zugang"
|
||||
|
||||
#: templates/datacenterlight/index.html:281
|
||||
#: templates/datacenterlight/index.html:289
|
||||
#, fuzzy
|
||||
#| msgid "Request Beta Access"
|
||||
msgid "Request Sent"
|
||||
msgstr "Anfrage verschickt"
|
||||
|
||||
#: templates/datacenterlight/index.html:284
|
||||
#: templates/datacenterlight/index.html:292
|
||||
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: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:320
|
||||
msgid "Switzerland "
|
||||
msgstr "Schweiz"
|
||||
|
||||
#: templates/datacenterlight/index.html:344
|
||||
#: templates/datacenterlight/index.html:337
|
||||
msgid "Questions?"
|
||||
msgstr "Fragen?"
|
||||
|
||||
#: templates/datacenterlight/index.html:337
|
||||
msgid "Contact us!"
|
||||
msgstr "Kontaktiere uns!"
|
||||
|
||||
#: templates/datacenterlight/index.html:355
|
||||
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:368
|
||||
msgid "Pricing"
|
||||
msgstr "Preise"
|
||||
|
||||
#~ 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"
|
||||
|
||||
|
|
|
@ -461,7 +461,6 @@ h6 {
|
|||
padding-bottom: 25px;
|
||||
position: relative;
|
||||
text-align: right;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.contact-section .title h2::before{
|
||||
content: "";
|
||||
|
|
|
@ -219,6 +219,7 @@
|
|||
<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">
|
||||
|
@ -334,7 +335,7 @@
|
|||
</div>
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="title">
|
||||
<h2>{% trans "Questions?" %} {% trans "Contact Us!" %}</h2>
|
||||
<h2>{% trans "Questions?" %} {% trans "Contact us!" %}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,8 @@ from django.contrib import messages
|
|||
from django.core.urlresolvers import reverse_lazy, reverse
|
||||
from utils.mailer import BaseEmail
|
||||
|
||||
from opennebula_api.models import OpenNebulaManager
|
||||
from opennebula_api.serializers import VirtualMachineTemplateSerializer
|
||||
|
||||
class LandingProgramView(TemplateView):
|
||||
template_name = "datacenterlight/landing.html"
|
||||
|
@ -27,6 +29,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')
|
||||
|
|
|
@ -3,7 +3,7 @@ from .base import *
|
|||
ADMINS = (
|
||||
('Nico Schottelius', 'nico.schottelius@ungleich.ch'),
|
||||
('Raul Ascencio', 'raul.ascencio@yandex.com'),
|
||||
('Tomislav Rupcic','tmslav@gmail.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,20 @@
|
|||
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
|
||||
|
||||
|
||||
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'))
|
||||
template = OpenNebulaManager().get_template(template_id)
|
||||
data = VirtualMachineTemplateSerializer(template).data
|
||||
request.session['template'] = data
|
||||
|
||||
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 %}
|
56
hosting/templates/hosting/create_virtual_machine.html
Normal file
56
hosting/templates/hosting/create_virtual_machine.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
{% 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
|
||||
</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%}
|
|
@ -24,58 +24,17 @@
|
|||
{% 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="vm_template_id" 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>
|
||||
<label for="configuration">Configuration: </label>
|
||||
<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 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>
|
||||
<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>
|
||||
|
|
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:key_pair'%}" ><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"%}
|
||||
|
@ -85,7 +79,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 +87,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>
|
||||
|
||||
|
@ -109,54 +103,21 @@
|
|||
</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 +126,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 +181,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,6 +108,7 @@
|
|||
// Remove anchor from body
|
||||
document.body.removeChild(a);
|
||||
|
||||
|
||||
</script>
|
||||
{%endif%}
|
||||
|
||||
|
|
|
@ -4,14 +4,28 @@
|
|||
<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 "Status"%}</th>
|
||||
<th></th>
|
||||
|
@ -19,28 +33,29 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for vm in vms %}
|
||||
<tr>
|
||||
<td scope="row">{{vm.name}}</td>
|
||||
<td>{{vm.location}}</td>
|
||||
<tr>
|
||||
<td scope="row">{{vm.vm_id}}</td>
|
||||
<td>{{vm.price}} CHF</td>
|
||||
<td>
|
||||
|
||||
{% if vm.status == 'pending' %}
|
||||
<span class="h3 label label-warning"><strong>{{vm.get_status_display}}</strong></span>
|
||||
{% elif vm.status == 'online' %}
|
||||
<span class="h3 label label-success"><strong>{{vm.get_status_display}}</strong></span>
|
||||
|
||||
{% if vm.state == 'ACTIVE' %}
|
||||
<span class="h3 label label-success"><strong> {{vm.state}}</strong></span>
|
||||
{% elif vm.state == 'FAILED' %}
|
||||
<span class="h3 label label-danger"><strong>{{vm.state}}</strong></span>
|
||||
{% else %}
|
||||
<span class="h3 label label-danger"><strong>{{vm.get_status_display}}</strong></span>
|
||||
<span class="h3 label label-warning"><strong>{{vm.state}}</strong></span>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-default"><a href="{% url 'hosting:virtual_machines' vm.id %}">{% trans "View Detail"%}</a></button>
|
||||
</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 +80,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'),
|
||||
|
|
507
hosting/views.py
507
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,43 @@ 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_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(),
|
||||
# 'vm_types': VirtualMachineType.get_serialized_vm_types(),
|
||||
# 'configuration_options': dict(VirtualMachinePlan.VM_CONFIGURATION)
|
||||
}
|
||||
|
||||
return context
|
||||
|
@ -54,15 +75,17 @@ class RailsHostingView(ProcessVMSelectionMixin, View):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
HOSTING = 'rails'
|
||||
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING)
|
||||
|
||||
templates = OpenNebulaManager().get_templates()
|
||||
data = VirtualMachineTemplateSerializer(templates, many=True).data
|
||||
|
||||
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(),
|
||||
'vm_types': data,
|
||||
}
|
||||
return context
|
||||
|
||||
|
@ -77,15 +100,18 @@ 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()
|
||||
data = VirtualMachineTemplateSerializer(templates, many=True).data
|
||||
|
||||
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(),
|
||||
'vm_types': data,
|
||||
}
|
||||
return context
|
||||
|
||||
|
@ -100,11 +126,14 @@ 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()
|
||||
data = VirtualMachineTemplateSerializer(templates, many=True).data
|
||||
|
||||
context = {
|
||||
'configuration_options': configuration_options,
|
||||
# 'configuration_options': configuration_options,
|
||||
'email': "info@django-hosting.ch",
|
||||
'vm_types': VirtualMachineType.get_serialized_vm_types(),
|
||||
'vm_types': data,
|
||||
}
|
||||
|
||||
return context
|
||||
|
@ -120,13 +149,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,7 +173,7 @@ 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):
|
||||
|
@ -149,7 +182,7 @@ class SignupView(CreateView):
|
|||
model = CustomUser
|
||||
|
||||
def get_success_url(self):
|
||||
next_url = self.request.session.get('next', reverse_lazy('hosting:signup'))
|
||||
next_url = self.request.session.get('next', reverse_lazy('hosting:virtual_machines'))
|
||||
return next_url
|
||||
|
||||
def form_valid(self, form):
|
||||
|
@ -175,6 +208,43 @@ 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 +276,159 @@ 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()
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
# return HttpResponseRedirect(reverse('hosting:key_pair'))
|
||||
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'))
|
||||
|
||||
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 +445,58 @@ 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 +523,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 +564,148 @@ 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 +721,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
|
||||
|
|
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
338
opennebula_api/models.py
Normal file
338
opennebula_api/models.py
Normal file
|
@ -0,0 +1,338 @@
|
|||
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()
|
||||
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>
|
||||
<CONTEXT>
|
||||
<SSH_PUBLIC_KEY>{ssh}</SSH_PUBLIC_KEY>
|
||||
</CONTEXT>
|
||||
"""
|
||||
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']),
|
||||
ssh=ssh_key
|
||||
|
||||
)
|
||||
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']),
|
||||
ssh=ssh_key
|
||||
|
||||
)
|
||||
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 = template.instantiate(name ='',
|
||||
pending=False,
|
||||
extra_template=vm_specs, )
|
||||
|
||||
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))
|
||||
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
|
||||
)
|
149
opennebula_api/serializers.py
Normal file
149
opennebula_api/serializers.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
import oca
|
||||
|
||||
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.IntegerField(source='template.vcpu')
|
||||
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_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):
|
||||
# TODO: Filter public- away
|
||||
return obj.name
|
||||
|
||||
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()
|
||||
ip = serializers.CharField(read_only=True,
|
||||
source='user_template.ungleich_public_ip',
|
||||
default='-')
|
||||
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
|
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
|
||||
|
|
Loading…
Reference in a new issue