Merged master into task/3701/enable_monthly_payments

This commit is contained in:
PCoder 2017-08-24 04:06:48 +05:30
commit a964f2fb5b
17 changed files with 968 additions and 384 deletions

View file

@ -41,13 +41,15 @@ def retry_task(task, exception=None):
@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,
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()
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,
@ -114,7 +116,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, stripe_customer_
'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()]),
'body': "\n".join(
["%s=%s" % (k, v) for (k, v) in context.items()]),
'reply_to': [context['email']],
}
email = EmailMessage(**email_data)
@ -124,11 +127,13 @@ def create_vm_task(self, vm_template_id, user, specs, template, stripe_customer_
try:
retry_task(self)
except MaxRetriesExceededError:
msg_text = 'Finished {} retries for create_vm_task'.format(self.request.retries)
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),
'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)

View file

@ -1,3 +1,120 @@
# from django.test import TestCase
from time import sleep
import stripe
from celery.result import AsyncResult
from django.conf import settings
from django.core.management import call_command
# Create your tests here.
from django.test import TestCase, override_settings
from model_mommy import mommy
from datacenterlight.models import VMTemplate
from datacenterlight.tasks import create_vm_task
from membership.models import StripeCustomer
from opennebula_api.serializers import VMTemplateSerializer
from utils.models import BillingAddress
from utils.stripe_utils import StripeUtils
class CeleryTaskTestCase(TestCase):
@override_settings(
task_eager_propagates=True,
task_always_eager=True,
)
def setUp(self):
self.customer_password = 'test_password'
self.customer_email = 'celery-createvm-task-test@ungleich.ch'
self.customer_name = "Monty Python"
self.user = {
'email': self.customer_email,
'name': self.customer_name
}
self.customer = mommy.make('membership.CustomUser')
self.customer.set_password(self.customer_password)
self.customer.email = self.customer_email
self.customer.save()
self.stripe_utils = StripeUtils()
stripe.api_key = settings.STRIPE_API_PRIVATE_KEY_TEST
self.token = stripe.Token.create(
card={
"number": '4111111111111111',
"exp_month": 12,
"exp_year": 2022,
"cvc": '123'
},
)
# Run fetchvmtemplates so that we have the VM templates from
# OpenNebula
call_command('fetchvmtemplates')
def test_create_vm_task(self):
"""Tests the create vm task."""
# We create a VM from the first template available to DCL
vm_template = VMTemplate.objects.all().first()
template_data = VMTemplateSerializer(vm_template).data
# The specs of VM that we want to create
specs = {
'cpu': 1,
'memory': 2,
'disk_size': 10,
'price': 15,
}
stripe_customer = StripeCustomer.get_or_create(
email=self.customer_email,
token=self.token)
billing_address = BillingAddress(
cardholder_name=self.customer_name,
postal_code='1232',
country='CH',
street_address='Monty\'s Street',
city='Hollywood')
billing_address.save()
billing_address_data = {'cardholder_name': self.customer_name,
'postal_code': '1231',
'country': 'CH',
'token': self.token,
'street_address': 'Monty\'s Street',
'city': 'Hollywood'}
billing_address_id = billing_address.id
vm_template_id = template_data.get('id', 1)
final_price = specs.get('price')
# Make stripe charge to a customer
stripe_utils = StripeUtils()
charge_response = stripe_utils.make_charge(
amount=final_price,
customer=stripe_customer.stripe_id)
# Check if the payment was approved
if not charge_response.get(
'response_object'):
msg = charge_response.get('error')
raise Exception("make_charge failed: {}".format(msg))
charge = charge_response.get('response_object')
async_task = create_vm_task.delay(vm_template_id, self.user,
specs,
template_data,
stripe_customer.id,
billing_address_data,
billing_address_id,
charge)
new_vm_id = 0
res = None
for i in range(0, 10):
sleep(5)
res = AsyncResult(async_task.task_id)
if res.result is not None and res.result > 0:
new_vm_id = res.result
break
# We expect a VM to be created within 50 seconds
self.assertGreater(new_vm_id, 0,
"VM could not be created. res._get_task_meta() = {}"
.format(res._get_task_meta()))

View file

