Merge branch 'master' into task/3711/show_ipv4

This commit is contained in:
PCoder 2017-08-20 23:31:29 +05:30
commit 4a75da0140
14 changed files with 578 additions and 224 deletions

169
datacenterlight/tasks.py Normal file
View file

@ -0,0 +1,169 @@
from dynamicweb.celery import app
from celery.utils.log import get_task_logger
from django.conf import settings
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer
from hosting.models import HostingOrder, HostingBill
from utils.forms import UserBillingAddressForm
from datetime import datetime
from membership.models import StripeCustomer
from django.core.mail import EmailMessage
from utils.models import BillingAddress
from celery.exceptions import MaxRetriesExceededError
logger = get_task_logger(__name__)
def retry_task(task, exception=None):
"""Retries the specified task using a "backing off countdown",
meaning that the interval between retries grows exponentially
with every retry.
Arguments:
task:
The task to retry.
exception:
Optionally, the exception that caused the retry.
"""
def backoff(attempts):
return 2 ** attempts
kwargs = {
'countdown': backoff(task.request.retries),
}
if exception:
kwargs['exc'] = exception
raise task.retry(**kwargs)
@app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES)
def create_vm_task(self, vm_template_id, user, specs, template, stripe_customer_id, billing_address_data,
billing_address_id,
charge):
vm_id = None
try:
final_price = specs.get('price')
billing_address = BillingAddress.objects.filter(id=billing_address_id).first()
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
# Create OpenNebulaManager
manager = OpenNebulaManager(email=settings.OPENNEBULA_USERNAME,
password=settings.OPENNEBULA_PASSWORD)
# Create a vm using oneadmin, also specify the name
vm_id = manager.create_vm(
template_id=vm_template_id,
specs=specs,
ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
vm_name="{email}-{template_name}-{date}".format(
email=user.get('email'),
template_name=template.get('name'),
date=int(datetime.now().strftime("%s")))
)
if vm_id is None:
raise Exception("Could not create VM")
# Create a Hosting Order
order = HostingOrder.create(
price=final_price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
# Create a Hosting 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
charge_object = DictDotLookup(charge)
order.set_stripe_charge(charge_object)
# If the Stripe payment succeeds, set order status approved
order.set_approved()
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
context = {
'name': user.get('name'),
'email': user.get('email'),
'cores': specs.get('cpu'),
'memory': specs.get('memory'),
'storage': specs.get('disk_size'),
'price': specs.get('price'),
'template': template.get('name'),
'vm.name': vm['name'],
'vm.id': vm['vm_id'],
'order.id': order.id
}
email_data = {
'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': ['info@ungleich.ch'],
'body': "\n".join(["%s=%s" % (k, v) for (k, v) in context.items()]),
'reply_to': [context['email']],
}
email = EmailMessage(**email_data)
email.send()
except Exception as e:
logger.error(str(e))
try:
retry_task(self)
except MaxRetriesExceededError:
msg_text = 'Finished {} retries for create_vm_task'.format(self.request.retries)
logger.error(msg_text)
# Try sending email and stop
email_data = {
'subject': '{} CELERY TASK ERROR: {}'.format(settings.DCL_TEXT, msg_text),
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': ['info@ungleich.ch'],
'body': ',\n'.join(str(i) for i in self.request.args)
}
email = EmailMessage(**email_data)
email.send()
return
return vm_id
class DictDotLookup(object):
"""
Creates objects that behave much like a dictionaries, but allow nested
key access using object '.' (dot) lookups.
"""
def __init__(self, d):
for k in d:
if isinstance(d[k], dict):
self.__dict__[k] = DictDotLookup(d[k])
elif isinstance(d[k], (list, tuple)):
l = []
for v in d[k]:
if isinstance(v, dict):
l.append(DictDotLookup(v))
else:
l.append(v)
self.__dict__[k] = l
else:
self.__dict__[k] = d[k]
def __getitem__(self, name):
if name in self.__dict__:
return self.__dict__[name]
def __iter__(self):
return iter(self.__dict__.keys())

View file

@ -4,7 +4,6 @@ from .forms import BetaAccessForm
from .models import BetaAccess, BetaAccessVMType, BetaAccessVM, VMTemplate from .models import BetaAccess, BetaAccessVMType, BetaAccessVM, VMTemplate
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.mail import EmailMessage
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from django.shortcuts import render from django.shortcuts import render
from django.shortcuts import redirect from django.shortcuts import redirect
@ -13,14 +12,14 @@ from django.core.exceptions import ValidationError
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from utils.forms import BillingAddressForm, UserBillingAddressForm from utils.forms import BillingAddressForm
from utils.models import BillingAddress from utils.models import BillingAddress
from hosting.models import HostingOrder, HostingBill from hosting.models import HostingOrder
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from datetime import datetime
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineTemplateSerializer, VirtualMachineSerializer, VMTemplateSerializer from opennebula_api.serializers import VirtualMachineTemplateSerializer, VMTemplateSerializer
from datacenterlight.tasks import create_vm_task
class LandingProgramView(TemplateView): class LandingProgramView(TemplateView):
@ -33,7 +32,6 @@ class SuccessView(TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'specs' not in request.session or 'user' not in request.session: if 'specs' not in request.session or 'user' not in request.session:
return HttpResponseRedirect(reverse('datacenterlight:index')) return HttpResponseRedirect(reverse('datacenterlight:index'))
elif 'token' not in request.session: elif 'token' not in request.session:
return HttpResponseRedirect(reverse('datacenterlight:payment')) return HttpResponseRedirect(reverse('datacenterlight:payment'))
elif 'order_confirmation' not in request.session: elif 'order_confirmation' not in request.session:
@ -79,8 +77,7 @@ class PricingView(TemplateView):
manager = OpenNebulaManager() manager = OpenNebulaManager()
template = manager.get_template(template_id) template = manager.get_template(template_id)
request.session['template'] = VirtualMachineTemplateSerializer( request.session['template'] = VirtualMachineTemplateSerializer(template).data
template).data
if not request.user.is_authenticated(): if not request.user.is_authenticated():
request.session['next'] = reverse('hosting:payment') request.session['next'] = reverse('hosting:payment')
@ -132,8 +129,7 @@ class BetaAccessView(FormView):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
messages.add_message( messages.add_message(self.request, messages.SUCCESS, self.success_message)
self.request, messages.SUCCESS, self.success_message)
return render(self.request, 'datacenterlight/beta_success.html', {}) return render(self.request, 'datacenterlight/beta_success.html', {})
@ -185,8 +181,7 @@ class BetaProgramView(CreateView):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
messages.add_message( messages.add_message(self.request, messages.SUCCESS, self.success_message)
self.request, messages.SUCCESS, self.success_message)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
@ -230,8 +225,7 @@ class IndexView(CreateView):
storage_field = forms.IntegerField(validators=[self.validate_storage]) storage_field = forms.IntegerField(validators=[self.validate_storage])
price = request.POST.get('total') price = request.POST.get('total')
template_id = int(request.POST.get('config')) template_id = int(request.POST.get('config'))
template = VMTemplate.objects.filter( template = VMTemplate.objects.filter(opennebula_vm_template_id=template_id).first()
opennebula_vm_template_id=template_id).first()
template_data = VMTemplateSerializer(template).data template_data = VMTemplateSerializer(template).data
name = request.POST.get('name') name = request.POST.get('name')
@ -243,40 +237,35 @@ class IndexView(CreateView):
cores = cores_field.clean(cores) cores = cores_field.clean(cores)
except ValidationError as err: except ValidationError as err:
msg = '{} : {}.'.format(cores, str(err)) msg = '{} : {}.'.format(cores, str(err))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='cores')
self.request, messages.ERROR, msg, extra_tags='cores')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
try: try:
memory = memory_field.clean(memory) memory = memory_field.clean(memory)
except ValidationError as err: except ValidationError as err:
msg = '{} : {}.'.format(memory, str(err)) msg = '{} : {}.'.format(memory, str(err))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='memory')
self.request, messages.ERROR, msg, extra_tags='memory')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
try: try:
storage = storage_field.clean(storage) storage = storage_field.clean(storage)
except ValidationError as err: except ValidationError as err:
msg = '{} : {}.'.format(storage, str(err)) msg = '{} : {}.'.format(storage, str(err))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='storage')
self.request, messages.ERROR, msg, extra_tags='storage')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
try: try:
name = name_field.clean(name) name = name_field.clean(name)
except ValidationError as err: except ValidationError as err:
msg = '{} {}.'.format(name, _('is not a proper name')) msg = '{} {}.'.format(name, _('is not a proper name'))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='name')
self.request, messages.ERROR, msg, extra_tags='name')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
try: try:
email = email_field.clean(email) email = email_field.clean(email)
except ValidationError as err: except ValidationError as err:
msg = '{} {}.'.format(email, _('is not a proper email')) msg = '{} {}.'.format(email, _('is not a proper email'))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='email')
self.request, messages.ERROR, msg, extra_tags='email')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
specs = { specs = {
@ -341,8 +330,7 @@ class IndexView(CreateView):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
messages.add_message( messages.add_message(self.request, messages.SUCCESS, self.success_message)
self.request, messages.SUCCESS, self.success_message)
return super(IndexView, self).form_valid(form) return super(IndexView, self).form_valid(form)
@ -411,7 +399,6 @@ class PaymentOrderView(FormView):
# Create Billing Address # Create Billing Address
billing_address = form.save() billing_address = form.save()
request.session['billing_address_data'] = billing_address_data request.session['billing_address_data'] = billing_address_data
request.session['billing_address'] = billing_address.id request.session['billing_address'] = billing_address.id
request.session['token'] = token request.session['token'] = token
@ -436,13 +423,11 @@ class OrderConfirmationView(DetailView):
stripe_customer_id = request.session.get('customer') stripe_customer_id = request.session.get('customer')
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details( card_details = stripe_utils.get_card_details(customer.stripe_id, request.session.get('token'))
customer.stripe_id, request.session.get('token'))
if not card_details.get('response_object') and not card_details.get('paid'): if not card_details.get('response_object') and not card_details.get('paid'):
msg = card_details.get('error') msg = card_details.get('error')
messages.add_message(self.request, messages.ERROR, msg, extra_tags='failed_payment') messages.add_message(self.request, messages.ERROR, msg, extra_tags='failed_payment')
return HttpResponseRedirect(reverse('datacenterlight:payment') + '#payment_error') return HttpResponseRedirect(reverse('datacenterlight:payment') + '#payment_error')
context = { context = {
'site_url': reverse('datacenterlight:index'), 'site_url': reverse('datacenterlight:index'),
'cc_last4': card_details.get('response_object').get('last4'), 'cc_last4': card_details.get('response_object').get('last4'),
@ -458,8 +443,6 @@ class OrderConfirmationView(DetailView):
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
billing_address_data = request.session.get('billing_address_data') billing_address_data = request.session.get('billing_address_data')
billing_address_id = request.session.get('billing_address') billing_address_id = request.session.get('billing_address')
billing_address = BillingAddress.objects.filter(
id=billing_address_id).first()
vm_template_id = template.get('id', 1) vm_template_id = template.get('id', 1)
final_price = specs.get('price') final_price = specs.get('price')
@ -475,72 +458,8 @@ class OrderConfirmationView(DetailView):
return HttpResponseRedirect(reverse('datacenterlight:payment') + '#payment_error') return HttpResponseRedirect(reverse('datacenterlight:payment') + '#payment_error')
charge = charge_response.get('response_object') charge = charge_response.get('response_object')
create_vm_task.delay(vm_template_id, user, specs, template, stripe_customer_id, billing_address_data,
# Create OpenNebulaManager billing_address_id,
manager = OpenNebulaManager(email=settings.OPENNEBULA_USERNAME, charge)
password=settings.OPENNEBULA_PASSWORD)
# Create a vm using oneadmin, also specify the name
vm_id = manager.create_vm(
template_id=vm_template_id,
specs=specs,
ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
vm_name="{email}-{template_name}-{date}".format(
email=user.get('email'),
template_name=template.get('name'),
date=int(datetime.now().strftime("%s")))
)
# Create a Hosting Order
order = HostingOrder.create(
price=final_price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
# Create a Hosting 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
context = {
'name': user.get('name'),
'email': user.get('email'),
'cores': specs.get('cpu'),
'memory': specs.get('memory'),
'storage': specs.get('disk_size'),
'price': specs.get('price'),
'template': template.get('name'),
'vm.name': vm['name'],
'vm.id': vm['vm_id'],
'order.id': order.id
}
email_data = {
'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': ['info@ungleich.ch'],
'body': "\n".join(["%s=%s" % (k, v) for (k, v) in context.items()]),
'reply_to': [context['email']],
}
email = EmailMessage(**email_data)
email.send()
request.session['order_confirmation'] = True request.session['order_confirmation'] = True
return HttpResponseRedirect(reverse('datacenterlight:order_success')) return HttpResponseRedirect(reverse('datacenterlight:order_success'))

View file

@ -13,6 +13,7 @@ while true; do
case "$1" in case "$1" in
-h | --help ) HELP=true; shift ;; -h | --help ) HELP=true; shift ;;
-v | --verbose ) VERBOSE=true; shift ;; -v | --verbose ) VERBOSE=true; shift ;;
-D | --dbmakemigrations ) DB_MAKE_MIGRATIONS=true; shift ;;
-d | --dbmigrate ) DB_MIGRATE=true; shift ;; -d | --dbmigrate ) DB_MIGRATE=true; shift ;;
-n | --nogit ) NO_GIT=true; shift ;; -n | --nogit ) NO_GIT=true; shift ;;
-b | --branch ) BRANCH="$2"; shift 2 ;; -b | --branch ) BRANCH="$2"; shift 2 ;;
@ -31,13 +32,15 @@ if [ "$HELP" == "true" ]; then
echo "options are : " echo "options are : "
echo " -h, --help: Print this help message" echo " -h, --help: Print this help message"
echo " -v, --verbose: Show verbose output to stdout. Without this a deploy.log is written to ~/app folder" echo " -v, --verbose: Show verbose output to stdout. Without this a deploy.log is written to ~/app folder"
echo " -d, --dbmigrate: Do DB migrate" echo " -D, --dbmakemigrations: Do DB makemigrations"
echo " -n, --nogit: Don't execute git commands. With this --branch has no effect." echo " -d, --dbmigrate: Do DB migrate. To do both makemigrations and migrate, supply both switches -D and -d"
echo " -n, --nogit: Don't execute git commands. This is used to deploy the current code in the project repo. With this --branch has no effect."
echo " -b, --branch: The branch to pull from origin repo." echo " -b, --branch: The branch to pull from origin repo."
exit exit
fi fi
echo "BRANCH="$BRANCH echo "BRANCH="$BRANCH
echo "DB_MAKE_MIGRATIONS="$DB_MAKE_MIGRATIONS
echo "DB_MIGRATE="$DB_MIGRATE echo "DB_MIGRATE="$DB_MIGRATE
echo "NO_GIT="$NO_GIT echo "NO_GIT="$NO_GIT
echo "VERBOSE="$VERBOSE echo "VERBOSE="$VERBOSE
@ -45,7 +48,7 @@ echo "VERBOSE="$VERBOSE
# The project directory exists, we pull the specified branch # The project directory exists, we pull the specified branch
cd $APP_HOME_DIR cd $APP_HOME_DIR
if [ -z "$NO_GIT" ]; then if [ -z "$NO_GIT" ]; then
echo 'We are executing default git commands. Please -no_git to not use this.' echo 'We are executing default git commands. Please add --nogit to not do this.'
# Save any modified changes before git pulling # Save any modified changes before git pulling
git stash git stash
# Fetch all branches/tags # Fetch all branches/tags
@ -59,16 +62,23 @@ fi
source ~/pyvenv/bin/activate source ~/pyvenv/bin/activate
pip install -r requirements.txt > deploy.log 2>&1 pip install -r requirements.txt > deploy.log 2>&1
echo "###" >> deploy.log echo "###" >> deploy.log
if [ -z "$DB_MIGRATE" ]; then if [ -z "$DB_MAKE_MIGRATIONS" ]; then
echo 'We are not doing DB migration' echo 'We are not doing DB makemigrations'
else else
echo 'Doing DB makemigrations'
./manage.py makemigrations >> deploy.log 2>&1 ./manage.py makemigrations >> deploy.log 2>&1
echo "###" >> deploy.log echo "###" >> deploy.log
fi
if [ -z "$DB_MIGRATE" ]; then
echo 'We are not doing DB migrate'
else
echo 'Doing DB migrate'
./manage.py migrate >> deploy.log 2>&1 ./manage.py migrate >> deploy.log 2>&1
echo "###" >> deploy.log echo "###" >> deploy.log
fi fi
printf 'yes' | ./manage.py collectstatic >> deploy.log 2>&1 printf 'yes' | ./manage.py collectstatic >> deploy.log 2>&1
echo "###" >> deploy.log echo "###" >> deploy.log
django-admin compilemessages django-admin compilemessages
sudo systemctl restart celery.service
sudo systemctl restart uwsgi sudo systemctl restart uwsgi

