diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 3cab0ea0..69929ed9 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -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) diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py index a79ca8be..b0768c9a 100644 --- a/datacenterlight/tests.py +++ b/datacenterlight/tests.py @@ -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())) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 91dbf900..fd5c7959 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -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'), diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index a724b38e..f3d00e47 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -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': { diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po index 3cc30292..6f0ec86b 100644 --- a/hosting/locale/de/LC_MESSAGES/django.po +++ b/hosting/locale/de/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: LANGUAGE \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 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" diff --git a/hosting/static/hosting/css/virtual-machine.css b/hosting/static/hosting/css/virtual-machine.css index 420a9452..45aa68ff 100644 --- a/hosting/static/hosting/css/virtual-machine.css +++ b/hosting/static/hosting/css/virtual-machine.css @@ -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; + } } \ No newline at end of file diff --git a/hosting/static/hosting/img/24-hours-support.svg b/hosting/static/hosting/img/24-hours-support.svg new file mode 100644 index 00000000..4db05be3 --- /dev/null +++ b/hosting/static/hosting/img/24-hours-support.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hosting/static/hosting/img/billing.svg b/hosting/static/hosting/img/billing.svg new file mode 100644 index 00000000..d002fa6c --- /dev/null +++ b/hosting/static/hosting/img/billing.svg @@ -0,0 +1 @@ +billing icon \ No newline at end of file diff --git a/hosting/static/hosting/img/connected.svg b/hosting/static/hosting/img/connected.svg new file mode 100644 index 00000000..fa3875dc --- /dev/null +++ b/hosting/static/hosting/img/connected.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hosting/static/hosting/img/settings.svg b/hosting/static/hosting/img/settings.svg new file mode 100644 index 00000000..61dc8613 --- /dev/null +++ b/hosting/static/hosting/img/settings.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hosting/static/hosting/img/shopping-cart.svg b/hosting/static/hosting/img/shopping-cart.svg new file mode 100644 index 00000000..19e70e1d --- /dev/null +++ b/hosting/static/hosting/img/shopping-cart.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + diff --git a/hosting/static/hosting/js/initial.js b/hosting/static/hosting/js/initial.js index da2887c6..50975806 100644 --- a/hosting/static/hosting/js/initial.js +++ b/hosting/static/hosting/js/initial.js @@ -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); + } + }); }); \ No newline at end of file diff --git a/hosting/templates/hosting/base.html b/hosting/templates/hosting/base.html index b485451f..ec57475d 100644 --- a/hosting/templates/hosting/base.html +++ b/hosting/templates/hosting/base.html @@ -38,7 +38,6 @@ {% with 'hosting/img/'|add:hosting|add:'-intro-bg.png' as image_static %} - alt="">