@ -18,7 +18,8 @@ from hosting.models import HostingOrder
from utils.stripe_utils import StripeUtils
from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineTemplateSerializer, VMTemplateSerializer
from opennebula_api.serializers import VirtualMachineTemplateSerializer, \
VMTemplateSerializer
from datacenterlight.tasks import create_vm_task
@ -35,9 +36,11 @@ class SuccessView(TemplateView):
elif 'token' not in request.session:
return HttpResponseRedirect(reverse('datacenterlight:payment'))
elif 'order_confirmation' not in request.session:
return HttpResponseRedirect(reverse('datacenterlight:order_confirmation'))
return HttpResponseRedirect(
reverse('datacenterlight:order_confirmation'))
else:
for session_var in ['specs', 'user', 'template', 'billing_address', 'billing_address_data',
for session_var in ['specs', 'user', 'template', 'billing_address',
'billing_address_data',
'token', 'customer']:
if session_var in request.session:
del request.session[session_var]
@ -53,7 +56,8 @@ class PricingView(TemplateView):
templates = manager.get_templates()
context = {
'templates': VirtualMachineTemplateSerializer(templates, many=True).data,
'templates': VirtualMachineTemplateSerializer(templates,
many=True).data,
}
except:
messages.error(request,
@ -77,7 +81,8 @@ class PricingView(TemplateView):
manager = OpenNebulaManager()
template = manager.get_template(template_id)
request.session['template'] = VirtualMachineTemplateSerializer(template).data
request.session['template'] = VirtualMachineTemplateSerializer(
template).data
if not request.user.is_authenticated():
request.session['next'] = reverse('hosting:payment')
@ -99,7 +104,8 @@ class BetaAccessView(FormView):
def form_valid(self, form):
context = {
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host())
'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host())
}
email_data = {
@ -129,7 +135,8 @@ class BetaAccessView(FormView):
email = BaseEmail(**email_data)
email.send()
messages.add_message(self.request, messages.SUCCESS, self.success_message)
messages.add_message(self.request, messages.SUCCESS,
self.success_message)
return render(self.request, 'datacenterlight/beta_success.html', {})
@ -154,7 +161,8 @@ class BetaProgramView(CreateView):
# data = VirtualMachineTemplateSerializer(templates, many=True).data
context.update({
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()),
'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host()),
'vms': vms
})
return context
@ -164,7 +172,8 @@ class BetaProgramView(CreateView):
vms = BetaAccessVM.create(data)
context = {
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()),
'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host()),
'email': data.get('email'),
'name': data.get('name'),
'vms': vms
@ -181,7 +190,8 @@ class BetaProgramView(CreateView):
email = BaseEmail(**email_data)
email.send()
messages.add_message(self.request, messages.SUCCESS, self.success_message)
messages.add_message(self.request, messages.SUCCESS,
self.success_message)
return HttpResponseRedirect(self.get_success_url())
@ -225,7 +235,8 @@ class IndexView(CreateView):
storage_field = forms.IntegerField(validators=[self.validate_storage])
price = request.POST.get('total')
template_id = int(request.POST.get('config'))
template = VMTemplate.objects.filter(opennebula_vm_template_id=template_id).first()
template = VMTemplate.objects.filter(
opennebula_vm_template_id=template_id).first()
template_data = VMTemplateSerializer(template).data
name = request.POST.get('name')
@ -237,36 +248,46 @@ class IndexView(CreateView):
cores = cores_field.clean(cores)
except ValidationError as err:
msg = '{} : {}.'.format(cores, str(err))
messages.add_message(self.request, messages.ERROR, msg, extra_tags='cores')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='cores')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
try:
memory = memory_field.clean(memory)
except ValidationError as err:
msg = '{} : {}.'.format(memory, str(err))
messages.add_message(self.request, messages.ERROR, msg, extra_tags='memory')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='memory')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
try:
storage = storage_field.clean(storage)
except ValidationError as err:
msg = '{} : {}.'.format(storage, str(err))
messages.add_message(self.request, messages.ERROR, msg, extra_tags='storage')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='storage')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
try:
name = name_field.clean(name)
except ValidationError as err:
msg = '{} {}.'.format(name, _('is not a proper name'))
messages.add_message(self.request, messages.ERROR, msg, extra_tags='name')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='name')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
try:
email = email_field.clean(email)
except ValidationError as err:
msg = '{} {}.'.format(email, _('is not a proper email'))
messages.add_message(self.request, messages.ERROR, msg, extra_tags='email')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='email')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
specs = {
'cpu': cores,
@ -293,14 +314,16 @@ class IndexView(CreateView):
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context.update({
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host())
'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host())
})
return context
def form_valid(self, form):
context = {
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host())
'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host())
}
email_data = {
@ -330,7 +353,8 @@ class IndexView(CreateView):
email = BaseEmail(**email_data)
email.send()
messages.add_message(self.request, messages.SUCCESS, self.success_message)
messages.add_message(self.request, messages.SUCCESS,
self.success_message)
return super(IndexView, self).form_valid(form)
@ -403,7 +427,8 @@ class PaymentOrderView(FormView):
request.session['billing_address'] = billing_address.id
request.session['token'] = token
request.session['customer'] = customer.id
return HttpResponseRedirect(reverse('datacenterlight:order_confirmation'))
return HttpResponseRedirect(
reverse('datacenterlight:order_confirmation'))
else:
return self.form_invalid(form)
@ -423,11 +448,15 @@ class OrderConfirmationView(DetailView):
stripe_customer_id = request.session.get('customer')
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details(customer.stripe_id, request.session.get('token'))
if not card_details.get('response_object') and not card_details.get('paid'):
card_details = stripe_utils.get_card_details(customer.stripe_id,
request.session.get(
'token'))
if not card_details.get('response_object'):
msg = card_details.get('error')
messages.add_message(self.request, messages.ERROR, msg, extra_tags='failed_payment')
return HttpResponseRedirect(reverse('datacenterlight:payment') + '#payment_error')
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment')
return HttpResponseRedirect(
reverse('datacenterlight:payment') + '#payment_error')
context = {
'site_url': reverse('datacenterlight:index'),
'cc_last4': card_details.get('response_object').get('last4'),

View file

@ -37,8 +37,10 @@ def int_env(val, default_value=0):
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)))
logger.error(
("Encountered exception trying to get env value for {}\nException "
"details: {}").format(
val, str(e)))
return return_value
@ -169,10 +171,12 @@ TEMPLATES = [
os.path.join(PROJECT_DIR, 'membership'),
os.path.join(PROJECT_DIR, 'hosting/templates/'),
os.path.join(PROJECT_DIR, 'nosystemd/templates/'),
os.path.join(PROJECT_DIR, 'ungleich/templates/djangocms_blog/'),
os.path.join(PROJECT_DIR,
'ungleich/templates/djangocms_blog/'),
os.path.join(PROJECT_DIR, 'ungleich/templates/cms/ungleichch'),
os.path.join(PROJECT_DIR, 'ungleich/templates/ungleich'),
os.path.join(PROJECT_DIR, 'ungleich_page/templates/ungleich_page'),
os.path.join(PROJECT_DIR,
'ungleich_page/templates/ungleich_page'),
os.path.join(PROJECT_DIR, 'templates/analytics'),
],
'APP_DIRS': True,
@ -493,6 +497,7 @@ REGISTRATION_MESSAGE = {'subject': "Validation mail",
}
STRIPE_API_PRIVATE_KEY = env('STRIPE_API_PRIVATE_KEY')
STRIPE_API_PUBLIC_KEY = env('STRIPE_API_PUBLIC_KEY')
STRIPE_API_PRIVATE_KEY_TEST = env('STRIPE_API_PRIVATE_KEY_TEST')
ANONYMOUS_USER_NAME = 'anonymous@ungleich.ch'
GUARDIAN_GET_INIT_ANONYMOUS_USER = 'membership.models.get_anonymous_user_instance'
@ -535,9 +540,12 @@ GOOGLE_ANALYTICS_PROPERTY_IDS = {
'ungleich.ch': 'UA-62285904-1',
'digitalglarus.ch': 'UA-62285904-2',
'blog.ungleich.ch': 'UA-62285904-4',
'hosting': 'UA-62285904-5',
'datacenterlight.ch': 'UA-62285904-9',
'rails-hosting.ch': 'UA-62285904-5',
'django-hosting.ch': 'UA-62285904-6',
'node-hosting.ch': 'UA-62285904-7',
'datacenterlight.ch': 'UA-62285904-8',
'devuanhosting.ch': 'UA-62285904-9',
'ipv6onlyhosting.ch': 'UA-62285904-10',
'127.0.0.1:8000': 'localhost',
'dynamicweb-development.ungleich.ch': 'development',
'dynamicweb-staging.ungleich.ch': 'staging'
@ -562,7 +570,8 @@ if ENABLE_DEBUG_LOGGING:
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': "{PROJECT_DIR}/debug.log".format(PROJECT_DIR=PROJECT_DIR),
'filename': "{PROJECT_DIR}/debug.log".format(
PROJECT_DIR=PROJECT_DIR),
},
},
'loggers': {

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-08-16 04:19+0530\n"
"POT-Creation-Date: 2017-08-20 21:37+0530\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"
@ -245,31 +245,23 @@ msgstr "Gesamt"
msgid "Finish Configuration"
msgstr "Konfiguration beenden"
msgid "Order Nr."
msgstr "Bestellung Nr."
msgid "Amount"
msgstr "Betrag"
msgid "Status"
msgstr ""
msgid "Approved"
msgstr "Akzeptiert"
msgid "Declined"
msgstr "Abgelehnt"
msgid "View Detail"
msgstr "Details anzeigen"
msgid "Cancel Order"
msgstr "Bestellung stornieren"
msgid "Page"
msgstr ""
#, fuzzy
#| msgid "Do You want to delete your order?"
msgid "Do you want to delete your order?"
msgstr "Willst du deine Bestellung löschen?"
msgid "Delete"
msgstr "Löschen"
msgid "of"
msgstr ""
msgid "Your Order"
msgstr "Deine Bestellung"
@ -300,13 +292,21 @@ msgstr ""
"\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und "
"speichern keine Informationen in unserer Datenbank."
#, fuzzy
#| msgid ""
#| "\n"
#| " You are not making any "
#| "payment yet. After submitting your card\n"
#| " information, you will be "
#| "taken to the Confirm Order Page.\n"
#| " "
msgid ""
"\n"
" You are not making any payment yet. "
"After submitting your card\n"
" information, you will be taken to "
"the Confirm Order Page.\n"
" "
" You are not making any "
"payment yet. After submitting your card\n"
" information, you will be "
"taken to the Confirm Order Page.\n"
" "
msgstr ""
"\n"
"Es wird noch keine Bezahlung vorgenommen. Nach der Eingabe Deiner "
@ -328,19 +328,6 @@ msgstr ""
msgid "Card Type"
msgstr "Kartentyp"
msgid ""
"\n"
" You are not making any payment "
"yet. After submitting your card\n"
" information, you will be taken "
"to the Confirm Order Page.\n"
" "
msgstr ""
"\n"
"Es wird noch keine Bezahlung vorgenommen. Nach der Eingabe Deiner "
"Kreditkateninformationen wirst du auf die Bestellbestätigungsseite "
"weitergeleitet."
msgid "Processing"
msgstr "Weiter"
@ -390,6 +377,9 @@ msgstr ""
msgid "Private Key"
msgstr ""
msgid "Delete"
msgstr "Löschen"
msgid "Delete SSH Key"
msgstr "SSH Key löschen"
@ -399,38 +389,76 @@ msgstr "Möchtest Du den Schlüssel löschen?"
msgid "Show"
msgstr "Anzeigen"
msgid "Public SSH Key"
#, fuzzy
#| msgid "Public SSH Key"
msgid "Public SSH key"
msgstr "Public SSH Key"
msgid "Download"
msgstr ""
msgid "Settings"
msgstr "Einstellungen"
msgid "Your Virtual Machine Detail"
msgstr "Virtuelle Maschinen Detail"
msgid "Billing"
msgstr "Abrechnungen"
msgid "VM Settings"
msgstr "VM Einstellungen"
msgid "Ip not assigned yet"
msgstr "Ip nicht zugewiesen"
msgid "Copied"
msgstr "Kopiert"
msgid "Disk"
msgstr "Festplatte"
msgid "Current pricing"
msgid "Billing"
msgstr "Abrechnungen"
msgid "Current Pricing"
msgstr "Aktueller Preis"
msgid "Current status"
msgstr "Aktueller Status"
msgid "Month"
msgstr "Monat"
msgid "Terminate Virtual Machine"
msgstr "Virtuelle Maschine beenden"
msgid "See Invoice"
msgstr "Rechnung"
msgid "Your VM is"
msgstr "Deine VM ist"
msgid "Pending"
msgstr "In Vorbereitung"
msgid "Online"
msgstr ""
msgid "Failed"
msgstr "Fehlgeschlagen"
msgid "Terminate VM"
msgstr "VM Beenden"
msgid "Support / Contact"
msgstr "Support / Kontakt"
msgid "Something doesn't work?"
msgstr "Etwas funktioniert nicht?"
msgid "We are here to help you!"
msgstr "Wir sind hier, um Dir zu helfen!"
msgid "CONTACT"
msgstr "KONTACT"
msgid "BACK TO LIST"
msgstr "ZURÜCK ZUR LISTE"
msgid "Terminate your Virtual Machine"
msgstr "Ihre virtuelle Maschine beenden"
msgstr "Deine Virtuelle Maschine beenden"
msgid "Are you sure do you want to cancel your Virtual Machine "
msgstr "Sind Sie sicher, dass Sie ihre virtuelle Maschine beenden wollen "
msgid "Do you want to cancel your Virtual Machine"
msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
msgid "OK"
msgstr ""
msgid "Virtual Machines"
msgstr "Virtuelle Maschinen"
@ -441,14 +469,8 @@ msgstr ""
msgid "CREATE VM"
msgstr "NEUE VM"
msgid "Page"
msgstr ""
msgid "of"
msgstr ""
msgid "login"
msgstr "einloggen"
msgstr "Einloggen"
msgid ""
"Thank you for signing up. We have sent an email to you. Please follow the "
@ -474,6 +496,9 @@ msgstr "Du kannst dich nun"
msgid "Sorry. Your request is invalid."
msgstr "Entschuldigung, deine Anfrage ist ungültig."
msgid "Invalid credit card"
msgstr "Ungültige Kreditkarte"
msgid "Confirm Order"
msgstr "Bestellung Bestätigen"
@ -482,6 +507,42 @@ msgid ""
"contact Data Center Light Support."
msgstr ""
#~ msgid "Approved"
#~ msgstr "Akzeptiert"
#~ msgid "Declined"
#~ msgstr "Abgelehnt"
#~ msgid "Cancel Order"
#~ msgstr "Bestellung stornieren"
#, fuzzy
#~| msgid "Do You want to delete your order?"
#~ msgid "Do you want to delete your order?"
#~ msgstr "Willst du deine Bestellung löschen?"
#~ msgid ""
#~ "\n"
#~ " You are not making any payment "
#~ "yet. After submitting your card\n"
#~ " information, you will be taken to "
#~ "the Confirm Order Page.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ "Es wird noch keine Bezahlung vorgenommen. Nach der Eingabe Deiner "
#~ "Kreditkateninformationen wirst du auf die Bestellbestätigungsseite "
#~ "weitergeleitet."
#~ msgid "Ip not assigned yet"
#~ msgstr "Ip nicht zugewiesen"
#~ msgid "Current status"
#~ msgstr "Aktueller Status"
#~ msgid "Terminate Virtual Machine"
#~ msgstr "Virtuelle Maschine beenden"
#~ msgid "Ipv4"
#~ msgstr "IPv4"

View file

@ -1,3 +1,6 @@
.virtual-machine-container {
max-width: 900px;
}
.virtual-machine-container .tabs-left, .virtual-machine-container .tabs-right {
border-bottom: none;
padding-top: 2px;
@ -229,6 +232,204 @@
}
}
/* Vm Details */
.vm-detail-item, .vm-contact-us {
overflow: hidden;
border: 1px solid #ccc;
padding: 15px;
color: #555;
font-weight: 300;
margin-bottom: 15px;
}
.vm-detail-title {
margin-top: 0;
font-size: 20px;
font-weight: 300;
}
.vm-detail-title .un-icon {
float: right;
height: 24px;
width: 21px;
margin-top: 0;
}
.vm-detail-item .vm-name {
font-size: 16px;
margin-bottom: 15px;
}
.vm-detail-item p {
margin-bottom: 5px;
position: relative;
}
.vm-detail-ip {
padding-bottom: 5px;
border-bottom: 1px solid #ddd;
margin-bottom: 10px;
}
.vm-detail-ip .un-icon {
height: 14px;
width: 14px;
}
.vm-detail-ip .to_copy {
position: absolute;
right: 0;
top: 1px;
padding: 0;
line-height: 1;
}
.vm-vmid {
padding: 50px 0 70px;
text-align: center;
}
.vm-item-lg {
font-size: 22px;
margin-top: 5px;
margin-bottom: 15px;
letter-spacing: 0.6px;
}
.vm-color-online {
color: #37B07B;
}
.vm-color-pending {
color: #e47f2f;
}
.vm-detail-item .value{
font-weight: 400;
}
.vm-detail-config .value {
float: right;
font-weight: 600;
}
.vm-detail-contain {
margin-top: 25px;
}
.vm-contact-us {
margin: 25px 0 30px;
/* text-align: center; */
}
@media(min-width: 768px) {
.vm-detail-contain {
display: flex;
margin-left: -15px;
margin-right: -15px;
}
.vm-detail-item {
width: 33.333333%;
margin: 0 15px;
}
.vm-contact-us {
display: flex;
align-items: center;
justify-content: space-between;
}
.vm-contact-us .vm-detail-title {
margin-bottom: 0;
}
.vm-contact-us .un-icon {
width: 22px;
height: 22px;
margin-right: 5px;
}
.vm-contact-us div {
padding: 0 15px;
position: relative;
}
.vm-contact-us-text {
display: flex;
align-items: center;
}
}
.value-sm-block {
display: block;
padding-top: 2px;
}
@media(max-width: 767px) {
.vm-contact-us div {
margin-bottom: 30px;
}
.vm-contact-us div span {
display: block;
margin-bottom: 3px;
}
.dashboard-title-thin {
font-size: 22px;
}
}
.btn-vm-invoice {
color: #87B6EA;
border: 2px solid #87B6EA;
padding: 4px 18px;
letter-spacing: 0.6px;
}
.btn-vm-invoice:hover, .btn-vm-invoice:focus {
color : #fff;
background: #87B6EA;
}
.btn-vm-term {
color: #aaa;
border: 2px solid #ccc;
background: #fff;
padding: 4px 18px;
letter-spacing: 0.6px;
}
.btn-vm-term:hover, .btn-vm-term:focus, .btn-vm-term:active {
color: #eb4d5c;
border-color: #eb4d5c;
}
.btn-vm-contact {
color: #fff;
background: #A3C0E2;
border: 2px solid #A3C0E2;
padding: 5px 25px;
font-size: 12px;
letter-spacing: 1.3px;
}
.btn-vm-contact:hover, .btn-vm-contact:focus {
background: #fff;
color: #a3c0e2;
}
.btn-vm-back {
color: #fff;
background: #C4CEDA;
border: 2px solid #C4CEDA;
padding: 5px 25px;
font-size: 12px;
letter-spacing: 1.3px;
}
.btn-vm-back:hover, .btn-vm-back:focus {
color: #fff;
background: #8da4c0;
border-color: #8da4c0;
}
.vm-contact-us-text {
letter-spacing: 0.4px;
}
/* New styles */
.dashboard-container-head {
padding: 0 8px;
@ -239,10 +440,10 @@
}
.dashboard-title-thin .un-icon {
height: 34px;
height: 30px;
margin-right: 5px;
margin-top: -1px;
width: 20px;
width: 30px;
}
.dashboard-subtitle {
@ -287,6 +488,24 @@
color: #3770CC;
}
.btn-order-detail {
background: #87B6EA;
color: #fff;
font-weight: 400;
letter-spacing: 0.6px;
font-size: 14px;
border-radius: 3px;
border: 2px solid #87B6EA;
padding: 4px 20px;
min-width: 155px;
/* padding-bottom: 7px; */
}
.btn-order-detail:hover, .btn-order-detail:focus, .btn-order-detail:active {
background: #fff;
color: #87B6EA;
}
.vm-status, .vm-status-active, .vm-status-failed {
font-weight: 600;
}
@ -355,8 +574,8 @@
position: relative;
border-top: 1px solid #ddd;
/* margin-top: 15px; */
padding-top: 5px;
padding-bottom: 15px;
padding-top: 10px;
padding-bottom: 13px;
}
.table-switch tbody tr:last-child {
border-bottom: 1px solid #ddd;
@ -373,11 +592,28 @@
font-weight: 600;
position: absolute;
top: 5px;
left: 8px;
}
.table-switch .last-td {
position: absolute;
bottom: 20px;
bottom: 13px;
right: 0;
}
.table-switch tbody tr .xs-td-inline {
text-align: right;
padding-top: 6px;
}
.table-switch tbody tr .xs-td-bighalf {
width: 52%;
display: inline-block;
}
.table-switch tbody tr .xs-td-smallhalf {
width: 47%;
text-align: right;
display: inline-block;
}
.table-switch tbody tr .xs-td-smallhalf:before {
left: auto;
right: 8px;
}
}

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 279.525 279.525" style="enable-background:new 0 0 279.525 279.525;" xml:space="preserve">
<g>
<path d="M165.066,1.544c-29.272,0-56.007,11.05-76.268,29.191c4.494,7.146,7.047,15.46,7.287,24.042l0.001,0.025l0.001,0.025
c0.102,3.867,0.333,7.735,0.664,11.597c15.368-21.117,40.258-34.88,68.315-34.88c46.571,0,84.459,37.888,84.459,84.459
c0,46.08-37.098,83.634-82.994,84.422c4.191,3.502,8.518,6.84,12.976,9.974l0.02,0.015l0.021,0.014
c6.07,4.282,11.014,9.896,14.483,16.317c49.133-12.861,85.493-57.633,85.493-110.742C279.525,52.89,228.18,1.544,165.066,1.544z"/>
<path d="M162.256,234.942c-13.076-10.438-21.234-17.389-32.909-28.204c-3.435-3.182-7.633-5.164-11.944-5.164
c-3.299,0-6.557,1.051-9.239,3.252c-2.768,2.33-5.536,4.66-8.305,6.989c-22.499-26.738-39.206-57.895-49.027-91.431
c3.472-1.016,6.945-2.033,10.417-3.049c7.652-2.343,11.252-10.512,10.129-18.701c-2.443-17.824-3.77-26.679-5.282-43.018
c-0.775-8.375-6.349-15.65-14.338-16.085c-1.246-0.121-2.491-0.181-3.726-0.181c-29.71,0-55.578,34.436-46.009,76.564
c11.907,52.172,37.684,100.243,74.551,139.031c15.102,15.856,33.603,23.036,50.312,23.036c17.627,0,33.261-7.984,40.833-22.195
C171.778,248.891,168.83,240.19,162.256,234.942z"/>
<path d="M130.645,118.121c-7.912,7.341-13.089,13.113-15.823,17.643c-1.93,3.195-3.338,6.573-4.187,10.04
c-0.399,1.632-0.032,3.326,1.007,4.649c1.038,1.321,2.596,2.079,4.276,2.079h37.758c4.626,0,8.39-3.764,8.39-8.39
c0-4.626-3.764-8.39-8.39-8.39h-17.051c0.139-0.164,0.282-0.328,0.428-0.493c1.114-1.254,3.842-3.874,8.107-7.785
c4.473-4.105,7.493-7.179,9.232-9.398c2.621-3.336,4.571-6.593,5.794-9.679c1.247-3.145,1.88-6.498,1.88-9.967
c0-6.224-2.254-11.507-6.699-15.705c-4.416-4.164-10.495-6.274-18.071-6.274c-6.884,0-12.731,1.802-17.377,5.356
c-2.803,2.146-4.961,5.119-6.415,8.839c-0.982,2.513-0.728,5.388,0.68,7.689c1.408,2.302,3.852,3.837,6.537,4.105
c0.299,0.03,0.597,0.045,0.891,0.045c3.779,0,7.149-2.403,8.387-5.979c0.388-1.121,0.901-2.012,1.527-2.65
c1.318-1.343,3.093-1.997,5.428-1.997c2.373,0,4.146,0.618,5.418,1.889c1.269,1.269,1.886,3.12,1.886,5.66
c0,2.359-0.843,4.819-2.505,7.314C140.862,108.028,138.199,111.083,130.645,118.121z"/>
<path d="M206.235,76.451h-6.307c-1.797,0-3.475,0.886-4.489,2.37l-29.168,42.698c-0.851,1.246-1.301,2.703-1.301,4.212v6.919
c0,2.997,2.439,5.436,5.436,5.436h23.945v5.787c0,4.775,3.885,8.66,8.66,8.66c4.775,0,8.66-3.885,8.66-8.66v-5.787h0.865
c4.437,0,8.047-3.61,8.047-8.047c0-4.437-3.61-8.047-8.047-8.047h-0.865V81.887C211.671,78.89,209.232,76.451,206.235,76.451z
M194.352,121.992h-10.748l10.748-15.978V121.992z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="symbol symbol-billing" aria-labelledby="title" role="img"><title id="title">billing icon</title><g data-name="Layer 1"><path class="cls-1" d="M.37.023v15.954l2.775-1.387 2.775 1.387L8 14.59l2.775 1.387 2.081-1.387 2.775 1.387V.023zm13.873 13.709l-1.487-.744-2.081 1.387L7.9 12.989l-2.08 1.387-2.675-1.337-1.387.694V1.41h12.485z" role="presentation"/><path class="cls-1" d="M4.206 3.617h7.741v1.348H4.206zm0 2.697h7.741v1.349H4.206zm0 2.697h7.741v1.349H4.206z" role="presentation"/></g></svg>