View file

@ -0,0 +1,5 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ['celery_app']

21
dynamicweb/celery.py Normal file
View file

@ -0,0 +1,21 @@
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dynamicweb.settings')
app = Celery('dynamicweb')
# Using a string here means the worker don't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))

View file

@ -10,6 +10,9 @@ from django.utils.translation import ugettext_lazy as _
# dotenv # dotenv
import dotenv import dotenv
import logging
logger = logging.getLogger(__name__)
def gettext(s): def gettext(s):
@ -25,6 +28,21 @@ def bool_env(val):
return True if os.environ.get(val, False) == 'True' else False return True if os.environ.get(val, False) == 'True' else False
def int_env(val, default_value=0):
"""Replaces string based environment values with Python integers
Return default_value if val is not set or cannot be parsed, otherwise
returns the python integer equal to the passed val
"""
return_value = default_value
try:
return_value = int(os.environ.get(val))
except Exception as e:
logger.error("Encountered exception trying to get env value for {}\nException details: {}".format(
val, str(e)))
return return_value
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.abspath( PROJECT_DIR = os.path.abspath(
@ -120,7 +138,8 @@ INSTALLED_APPS = (
'datacenterlight.templatetags', 'datacenterlight.templatetags',
'alplora', 'alplora',
'rest_framework', 'rest_framework',
'opennebula_api' 'opennebula_api',
'django_celery_results',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
@ -524,6 +543,15 @@ GOOGLE_ANALYTICS_PROPERTY_IDS = {
'dynamicweb-staging.ungleich.ch': 'staging' 'dynamicweb-staging.ungleich.ch': 'staging'
} }
# CELERY Settings
CELERY_BROKER_URL = env('CELERY_BROKER_URL')
CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND')
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Europe/Zurich'
CELERY_MAX_RETRIES = int_env('CELERY_MAX_RETRIES', 5)
ENABLE_DEBUG_LOGGING = bool_env('ENABLE_DEBUG_LOGGING') ENABLE_DEBUG_LOGGING = bool_env('ENABLE_DEBUG_LOGGING')
if ENABLE_DEBUG_LOGGING: if ENABLE_DEBUG_LOGGING:

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-08-11 01:16+0530\n" "POT-Creation-Date: 2017-08-16 04:19+0530\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -435,17 +435,17 @@ msgstr "Sind Sie sicher, dass Sie ihre virtuelle Maschine beenden wollen "
msgid "Virtual Machines" msgid "Virtual Machines"
msgstr "Virtuelle Maschinen" msgstr "Virtuelle Maschinen"
msgid "Create VM" msgid "To create a new virtual machine, click \"Create VM\""
msgstr "Neue VM"
msgid "ID"
msgstr "" msgstr ""
msgid "Ipv4" msgid "CREATE VM"
msgstr "IPv4" msgstr "NEUE VM"
msgid "Ipv6" msgid "Page"
msgstr "IPv6" msgstr ""
msgid "of"
msgstr ""
msgid "login" msgid "login"
msgstr "einloggen" msgstr "einloggen"
@ -482,6 +482,12 @@ msgid ""
"contact Data Center Light Support." "contact Data Center Light Support."
msgstr "" msgstr ""
#~ msgid "Ipv4"
#~ msgstr "IPv4"
#~ msgid "Ipv6"
#~ msgstr "IPv6"
#~ msgid "Close" #~ msgid "Close"
#~ msgstr "Schliessen" #~ msgstr "Schliessen"

View file

@ -1,13 +1,9 @@
import os import os
import logging import logging
from django.db import models from django.db import models
from django.utils.functional import cached_property from django.utils.functional import cached_property
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from membership.models import StripeCustomer, CustomUser from membership.models import StripeCustomer, CustomUser
from utils.models import BillingAddress from utils.models import BillingAddress
from utils.mixins import AssignPermissionsMixin from utils.mixins import AssignPermissionsMixin
@ -42,7 +38,6 @@ class HostingPlan(models.Model):
class HostingOrder(AssignPermissionsMixin, models.Model): class HostingOrder(AssignPermissionsMixin, models.Model):
ORDER_APPROVED_STATUS = 'Approved' ORDER_APPROVED_STATUS = 'Approved'
ORDER_DECLINED_STATUS = 'Declined' ORDER_DECLINED_STATUS = 'Declined'

View file

@ -188,3 +188,49 @@
text-align: left; text-align: left;
vertical-align: middle; vertical-align: middle;
} }
.un-icon {
width: 15px;
height: 15px;
opacity: 0.5;
margin-top: -1px;
}
.css-plus {
position: relative;
width: 16px;
height: 20px;
display: inline-block;
vertical-align: middle;
/* top: -1px; */
}
.css-plus + span {
vertical-align: middle;
}
.css-plus:before {
content: '';
width: 10px;
height: 2px;
background: #f6f7f9;
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
}
.css-plus:after {
content: '';
width: 2px;
height: 10px;
background: #f6f7f9;
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
}