After

Width:  |  Height:  |  Size: 558 B

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 278.898 278.898" style="enable-background:new 0 0 278.898 278.898;" xml:space="preserve">
<g>
<path d="M269.898,175.773h-20.373V64.751c0-4.971-4.029-9-9-9h-62.702V35.377c0-4.971-4.029-9-9-9h-58.748c-4.971,0-9,4.029-9,9
v20.374H38.373c-4.971,0-9,4.029-9,9v111.022H9c-4.971,0-9,4.029-9,9v58.748c0,4.971,4.029,9,9,9h58.747c4.971,0,9-4.029,9-9
v-58.748c0-4.971-4.029-9-9-9H47.373V73.751h53.702v20.374c0,4.971,4.029,9,9,9h20.374v72.648h-20.374c-4.971,0-9,4.029-9,9v58.748
c0,4.971,4.029,9,9,9h58.748c4.971,0,9-4.029,9-9v-58.748c0-4.971-4.029-9-9-9h-20.374v-72.648h20.374c4.971,0,9-4.029,9-9V73.751
h53.702v102.022h-20.374c-4.971,0-9,4.029-9,9v58.748c0,4.971,4.029,9,9,9h58.747c4.971,0,9-4.029,9-9v-58.748
C278.898,179.803,274.869,175.773,269.898,175.773z M58.747,234.521H18v-40.748h40.747V234.521z M159.823,234.521h-40.748v-40.748
h40.748V234.521z M159.823,85.125h-40.748V44.377h40.748V85.125z M260.898,234.521h-40.747v-40.748h40.747V234.521z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="340.274px" height="340.274px" viewBox="0 0 340.274 340.274" style="enable-background:new 0 0 340.274 340.274;"
xml:space="preserve">
<g>
<g>
<g>
<path d="M293.629,127.806l-5.795-13.739c19.846-44.856,18.53-46.189,14.676-50.08l-25.353-24.77l-2.516-2.12h-2.937
c-1.549,0-6.173,0-44.712,17.48l-14.184-5.719c-18.332-45.444-20.212-45.444-25.58-45.444h-35.765
c-5.362,0-7.446-0.006-24.448,45.606l-14.123,5.734C86.848,43.757,71.574,38.19,67.452,38.19l-3.381,0.105L36.801,65.032
c-4.138,3.891-5.582,5.263,15.402,49.425l-5.774,13.691C0,146.097,0,147.838,0,153.33v35.068c0,5.501,0,7.44,46.585,24.127
l5.773,13.667c-19.843,44.832-18.51,46.178-14.655,50.032l25.353,24.8l2.522,2.168h2.951c1.525,0,6.092,0,44.685-17.516
l14.159,5.758c18.335,45.438,20.218,45.427,25.598,45.427h35.771c5.47,0,7.41,0,24.463-45.589l14.195-5.74
c26.014,11,41.253,16.585,45.349,16.585l3.404-0.096l27.479-26.901c3.909-3.945,5.278-5.309-15.589-49.288l5.734-13.702
c46.496-17.967,46.496-19.853,46.496-25.221v-35.029C340.268,146.361,340.268,144.434,293.629,127.806z M170.128,228.474
c-32.798,0-59.504-26.187-59.504-58.364c0-32.153,26.707-58.315,59.504-58.315c32.78,0,59.43,26.168,59.43,58.315
C229.552,202.287,202.902,228.474,170.128,228.474z"/>
</g>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<g>
<polygon points="447.992,336 181.555,336 69.539,80 0.008,80 0.008,48 90.477,48 202.492,304 447.992,304 "/>
</g>
<path d="M287.992,416c0,26.5-21.5,48-48,48s-48-21.5-48-48s21.5-48,48-48S287.992,389.5,287.992,416z"/>
<path d="M447.992,416c0,26.5-21.5,48-48,48s-48-21.5-48-48s21.5-48,48-48S447.992,389.5,447.992,416z"/>
<g>
<polygon points="499.18,144 511.992,112 160.008,112 172.805,144 "/>
<polygon points="211.195,240 223.992,272 447.992,272 460.805,240 "/>
<polygon points="486.398,176 185.602,176 198.398,208 473.586,208 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -13,4 +13,62 @@ $( document ).ready(function() {
}, 1000);
});
$('.alt-text').on('mouseenter mouseleave', function(e){
var $this = $(this);
var txt = $this.text();
var alt = $this.attr('data-alt');
$this.text(alt);
$this.attr('data-alt', txt);
});
});
function getScrollbarWidth() {
var outer = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.width = "100px";
outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
document.body.appendChild(outer);
var widthNoScroll = outer.offsetWidth;
// force scrollbars
outer.style.overflow = "scroll";
// add innerdiv
var inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
var widthWithScroll = inner.offsetWidth;
// remove divs
outer.parentNode.removeChild(outer);
return widthNoScroll - widthWithScroll;
}
// globally stores the width of scrollbar
var scrollbarWidth = getScrollbarWidth();
var paddingAdjusted = false;
$( document ).ready(function() {
// add proper padding to fixed topnav on modal show
$('body').on('click', '[data-toggle=modal]', function(){
var $body = $('body');
if ($body[0].scrollHeight > $body.height()) {
scrollbarWidth = getScrollbarWidth();
var topnavPadding = parseInt($('.navbar-fixed-top.topnav').css('padding-right'));
$('.navbar-fixed-top.topnav').css('padding-right', topnavPadding+scrollbarWidth);
paddingAdjusted = true;
}
});
// remove added padding on modal hide
$('body').on('hidden.bs.modal', function(){
if (paddingAdjusted) {
var topnavPadding = parseInt($('.navbar-fixed-top.topnav').css('padding-right'));
$('.navbar-fixed-top.topnav').css('padding-right', topnavPadding-scrollbarWidth);
}
});
});

View file

@ -38,7 +38,6 @@
<![endif]-->
{% with 'hosting/img/'|add:hosting|add:'-intro-bg.png' as image_static %}
alt="">
<style media="screen" type="text/css">
.intro-header {
background: url("{% static image_static %}") no-repeat center center;

View file

@ -3,95 +3,64 @@
{% load i18n %}
{% block content %}
<div>
<div class="orders-container">
<div class="row">
<div class="container-table col-md-8 col-md-offset-2">
<table class="table borderless table-hover">
<h3><i class="fa fa-credit-card fa-separate"></i>{% trans "My 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 orders %}
<tr>
<td scope="row">{{ order.id }}</td>
<td>{{ order.created_at | date:"M d, Y" }}</td>
<td>{{ order.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>
<a class="btn btn-default"
href="{% url 'hosting:orders' order.id %}">{% trans "View Detail"%}</a>
<button type="button" class="btn btn-default" data-toggle="modal"
data-target="#Modal{{ order.id }}"><a
href="#">{% trans "Cancel Order"%}</a>
</button>
</td>
</tr>
<div class="modal fade" id="Modal{{ order.id }}" tabindex="-1" role="dialog"
aria-labelledby="exampleModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Confirm"><span
aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="modal-icon"><i class="fa fa-trash" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% trans "Do you want to delete your order?"%}</h4>
<form method="post"
action="{% url 'hosting:delete_order' order.id %}">
{% csrf_token %}
<div class="modal-footer">
<button type="submit" class="btn btn-danger">{% trans "Delete"%}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% 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 class="dashboard-container">
<div class="dashboard-container-head">
<h3 class="dashboard-title-thin"><img src="{% static 'hosting/img/shopping-cart.svg' %}" class="un-icon" style="margin-top: -4px; width: 30px;"> {% trans "My Orders" %}</h3>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
</div>
{% endif %}
<div class="dashboard-subtitle"></div>
</div>
<table class="table table-switch">
<thead>
<tr>
<th>{% trans "Order Nr." %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Amount" %}</th>
<th>{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
<td class="xs-td-bighalf" data-header="{% trans 'Date' %}">{{ order.created_at | date:"M d, Y" }}</td>
<td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price }}</td>
<td data-header="{% trans 'Status' %}">
{% if order.approved %}
<span class="vm-status-active"><strong>Approved</strong></span>
{% else %}
<span class="vm-status-failed"><strong>Declined</strong></span>
{% endif %}
</td>
<td class="text-right last-td">
<a class="btn btn-order-detail alt-text" href="{% url 'hosting:orders' order.pk %}" data-alt="{% trans 'See Invoice' %}">{% trans "View Detail" %}</a>
</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">
{% trans "Page" %} {{ page_obj.number }} {% trans "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>
{% endblock %}

View file

@ -3,193 +3,110 @@
{% load i18n %}
{% block content %}
<div>
<div class="virtual-machine-container dashboard-container ">
<div class="row">
<div class="col-md-9 col-md-offset-2">
<div class="col-sm-12">
<h3><i class="fa fa-cloud fa-separate" aria-hidden="true"></i> {{virtual_machine.name}}</h3>
<hr/>
<div class="col-md-3"> <!-- required for floating -->
<!-- Nav tabs -->
<ul class="nav nav-tabs tabs-left sideways">
<li class="active">
<a href="#settings-v" data-toggle="tab">
<i class="fa fa-cogs" aria-hidden="true"></i>
{% trans "Settings"%}
</a>
</li>
<li>
<a href="#billing-v" data-toggle="tab">
<i class="fa fa-money" aria-hidden="true"></i>
{% trans "Billing"%}
</a>
</li>
<li>
<a href="#status-v" data-toggle="tab">
<i class="fa fa-signal" aria-hidden="true"></i> {% trans "Status"%}
</a>
</li>
</ul>
</div>
<div class="col-md-9">
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane active" id="settings-v">
<div class="row">
<div class="col-md-12 inline-headers">
<h3>{{virtual_machine.hosting_company_name}}</h3>
{% if virtual_machine.ipv6 %}
<div class="pull-right right-place">
<button type="link"
data-clipboard-text="{{virtual_machine.ipv4}}" id="copy_vm_id" class="to_copy btn btn-link"
data-toggle="tooltip" data-placement="bottom" title="Copied" data-trigger="click">
Ipv4: {{virtual_machine.ipv4}} <i class="fa fa-files-o" aria-hidden="true"></i>
</button>
<button type="link"
data-clipboard-text="{{virtual_machine.ipv6}}" id="copy_vm_id" class="to_copy btn btn-link"
data-toggle="tooltip" data-placement="bottom" title="Copied" data-trigger="click">
Ipv6: {{virtual_machine.ipv6}} <i class="fa fa-files-o" aria-hidden="true"></i>
</button>
</div>
{% else %}
<div class="pull-right right-place">
<span class="label label-warning"><strong>{% trans "Ip not assigned yet"%}</strong></span>
<i data-toggle="tooltip" title="Your ip will be assigned soon" class="fa fa-info-circle" aria-hidden="true"></i>
</div>
{% endif %}
<hr>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-3">
<div class="well text-center box-setting">
<i class="fa fa-cubes" aria-hidden="true"></i>
<span>{% trans "Cores"%}</span>
<span class="label label-success">{{virtual_machine.cores}}</span>
</div>
</div>
<div class="col-md-3">
<div class="well text-center box-setting">
<i class="fa fa-tachometer" aria-hidden="true"></i> {% trans "Memory"%} <br/>
<span class="label label-success">{{virtual_machine.memory}} GB</span>
</div>
</div>
<div class="col-md-3">
<div class="well text-center box-setting">
<i class="fa fa-hdd-o" aria-hidden="true"></i>
<span>{% trans "Disk"%}</span>
<span class="label label-success">{{virtual_machine.disk_size|floatformat:2}} GB</span>
</div>
</div>
</div><!--/row-->
</div><!--/col-12-->
</div><!--/row-->
<div class="row">
<div class="col-md-12">
{% trans "Configuration"%}: {{virtual_machine.configuration}}
</div>
</div>
</div>
<div class="tab-pane" id="billing-v">
<div class="row ">
<div class="col-md-12 inline-headers">
<h3>{% trans "Current pricing"%}</h3>
<span class="h3 pull-right"><strong>{{virtual_machine.price|floatformat}} CHF</strong>/month</span>
<hr>
</div>
</div>
</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.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>
</div>
{% if not virtual_machine.status == 'canceled' %}
<div class="row">
<div class="col-md-12 separate-md">
<div class="pull-right">
<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.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">
<button type="button" class="close" data-dismiss="modal"
aria-label="Confirm"><span
aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine"%}</h4>
<p class="modal-text">{% trans "Are you sure do you want to cancel your Virtual Machine "%} {{virtual_machine.name}} ?</p>
</div>
<div class="modal-footer">
<a class="btn btn-danger btn-ok">OK</a>
</div>
</div>
</div>
</div>
<!-- / Cancel Modal -->
</div>
{% endif %}
</div>
</div>
</div>
<div class="clearfix"></div>
{% if messages %}
<div class="alert alert-warning">
{% for message in messages %}
<span>{{ message }}</span>
{% endfor %}
</div>
{% endif %}
<div class="virtual-machine-container dashboard-container">
<h1 class="dashboard-title-thin">{% trans "Your Virtual Machine Detail" %}</h1>
<div class="vm-detail-contain">
<div class="vm-detail-item">
<h2 class="vm-detail-title">{% trans "VM Settings" %} <img src="{% static 'hosting/img/settings.svg' %}" class="un-icon"></h2>
<h3 class="vm-name">{{virtual_machine.name}}</h3>
{% if virtual_machine.ipv6 %}
<div class="vm-detail-ip">
<p>
<span>IPv4:</span>
<span class="value">{{virtual_machine.ipv4}}</span>
<button data-clipboard-text="{{virtual_machine.ipv4}}" class="to_copy btn btn-link" data-toggle="tooltip" data-placement="left" title="{% trans 'Copied' %}" data-trigger="click">
<img class="un-icon" src="{% static 'hosting/img/copy.svg' %}">
</button>
</p>
<p>
<span>IPv6:</span>
<span class="value value-sm-block">{{virtual_machine.ipv6}}</span>
<button data-clipboard-text="{{virtual_machine.ipv6}}" class="to_copy btn btn-link" data-toggle="tooltip" data-placement="left" title="{% trans 'Copied' %}" data-trigger="click">
<img class="un-icon" src="{% static 'hosting/img/copy.svg' %}">
</button>
</p>
</div>
{% endif %}
<div class="vm-detail-config">
<p><span>{% trans "Cores" %}:</span><span class="value">{{virtual_machine.cores}}</span></p>
<p><span>{% trans "Memory" %}:</span><span class="value">{{virtual_machine.memory}} GB</span></p>
<p><span>{% trans "Disk" %}:</span><span class="value">{{virtual_machine.disk_size|floatformat:2}} GB</span></p>
<p><span>{% trans "Configuration" %}:</span><span class="value">{{virtual_machine.configuration}}</span></p>
</div>
</div>
<div class="vm-detail-item">
<h2 class="vm-detail-title">{% trans "Billing" %} <img src="{% static 'hosting/img/billing.svg' %}" class="un-icon"></h2>
<div class="vm-vmid">
<div class="vm-item-subtitle">{% trans "Current Pricing" %}</div>
<div class="vm-item-lg">{{virtual_machine.price|floatformat}} CHF/{% trans "Month" %}</div>
<a class="btn btn-vm-invoice" href="{% url 'hosting:orders' order.pk %}">{% trans "See Invoice" %}</a>
</div>
</div>
<div class="vm-detail-item">
<h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2>
<div class="vm-vmid">
<div class="vm-item-subtitle">{% trans "Your VM is" %}</div>
{% if virtual_machine.state == 'PENDING' %}
<div class="vm-item-lg vm-color-pending">{% trans "Pending" %}</div>
{% elif virtual_machine.state == 'ACTIVE' %}
<div class="vm-item-lg vm-color-online">{% trans "Online" %}</div>
{% elif virtual_machine.state == 'FAILED'%}
<div class="vm-item-lg vm-color-failed">{% trans "Failed" %}</div>
{% endif %}
{% if not virtual_machine.status == 'canceled' %}
<form method="POST" id="virtual_machine_cancel_form" class="cancel-form" action="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}">
{% csrf_token %}
</form>
<button data-href="{% url 'hosting:virtual_machines' virtual_machine.vm_id %}" data-toggle="modal" data-target="#confirm-cancel" class="btn btn-vm-term">{% trans "Terminate VM" %}</button>
{% endif %}
</div>
</div>
</div>
<div class="vm-contact-us">
<div>
<h2 class="vm-detail-title">{% trans "Support / Contact" %} <img class="un-icon visible-xs" src="{% static 'hosting/img/24-hours-support.svg' %}"></h2>
</div>
<div class="vm-contact-us-text text-center">
<img class="un-icon hidden-xs" src="{% static 'hosting/img/24-hours-support.svg' %}">
<div>
<span>{% trans "Something doesn't work?" %}</span> <span>{% trans "We are here to help you!" %}</span>
</div>
</div>
<div class="text-center">
<a class="btn btn-vm-contact" href="mailto:support@datacenterlight.ch">{% trans "CONTACT" %}</a>
</div>
</div>
<div class="text-center">
<a class="btn btn-vm-back" href="{% url 'hosting:virtual_machines' %}">{% trans "BACK TO LIST" %}</a>
</div>
</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">
<button type="button" class="close" data-dismiss="modal" aria-label="Confirm"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine"%}</h4>
<div class="modal-text">
<p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p>
<p><strong>{{virtual_machine.name}}</strong></p>
</div>
</div>
<div class="modal-footer">
<a class="btn btn-danger btn-ok">{% trans "OK" %}</a>
</div>
</div>
</div>
</div>
</div>
<!-- / Cancel Modal -->
{%endblock%}

View file

@ -20,9 +20,12 @@ urlpatterns = [
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'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'ssh_keys/?$', SSHKeyListView.as_view(),
@ -44,5 +47,6 @@ urlpatterns = [
PasswordResetConfirmView.as_view(), name='reset_password_confirm'),
url(r'^logout/?$', auth_views.logout,
{'next_page': '/hosting/login?logged_out=true'}, name='logout'),
url(r'^validate/(?P<validate_slug>.*)/$', SignupValidatedView.as_view(), name='validate')
url(r'^validate/(?P<validate_slug>.*)/$',
SignupValidatedView.as_view(), name='validate')
]

View file

@ -244,7 +244,8 @@ class SignupValidatedView(SignupValidateView):
lurl=login_url)
else:
home_url = '<a href="' + \
reverse('datacenterlight:index') + '">Data Center Light</a>'
reverse('datacenterlight:index') + \
'">Data Center Light</a>'
message = '{sorry_message} <br />{go_back_to} {hurl}'.format(
sorry_message=_("Sorry. Your request is invalid."),
go_back_to=_('Go back to'),
@ -569,7 +570,7 @@ class PaymentVMView(LoginRequiredMixin, FormView):
customer=customer.stripe_id)
# Check if the payment was approved
if not charge_response.get('response_object') and not charge_response.get('paid'):
if not charge_response.get('response_object'):
msg = charge_response.get('error')
messages.add_message(self.request, messages.ERROR, msg, extra_tags='make_charge_error')
return HttpResponseRedirect(reverse('hosting:payment') + '#payment_error')
@ -831,6 +832,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
serializer = VirtualMachineSerializer(vm)
context = {
'virtual_machine': serializer.data,
'order': HostingOrder.objects.get(vm_id=serializer.data['vm_id'])
}
except:
pass