View file

@ -228,3 +228,156 @@
float: left !important; float: left !important;
} }
} }
/* New styles */
.dashboard-container-head {
padding: 0 8px;
}
.dashboard-title-thin {
font-weight: 300;
font-size: 32px;
}
.dashboard-title-thin .un-icon {
height: 34px;
margin-right: 5px;
margin-top: -1px;
width: 20px;
}
.dashboard-subtitle {
font-weight: 300;
margin-bottom: 25px;
}
.btn-vm {
background: #1596DA;
color: #fff;
font-weight: 400;
letter-spacing: 0.8px;
border-radius: 3px;
padding-bottom: 7px;
border: 2px solid #1596DA;
}
.btn-vm:hover, .btn-vm:focus {
color: #1596DA;
background: #fff;
}
.btn-vm:hover .css-plus:after,
.btn-vm:focus .css-plus:after,
.btn-vm:hover .css-plus:before,
.btn-vm:focus .css-plus:before {
background: #1596DA;
}
.btn-vm-detail {
background: #3770CC;
color: #fff;
font-weight: 400;
letter-spacing: 0.6px;
font-size: 14px;
border-radius: 3px;
border: 2px solid #3770CC;
padding: 4px 20px;
/* padding-bottom: 7px; */
}
.btn-vm-detail:hover, .btn-vm-detail:focus {
background: #fff;
color: #3770CC;
}
.vm-status, .vm-status-active, .vm-status-failed {
font-weight: 600;
}
.vm-status-active {
color: #4A90E2;
}
.vm-status-failed {
color: #eb4d5c;
}
@media (min-width:768px) {
.dashboard-subtitle {
display: flex;
justify-content: space-between;
font-size: 16px;
}
}
@media (max-width:767px) {
.dashboard-title-thin {
font-size: 22px;
}
.dashboard-title-thin .un-icon {
height: 20px;
width: 18px;
margin-top: -3px;
}
.dashboard-subtitle p {
width: 200px;
}
}
.table-switch {
color: #555;
}
.table-switch > tbody > tr > td {
padding: 12px 8px;
}
@media (min-width: 768px) {
.table-switch > tbody > tr > td:nth-child(1) {
padding-right: 45px;
}
.table-switch > tbody > tr:last-child > td {
border-bottom: 1px solid #ddd;
}
}
.table-switch .un-icon {
margin-left: 5px;
}
@media (max-width:767px) {
.dashboard-subtitle {
margin-bottom: 15px;
}
.table-switch .un-icon {
float: right;
margin-top: 0;
}
.table-switch thead {
display: none;
}
.table-switch tbody tr {
display: block;
position: relative;
border-top: 1px solid #ddd;
/* margin-top: 15px; */
padding-top: 5px;
padding-bottom: 15px;
}
.table-switch tbody tr:last-child {
border-bottom: 1px solid #ddd;
}
.table-switch tbody tr td {
display: block;
padding-top: 28px;
padding-bottom: 6px;
position: relative;
border: 0;
}
.table-switch td:before {
content: attr(data-header);
font-weight: 600;
position: absolute;
top: 5px;
}
.table-switch .last-td {
position: absolute;
bottom: 20px;
right: 0;
}
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><path d="M691,160.8V10H269.5C206.3,72.6,143.1,135.2,80,197.8v641.4h227.9V990H920V160.8H691z M269.5,64.4v134.4H133.1C178.5,154,224,109.2,269.5,64.4z M307.9,801.2H117.5V236.8h190.5V47.9h344.5v112.9h-154c-63.5,62.9-127,125.9-190.5,188.8V801.2z M499.5,215.2v134.5H363.1v-1c45.1-44.5,90.2-89,135.3-133.5L499.5,215.2z M881.5,952h-535V386.6H538V198.8h343.5V952z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 846 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><path d="M724.6,224.4c0,28.2,22.9,51,51,51s51-22.9,51-51c0-28.2-22.9-51-51-51S724.6,196.2,724.6,224.4z M724.6,551c0,28.2,22.9,51,51,51s51-22.9,51-51c0-28.2-22.9-51-51-51S724.6,522.9,724.6,551L724.6,551z M10,683.7c0,45.1,36.5,81.7,81.7,81.7h347.1v56.4c-10,6.7-18.6,15.3-25.3,25.3l-250.3,0c-28.2,0-51,22.9-51,51s22.9,51,51,51h250.3c16.5,24.7,44.5,40.8,76.4,40.8c31.8,0,59.8-16.1,76.4-40.8h270.7c28.2,0,51-22.9,51-51s-22.9-51-51-51H566.2c-6.7-10-15.3-18.6-25.3-25.3v-56.3h367.5c45.1,0,81.7-36.5,81.7-81.7V91.7c0-45.1-36.5-81.7-81.7-81.7H91.7C46.5,10,10,46.5,10,91.7L10,683.7L10,683.7z M157,112.1h686c24.9,0,44.9,20,44.9,44.9v134.7c0,24.9-20,44.9-44.9,44.9H157c-24.9,0-44.9-20-44.9-44.9V157C112.1,132.1,132.1,112.1,157,112.1L157,112.1L157,112.1z M157,438.7h686c24.9,0,44.9,20,44.9,44.9v134.7c0,24.9-20,44.9-44.9,44.9H157c-24.9,0-44.9-20-44.9-44.9V483.7C112.1,458.8,132.1,438.7,157,438.7L157,438.7L157,438.7z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -77,7 +77,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h4 class="modal-title" id="ModalLabel_Public_Key">{% trans "Public SSH key" %}</h4> <h4 class="modal-title" id="ModalLabel_Public_Key">{% trans "Public SSH Key" %}</h4>
<p class="key_contain" style="margin-top: 10px;">{{ user_key.public_key }}</p> <p class="key_contain" style="margin-top: 10px;">{{ user_key.public_key }}</p>
<div class="modal-footer"> <div class="modal-footer">
</div> </div>

View file

@ -1,14 +1,9 @@
{% extends "hosting/base_short.html" %} {% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 i18n %} {% load staticfiles bootstrap3 i18n %}
{% block content %} {% block content %}
<div>
<div class="dashboard-container"> <div class="dashboard-container">
<div class="row"> <div class="dashboard-container-head">
<div class="col-xs-12 container-table"> <h3 class="dashboard-title-thin"><img src="{% static 'hosting/img/vm.svg' %}" class="un-icon"> {% trans "Virtual Machines" %}</h3>
<table class="table borderless table-hover">
<h3 class="pull-left"><i class="fa fa-server fa-separate" aria-hidden="true"></i> {% trans "Virtual Machines"%}</h3>
<div class="col-md-12">
<br/>
{% if messages %} {% if messages %}
<div class="alert alert-warning"> <div class="alert alert-warning">
{% for message in messages %} {% for message in messages %}
@ -16,18 +11,23 @@
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div>
{% if not error %} {% if not error %}
<p class="pull-right btn-create-vm"> <div class="dashboard-subtitle">
<a class="btn btn-success" href="{% url 'hosting:create_virtual_machine' %}" >{% trans "Create VM"%} </a> <p>{% trans 'To create a new virtual machine, click "Create VM"' %}</p>
</p> <div class="text-right">
<br/> <a class="btn btn-vm" href="{% url 'hosting:create_virtual_machine' %}"><span class="css-plus"></span> <span>{% trans "CREATE VM" %}</span></a>
</div>
</div>
{% endif %}
</div>
{% if not error %}
<table class="table table-switch">
<thead> <thead>
<tr> <tr>
<th>{% trans "ID"%}</th> <th>ID</th>
<th>{% trans "Ipv4"%}</th> <th>IPv4</th>
<th>{% trans "Ipv6"%}</th> <th>IPv6</th>
<th>{% trans "Status" %}</th> <th>{% trans "Status" %}</th>
<th></th> <th></th>
</tr> </tr>
@ -35,33 +35,28 @@
<tbody> <tbody>
{% for vm in vms %} {% for vm in vms %}
<tr> <tr>
<td scope="row">{{vm.vm_id}}</td> <td data-header="ID">{{vm.vm_id}}</td>
{% if vm.ipv6 %} {% if vm.ipv6 %}
<td>{{vm.ipv4}}</td> <td data-header="IPv4">{{vm.ipv4}}</td>
<td data-header="IPv6">{{vm.ipv6}}</td>
<td>{{vm.ipv6}}</td>
{% endif %} {% endif %}
<td data-header="{% trans 'Status' %}">
<td>
{% if vm.state == 'ACTIVE' %} {% if vm.state == 'ACTIVE' %}
<span class="h3 label label-success"><strong> {{vm.state}}</strong></span> <span class="vm-status-active"><strong>{{vm.state|title}}</strong></span>
{% elif vm.state == 'FAILED' %} {% elif vm.state == 'FAILED' %}
<span class="h3 label label-danger"><strong>{{vm.state}}</strong></span> <span class="vm-status-failed"><strong>{{vm.state|title}}</strong></span>
{% else %} {% else %}
<span class="h3 label label-warning"><strong>{{vm.state}}</strong></span> <span class="vm-status"><strong>{{vm.state|title}}</strong></span>
{% endif %} {% endif %}
</td> </td>
<td> <td class="text-right last-td">
<button type="button" class="btn btn-default"><a <a class="btn btn-vm-detail" href="{% url 'hosting:virtual_machines' vm.vm_id %}">{% trans "View Detail" %}</a>
href="{% url 'hosting:virtual_machines' vm.vm_id %}">{% trans "View Detail"%}</a></button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
{% endif %}
</table> </table>
{% endif %}
{% if is_paginated %} {% if is_paginated %}
<div class="pagination"> <div class="pagination">
@ -70,7 +65,7 @@
<a href="{{request.path}}?page={{ page_obj.previous_page_number }}">{% trans "previous" %}</a> <a href="{{request.path}}?page={{ page_obj.previous_page_number }}">{% trans "previous" %}</a>
{% endif %} {% endif %}
<span class="page-current"> <span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. {% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}.
</span> </span>
{% if page_obj.has_next %} {% if page_obj.has_next %}
<a href="{{request.path}}?page={{ page_obj.next_page_number }}">{% trans "next" %}</a> <a href="{{request.path}}?page={{ page_obj.next_page_number }}">{% trans "next" %}</a>
@ -78,12 +73,5 @@
</span> </span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
</div>
{%endblock%} {%endblock%}