NextCloud Integration
This commit is contained in:
parent
02ad7a9441
commit
5f4b10cb82
113 changed files with 18992 additions and 235 deletions
|
@ -1,35 +1,8 @@
|
|||
import tldextract
|
||||
|
||||
from django import forms
|
||||
from django.forms import ModelForm
|
||||
from django.utils.translation import get_language, ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
from .validators import domain_name_validator
|
||||
from .models import VMInstance
|
||||
from uncloud_pay.models import BillingAddress
|
||||
|
||||
|
||||
class DomainNameField(forms.CharField):
|
||||
description = 'Domain name form field'
|
||||
default_validators = [domain_name_validator, ]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DomainNameField, self).__init__(*args, **kwargs)
|
||||
|
||||
class MainModelForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainModelForm, self).__init__(*args, **kwargs)
|
||||
for visible in self.visible_fields():
|
||||
visible.field.widget.attrs['class'] = 'form-control'
|
||||
|
||||
class MainForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainForm, self).__init__(*args, **kwargs)
|
||||
for visible in self.visible_fields():
|
||||
if (isinstance(visible.field.widget, forms.TextInput)):
|
||||
visible.field.widget.attrs['class'] = 'form-control'
|
||||
elif (isinstance(visible.field.widget, forms.CheckboxInput)):
|
||||
visible.field.widget.attrs['class'] = 'custom-control-input'
|
||||
from uncloud.forms import MainForm, MainModelForm, DomainNameField
|
||||
|
||||
|
||||
class InitialRequestForm(MainForm):
|
||||
|
@ -55,30 +28,4 @@ class RequestDomainsNamesForm(MainForm):
|
|||
raise ValidationError("webclient name already exists")
|
||||
return webclient_name
|
||||
|
||||
class RequestDomainsForm(MainForm):
|
||||
matrix_domain = DomainNameField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Matrix Domain *'}))
|
||||
homeserver_domain = DomainNameField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Homeserver Domain *'}))
|
||||
webclient_domain = DomainNameField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Webclient Domain *'}))
|
||||
is_open_registration = forms.BooleanField(required=False, initial=False)
|
||||
|
||||
def clean(self):
|
||||
homeserver_domain = self.cleaned_data.get('homeserver_domain', False)
|
||||
webclient_domain = self.cleaned_data.get('webclient_domain', False)
|
||||
if homeserver_domain and webclient_domain:
|
||||
# Homserver-Domain and Webclient-Domain cannot be below the same second level domain (i.e. homeserver.abc.ch and webclient.def.cloud are ok,
|
||||
# homeserver.abc.ch and webclient.abc.ch are not ok
|
||||
homeserver_base = tldextract.extract(homeserver_domain).domain
|
||||
webclient_base = tldextract.extract(webclient_domain).domain
|
||||
if homeserver_base == webclient_base:
|
||||
self._errors['webclient_domain'] = self.error_class([
|
||||
'Homserver-Domain and Webclient-Domain cannot be below the same second level domain'])
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class BillingAddressForm(MainModelForm):
|
||||
class Meta:
|
||||
model = BillingAddress
|
||||
fields = ['full_name', 'street',
|
||||
'city', 'postal_code', 'country', 'vat_number', 'active', 'owner']
|
||||
|
||||
|
||||
|
|
20
matrixhosting/migrations/0014_alter_vminstance_order.py
Normal file
20
matrixhosting/migrations/0014_alter_vminstance_order.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.4 on 2021-09-06 08:06
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_pay', '0031_auto_20210819_1304'),
|
||||
('matrixhosting', '0013_auto_20210808_1652'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vminstance',
|
||||
name='order',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='matrix_instance_id', to='uncloud_pay.order'),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,5 @@
|
|||
import logging
|
||||
import uuid
|
||||
import os
|
||||
import datetime
|
||||
import json
|
||||
import sys
|
||||
import gitlab
|
||||
|
@ -17,6 +16,7 @@ from uncloud_pay.models import Order, BillRecord
|
|||
# Initialize logger.
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VMInstance(models.Model):
|
||||
owner = models.ForeignKey(get_user_model(),
|
||||
on_delete=models.CASCADE,
|
||||
|
@ -30,7 +30,7 @@ class VMInstance(models.Model):
|
|||
|
||||
config = models.JSONField(null=False, blank=False)
|
||||
|
||||
order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='instance_id')
|
||||
order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='matrix_instance_id')
|
||||
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
@ -43,12 +43,12 @@ class VMInstance(models.Model):
|
|||
result = yaml.dump(self.config)
|
||||
gl = gitlab.Gitlab(settings.GITLAB_SERVER, oauth_token=settings.GITLAB_OAUTH_TOKEN)
|
||||
project = gl.projects.get(settings.GITLAB_PROJECT_ID)
|
||||
project.files.create({'file_path': settings.GITLAB_YAML_DIR + f'matrix-{self.vm_name}.yaml',
|
||||
project.files.create({'file_path': settings.GITLAB_YAML_DIR + f'matrix/{self.vm_name}.yaml',
|
||||
'branch': 'master',
|
||||
'content': result,
|
||||
'author_email': settings.GITLAB_AUTHOR_EMAIL,
|
||||
'author_name': settings.GITLAB_AUTHOR_NAME,
|
||||
'commit_message': f'Add New Deployment for matrix-{self.vm_name}'})
|
||||
'commit_message': f'Add New Deployment for matrix/{self.vm_name}'})
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
|
@ -58,11 +58,11 @@ class VMInstance(models.Model):
|
|||
return super().delete(*args, **kwargs)
|
||||
gl = gitlab.Gitlab(settings.GITLAB_SERVER, oauth_token=settings.GITLAB_OAUTH_TOKEN)
|
||||
project = gl.projects.get(settings.GITLAB_PROJECT_ID)
|
||||
f_path = settings.GITLAB_YAML_DIR + f'matrix-{self.vm_name}.yaml'
|
||||
f_path = settings.GITLAB_YAML_DIR + f'matrix/{self.vm_name}.yaml'
|
||||
file = project.files.get(file_path=f_path, ref='master')
|
||||
if file:
|
||||
project.files.delete(file_path=f_path,
|
||||
commit_message=f'Delete matrix-{self.vm_name}', branch='master',
|
||||
commit_message=f'Delete matrix/{self.vm_name}', branch='master',
|
||||
author_email=settings.GITLAB_AUTHOR_EMAIL,
|
||||
author_name=settings.GITLAB_AUTHOR_NAME)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
{% url 'matrix:index' as index_url %}
|
||||
{% url 'matrix:orders' as orders_url %}
|
||||
{% url 'matrix:instances' as instances_url %}
|
||||
{% url 'matrix:billing' as payments_url %}
|
||||
{% url 'uncloud_pay:billing' as payments_url %}
|
||||
{% url 'matrix:pricing' as pricing_url %}
|
||||
<nav class="primary-menu navbar navbar-expand-lg">
|
||||
<div id="header-nav" class="collapse navbar-collapse">
|
||||
|
@ -71,7 +71,7 @@
|
|||
<li class="dropdown-divider mx-n3"></li>
|
||||
<li><a class="dropdown-item" href=""><i class="fas fa-user"></i>{%trans "My Profile" %}</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'matrix:orders' %}"><i class="fas fa-shopping-cart"></i>{%trans "Orders" %}</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'matrix:billing' %}"><i class="fas fa-file-invoice"></i>{%trans "Billing" %}</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'uncloud_pay:billing' %}"><i class="fas fa-file-invoice"></i>{%trans "Billing" %}</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'matrix:instances' %}"><i class="fas fa-cloud"></i>{%trans "Instances" %}</a></li>
|
||||
<li class="dropdown-divider mx-n3"></li>
|
||||
<li><a class="dropdown-item" href=""><i class="fas fa-life-ring"></i>Need Help?</a></li>
|
||||
|
|
|
@ -218,11 +218,11 @@
|
|||
</div>
|
||||
<div id="newcard" style="display:none;">
|
||||
<div class="card-details-box p-4 bg-light">
|
||||
{% include "matrixhosting/includes/_card.html" %}
|
||||
{% include "uncloud_pay/includes/_card.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% else%}
|
||||
{% include "matrixhosting/includes/_card.html" %}
|
||||
{% include "uncloud_pay/includes/_card.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
|
||||
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Transaction History"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link active" id="second-tab" href="{% url 'matrixhosting:bills' %}">{% trans "Bills"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">{% trans "Payment Methods"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'uncloud_pay:billing' %}">{% trans "Transaction History"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link active" id="second-tab" href="{% url 'uncloud_pay:bills' %}">{% trans "Bills"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'uncloud_pay:cards' %}">{% trans "Payment Methods"%}</a> </li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
|
@ -78,7 +78,7 @@
|
|||
<span class="px-2 py-1 text-white bill-{{bill.status}}" data-toggle="tooltip" data-original-title="{{bill.status}}">{{bill.get_status_display}}</span>
|
||||
</div>
|
||||
<div class="col-1 col-sm-1 text-center text-3">
|
||||
<form method="get" action="{% url 'matrix:invoice_download' bill_id=bill.id %}">
|
||||
<form method="get" action="{% url 'uncloud_pay:invoice_download' bill_id=bill.id %}">
|
||||
<button class="download-bill border border-primary" type="submit"><span class="text-primary" data-toggle="tooltip" data-original-title="Download"><i class="text-primary fas fa-file-download"></i></span></button>
|
||||
</form>
|
||||
</div>
|
|
@ -40,8 +40,8 @@
|
|||
<p class="text-success text-8 font-weight-500 line-height-07">{%trans "Success!" %}</p>
|
||||
<p class="lead">{%trans "Order has been successfully added. Your VM will be up and running in a few moments. " %}</p>
|
||||
</div>
|
||||
<p class="text-3 mb-4">{%trans "We will send you a confirmation email as soon as it is ready." %} {%trans "Go to your dashboard" %} <a class="btn-link" href="{% url 'matrixhosting:bills' %}">{%trans "Billing" %}</a>.</p>
|
||||
<a href="{% url 'matrix:invoice_download' bill_id=bill_id %}" class="btn btn-white border border-success text-success" role="button"><i class="fas fa-print"></i> {%trans "Download Invoice" %}</a>
|
||||
<p class="text-3 mb-4">{%trans "We will send you a confirmation email as soon as it is ready." %} {%trans "Go to your dashboard" %} <a class="btn-link" href="{% url 'uncloud_pay:bills' %}">{%trans "Billing" %}</a>.</p>
|
||||
<a href="{% url 'uncloud_pay:invoice_download' bill_id=bill_id %}" class="btn btn-white border border-success text-success" role="button"><i class="fas fa-print"></i> {%trans "Download Invoice" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -9,9 +9,9 @@
|
|||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
|
||||
<li class="nav-item"> <a class="nav-link active" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Transaction History"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="second-tab" href="{% url 'matrixhosting:bills' %}">{% trans "Bills"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">{% trans "Payment Methods"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link active" id="first-tab" href="{% url 'uncloud_pay:billing' %}">{% trans "Transaction History"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="second-tab" href="{% url 'uncloud_pay:bills' %}">{% trans "Bills"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'uncloud_pay:cards' %}">{% trans "Payment Methods"%}</a> </li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
|
@ -41,15 +41,15 @@
|
|||
================================ -->
|
||||
<div class="col-12 mb-3" id="allFilters">
|
||||
<div class="custom-control custom-radio custom-control-inline">
|
||||
<input type="radio" id="allTransactions" name="filter" data-url="{% url 'matrix:billing' %}" class="custom-control-input" value="all" {% if not type %} checked=""{% endif %}>
|
||||
<input type="radio" id="allTransactions" name="filter" data-url="{% url 'uncloud_pay:billing' %}" class="custom-control-input" value="all" {% if not type %} checked=""{% endif %}>
|
||||
<label class="custom-control-label" for="allTransactions">{% trans "All"%}</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio custom-control-inline">
|
||||
<input type="radio" id="withdrawal" name="filter" class="custom-control-input" data-url="{% url 'matrix:billing' %}?type=withdraw" value="withdraw" {% if type == 'withdraw' %} checked=""{% endif %}>
|
||||
<input type="radio" id="withdrawal" name="filter" class="custom-control-input" data-url="{% url 'uncloud_pay:billing' %}?type=withdraw" value="withdraw" {% if type == 'withdraw' %} checked=""{% endif %}>
|
||||
<label class="custom-control-label" for="withdrawal">{% trans "Withdrawal"%}</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio custom-control-inline">
|
||||
<input type="radio" id="deposit" name="filter" class="custom-control-input" data-url="{% url 'matrix:billing' %}?type=deposit" value="deposit" {% if type == 'deposit' %} checked=""{% endif %}>
|
||||
<input type="radio" id="deposit" name="filter" class="custom-control-input" data-url="{% url 'uncloud_pay:billing' %}?type=deposit" value="deposit" {% if type == 'deposit' %} checked=""{% endif %}>
|
||||
<label class="custom-control-label" for="deposit">{% trans "Deposit"%}</label>
|
||||
</div>
|
||||
</div>
|
|
@ -1,13 +1,19 @@
|
|||
import datetime
|
||||
import json
|
||||
import logging
|
||||
from django import utils
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import VMInstance
|
||||
from .utils import *
|
||||
from uncloud_pay.models import Order, PricingPlan, BillingAddress, Product, RecurringPeriod, Bill, BillRecord
|
||||
|
||||
import oca
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
vm_product_config = {
|
||||
'features': {
|
||||
|
@ -85,4 +91,12 @@ class VMInstanceTestCase(TestCase):
|
|||
instances = VMInstance.objects.filter(order=order)
|
||||
self.assertEqual(len(instances), 1)
|
||||
|
||||
def test_update_dns(self):
|
||||
#add_to_dns_files("test12", "192.168.1.32", "2001:db8::44", settings.MATRIX_DNS_MAIN_DOMAIN)
|
||||
remove_from_dns_files("test1", settings.MATRIX_DNS_MAIN_DOMAIN)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -9,12 +9,8 @@ app_name = 'matrixhosting'
|
|||
urlpatterns = [
|
||||
path('order/new/', OrderPaymentView.as_view(), name='payment'),
|
||||
path('order/confirm/', OrderConfirmationView.as_view(), name='order_confirmation'),
|
||||
path('order/success/', OrderSuccessView.as_view(), name='order_success'),
|
||||
path('order/invoice/<int:bill_id>/download/', InvoiceDownloadView.as_view(), name='invoice_download'),
|
||||
path('billing/', PaymentsView.as_view(), name='billing'),
|
||||
path('billing/cards', CardsView.as_view(), name='cards'),
|
||||
path('billing/bills', BillsView.as_view(), name='bills'),
|
||||
path('instances/', InstancesView.as_view(), name='instances'),
|
||||
path('instances/webhook/', handle_k8s_webhook),
|
||||
path('orders/', OrdersView.as_view(), name='orders'),
|
||||
path('pricing/', PricingView.as_view(), name='pricing'),
|
||||
path('', IndexView.as_view(), name='index'),
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import json
|
||||
import datetime
|
||||
import gitlab
|
||||
import logging
|
||||
from uncloud_pay.models import Product, Order, ProductToRecurringPeriod, Bill, Payment
|
||||
from .models import VMInstance
|
||||
import dns.zone
|
||||
from dns.exception import DNSException
|
||||
from django.conf import settings
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
def finalize_order(request, customer, billing_address,
|
||||
one_time_price, pricing_plan,
|
||||
|
@ -19,11 +26,64 @@ def finalize_order(request, customer, billing_address,
|
|||
config=json.dumps(specs)
|
||||
)
|
||||
if order:
|
||||
bill = Bill.create_next_bill_for_order(order, ending_date=order.next_cancel_or_downgrade_date(order.starting_date))
|
||||
end_date = order.starting_date + datetime.timedelta(seconds=order.recurring_period.duration_seconds)
|
||||
bill = Bill.create_next_bill_for_order(order, ending_date=end_date)
|
||||
payment= Payment.withdraw(owner=request.user, amount=one_time_price, notes=f"BILL #{bill.id}")
|
||||
if payment:
|
||||
#Close the bill as the payment has been added
|
||||
VMInstance.create_instance(order)
|
||||
bill.close(status="paid")
|
||||
|
||||
return order, bill
|
||||
return order, bill
|
||||
|
||||
def add_to_dns_files(sub_domain, ipv4, ipv6, domain):
|
||||
gl = gitlab.Gitlab(settings.GITLAB_SERVER, oauth_token=settings.GITLAB_DNS_OAUTH_TOKEN)
|
||||
project = gl.projects.get(settings.GITLAB_DNS_PROJECT_ID)
|
||||
f_path = f'zones/{domain}'
|
||||
file = project.files.get(file_path=f_path, ref='master')
|
||||
if file:
|
||||
try:
|
||||
zone_file = file.decode()
|
||||
zone = dns.zone.from_file(zone_file, domain)
|
||||
|
||||
log.info(f"Adding record on {zone.origin} of type A: {sub_domain}")
|
||||
|
||||
rdataset = zone.find_rdataset(sub_domain, rdtype=dns.rdatatype.A, create=True)
|
||||
rdata = dns.rdtypes.IN.A.A(dns.rdataclass.IN, dns.rdatatype.A, address=ipv4)
|
||||
rdataset.add(rdata, ttl=600)
|
||||
|
||||
log.info(f"Adding record on {zone.origin} of type AAAA: {sub_domain}")
|
||||
|
||||
rdataset_ipv6 = zone.find_rdataset(sub_domain, rdtype=dns.rdatatype.AAAA, create=True)
|
||||
rdata_ipv6 = dns.rdtypes.IN.AAAA.AAAA(dns.rdataclass.IN, dns.rdatatype.AAAA, address=ipv6)
|
||||
rdataset_ipv6.add(rdata_ipv6, ttl=600)
|
||||
|
||||
file.content = zone.to_text()
|
||||
|
||||
# Write it back to gitlab
|
||||
file.save(branch='master', commit_message=f'Update ungleich-dns-zones {domain}')
|
||||
|
||||
except DNSException as e:
|
||||
log.error(e.__class__, e)
|
||||
raise e
|
||||
|
||||
def remove_from_dns_files(sub_domain, domain):
|
||||
gl = gitlab.Gitlab(settings.GITLAB_SERVER, oauth_token=settings.GITLAB_DNS_OAUTH_TOKEN)
|
||||
project = gl.projects.get(settings.GITLAB_DNS_PROJECT_ID)
|
||||
f_path = f'zones/{domain}'
|
||||
file = project.files.get(file_path=f_path, ref='master')
|
||||
if file:
|
||||
try:
|
||||
zone_file = file.decode()
|
||||
zone = dns.zone.from_file(zone_file, domain)
|
||||
|
||||
log.info(f"Removing record on {zone.origin}: {sub_domain}")
|
||||
zone.delete_node(sub_domain)
|
||||
|
||||
file.content = zone.to_text()
|
||||
# Write it back to gitlab
|
||||
file.save(branch='master', commit_message=f'Removing {sub_domain} from {domain}')
|
||||
except DNSException as e:
|
||||
log.error(e.__class__, e)
|
||||
raise e
|
||||
|
||||
|
|
|
@ -12,12 +12,18 @@ from django.utils.decorators import method_decorator
|
|||
from django.views import View
|
||||
from django.views.generic import FormView, DetailView
|
||||
from django.views.generic.list import ListView
|
||||
from matrixhosting.forms import InitialRequestForm, BillingAddressForm, RequestDomainsNamesForm
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
from matrixhosting.forms import InitialRequestForm, RequestDomainsNamesForm
|
||||
from uncloud_pay.forms import BillingAddressForm
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.http import (
|
||||
HttpResponseRedirect, JsonResponse, HttpResponse
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django_q.models import Schedule
|
||||
from django_q.tasks import schedule
|
||||
from wkhtmltopdf.views import PDFTemplateResponse
|
||||
from rest_framework import viewsets, permissions
|
||||
|
||||
|
@ -121,8 +127,8 @@ class OrderPaymentView(FormView):
|
|||
def post(self, request, *args, **kwargs):
|
||||
details_form = InitialRequestForm(request.POST)
|
||||
billing_address_form = BillingAddressForm(request.POST)
|
||||
context = self.get_context_data()
|
||||
if not details_form.is_valid() or not billing_address_form.is_valid():
|
||||
context = self.get_context_data()
|
||||
context.update({'details_form': details_form,
|
||||
'billing_address_form': billing_address_form})
|
||||
return self.render_to_response(context)
|
||||
|
@ -162,13 +168,13 @@ class OrderPaymentView(FormView):
|
|||
vat_rate = VATRate.get_vat_rate(billing_address_ins)
|
||||
vat_validation_status = "verified" if billing_address_ins.vat_number_validated_on and billing_address_ins.vat_number_verified else False
|
||||
pricing = get_order_total_with_vat(
|
||||
specs['cores'], specs['memory'], specs['storage'], request.session['pricing']['name'],
|
||||
specs['cores'], specs['memory'], specs['storage'], context['matrix_vm_pricing'].name,
|
||||
vat_rate=vat_rate * 100, vat_validation_status = vat_validation_status
|
||||
)
|
||||
self.request.session['pricing'] = pricing
|
||||
self.request.session['order'] = specs
|
||||
self.request.session['vat_validation_status'] = vat_validation_status
|
||||
amount = get_balance_for_user(self.request.user) - decimal.Decimal(pricing["total"])
|
||||
amount = get_balance_for_user(self.request.user) - pricing["total"]
|
||||
if (amount < 0 and not selected_card):
|
||||
messages.add_message(
|
||||
self.request, messages.ERROR, "You haven't enough balance please select credit card to continue",
|
||||
|
@ -206,7 +212,7 @@ class OrderConfirmationView(DetailView):
|
|||
return HttpResponseRedirect(reverse('matrix:payment'))
|
||||
|
||||
total = self.request.session['pricing']['total']
|
||||
amount = get_balance_for_user(self.request.user) - decimal.Decimal(total)
|
||||
amount = get_balance_for_user(self.request.user) - total
|
||||
if (amount < 0):
|
||||
context['stripe_deposit_amount'] = max(amount, settings.MIN_PER_TRANSACTION)
|
||||
return render(request, self.template_name, context)
|
||||
|
@ -222,10 +228,10 @@ class OrderConfirmationView(DetailView):
|
|||
self.request.session['order']['webclient_domain'] = domains_form.cleaned_data.get('webclient_name') + ".matrix.0co2.cloud"
|
||||
self.request.session['order']['is_open_registration'] = domains_form.cleaned_data.get('is_open_registration')
|
||||
try:
|
||||
amount = get_balance_for_user(self.request.user) - decimal.Decimal(total)
|
||||
amount = get_balance_for_user(self.request.user) - total
|
||||
if (amount < 0):
|
||||
Payment.deposit(request.user, max(abs(amount), settings.MIN_PER_TRANSACTION), source='stripe')
|
||||
amount = get_balance_for_user(self.request.user) - decimal.Decimal(total)
|
||||
amount = get_balance_for_user(self.request.user) - total
|
||||
if (amount < 0):
|
||||
messages.add_message(
|
||||
self.request, messages.ERROR, "Please make sure that you have enough balance in your wallet and try again later.",
|
||||
|
@ -241,7 +247,7 @@ class OrderConfirmationView(DetailView):
|
|||
request.session.get('order'))
|
||||
if order and bill:
|
||||
self.request.session['bill_id'] = bill.id
|
||||
return HttpResponseRedirect(reverse('matrix:order_success'))
|
||||
return HttpResponseRedirect(reverse('uncloud_pay:order_success'))
|
||||
except CardError as e:
|
||||
messages.add_message(
|
||||
self.request, messages.ERROR, e.user_message,
|
||||
|
@ -254,53 +260,7 @@ class OrderConfirmationView(DetailView):
|
|||
context['domains_form'] = domains_form
|
||||
return self.render_to_response(context)
|
||||
|
||||
class OrderSuccessView(DetailView):
|
||||
template_name = "matrixhosting/order_success.html"
|
||||
context_object_name = "order"
|
||||
model = Order
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
|
||||
def get(self, request, *args, **kwargs):
|
||||
context = {
|
||||
'order': self.request.session.get('order'),
|
||||
'bill_id': self.request.session['bill_id'],
|
||||
'balance': get_balance_for_user(self.request.user)
|
||||
}
|
||||
if ('order' not in request.session):
|
||||
return HttpResponseRedirect(reverse('matrix:index'))
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
class InvoiceDownloadView(View):
|
||||
template = 'matrixhosting/invoice.html'
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {'base_url': f'{self.request.scheme}://{self.request.get_host()}'}
|
||||
return context
|
||||
|
||||
def get(self, request, bill_id):
|
||||
cmd_options = settings.REPORT_FORMAT
|
||||
context = self.get_context_data()
|
||||
bill = Bill.objects.get(owner=self.request.user, id=bill_id)
|
||||
if bill:
|
||||
context['bill'] = bill
|
||||
context['vat_rate'] = str(round(bill.vat_rate * 100, 2))
|
||||
context['tax_amount'] = round(bill.vat_rate * bill.subtotal, 2)
|
||||
return PDFTemplateResponse(request=request,
|
||||
template=self.template,
|
||||
filename = f"bill-{bill_id}.pdf",
|
||||
cmd_options= cmd_options,
|
||||
footer_template= 'matrixhosting/includes/invoice_footer.html',
|
||||
context= context)
|
||||
|
||||
|
||||
|
||||
class OrdersView(ListView):
|
||||
template_name = "matrixhosting/orders.html"
|
||||
model = Order
|
||||
|
@ -315,6 +275,12 @@ class OrdersView(ListView):
|
|||
def post(self, request, *args, **kwargs):
|
||||
order = Order.objects.get(id=request.POST.get('order_id', 0))
|
||||
order.cancel()
|
||||
if hasattr(order, 'matrix_instance_id'):
|
||||
last_bill_record = BillRecord.objects.filter(order=order).order_by('id').last()
|
||||
schedule('matrixhosting.tasks.delete_instance',
|
||||
order.instance_id,
|
||||
schedule_type=Schedule.ONCE,
|
||||
next_run=last_bill_record.ending_date or (timezone.now() + datetime.timedelta(hours=1)))
|
||||
return JsonResponse({'message': 'Successfully Cancelled'})
|
||||
|
||||
class InstancesView(ListView):
|
||||
|
@ -328,72 +294,11 @@ class InstancesView(ListView):
|
|||
def get_queryset(self):
|
||||
return VMInstance.objects.filter(owner=self.request.user).order_by('-creation_date')
|
||||
|
||||
class PaymentsView(ListView):
|
||||
template_name = "matrixhosting/payments.html"
|
||||
model = Payment
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(PaymentsView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'balance': get_balance_for_user(self.request.user),
|
||||
'type': self.request.GET.get('type')
|
||||
})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.GET.get('type'):
|
||||
return Payment.objects.filter(owner=self.request.user, type=self.request.GET.get('type')).order_by('-timestamp')
|
||||
return Payment.objects.filter(owner=self.request.user).order_by('-timestamp')
|
||||
|
||||
class CardsView(ListView):
|
||||
template_name = "matrixhosting/cards.html"
|
||||
model = StripeCreditCard
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CardsView, self).get_context_data(**kwargs)
|
||||
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
|
||||
setup_intent = uncloud_stripe.create_setup_intent(customer_id)
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'balance': get_balance_for_user(self.request.user),
|
||||
'client_secret': setup_intent.client_secret,
|
||||
'username': self.request.user.username,
|
||||
'stripe_pk':uncloud_stripe.public_api_key,
|
||||
'min_amount': settings.MIN_PER_TRANSACTION
|
||||
})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
uncloud_stripe.sync_cards_for_user(self.request.user)
|
||||
return StripeCreditCard.objects.filter(owner=self.request.user).order_by('-active')
|
||||
|
||||
class BillsView(ListView):
|
||||
template_name = "matrixhosting/bills.html"
|
||||
model = Bill
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(BillsView, self).get_context_data(**kwargs)
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'balance': get_balance_for_user(self.request.user),
|
||||
})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
return Bill.objects.filter(owner=self.request.user).order_by('-creation_date')
|
||||
|
||||
|
||||
@require_POST
|
||||
@csrf_exempt
|
||||
def handle_k8s_webhook(request):
|
||||
payload = request.body
|
||||
print(">>>>>>>>>>>>>>>>>>>>>>>>>>")
|
||||
print(payload)
|
||||
return HttpResponse(status=200)
|
||||
|
|
0
nextcloud/__init__.py
Normal file
0
nextcloud/__init__.py
Normal file
4
nextcloud/admin.py
Normal file
4
nextcloud/admin.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from django.contrib import admin
|
||||
from .models import VMMachine
|
||||
|
||||
admin.site.register(VMMachine)
|
9
nextcloud/apps.py
Normal file
9
nextcloud/apps.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MatrixhostingConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'nextcloud'
|
||||
|
||||
def ready(self):
|
||||
from . import signals
|
29
nextcloud/forms.py
Normal file
29
nextcloud/forms.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from nextcloud.models import VMMachine
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import get_language, ugettext_lazy as _
|
||||
from uncloud.forms import MainForm, DomainNameField
|
||||
|
||||
class InitialRequestForm(MainForm):
|
||||
cores = forms.IntegerField(label='CPU', min_value=1, max_value=48, initial=1)
|
||||
memory = forms.IntegerField(label='RAM', min_value=2, max_value=200, initial=2)
|
||||
storage = forms.IntegerField(label='Storage', min_value=100, max_value=10000, initial=100)
|
||||
pricing_name = forms.CharField(required=True)
|
||||
|
||||
class RequestDomainsNamesForm(MainForm):
|
||||
subdomain = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Subdomain *'}))
|
||||
main_domain = forms.ChoiceField(required=True, choices= (
|
||||
('0co2.cloud', '.0co2.cloud'),
|
||||
('glarner.cloud', '.glarner.cloud')
|
||||
))
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
subdomain = cleaned_data['subdomain']
|
||||
main_domain = cleaned_data['main_domain']
|
||||
if VMMachine.objects.filter(domain=f"{subdomain}.{main_domain}").exists():
|
||||
raise ValidationError("domain name already exists")
|
||||
return cleaned_data
|
||||
|
||||
|
||||
|
||||
|
90
nextcloud/models.py
Normal file
90
nextcloud/models.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
import logging
|
||||
import uuid
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import gitlab
|
||||
import yaml
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from uncloud_pay.models import Order, BillRecord
|
||||
|
||||
|
||||
# Initialize logger.
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class VMMachine(models.Model):
|
||||
owner = models.ForeignKey(get_user_model(),
|
||||
on_delete=models.CASCADE,
|
||||
editable=True)
|
||||
|
||||
vm_name = models.CharField(max_length=253, editable=False, unique=True)
|
||||
|
||||
domain = models.CharField(max_length=253, unique=True, blank=True)
|
||||
|
||||
config = models.JSONField(null=False, blank=False)
|
||||
|
||||
order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='instance_id')
|
||||
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
termination_date = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Save it as new yaml file and push it to github repo
|
||||
if 'test' in sys.argv:
|
||||
return super().save(*args, **kwargs)
|
||||
result = yaml.dump(self.config)
|
||||
gl = gitlab.Gitlab(settings.GITLAB_SERVER, oauth_token=settings.GITLAB_OAUTH_TOKEN)
|
||||
project = gl.projects.get(settings.GITLAB_PROJECT_ID)
|
||||
project.files.create({'file_path': settings.GITLAB_YAML_DIR + f'nextcloud/{self.vm_name}.yaml',
|
||||
'branch': 'master',
|
||||
'content': result,
|
||||
'author_email': settings.GITLAB_AUTHOR_EMAIL,
|
||||
'author_name': settings.GITLAB_AUTHOR_NAME,
|
||||
'commit_message': f'Add New Deployment for nextcloud/{self.vm_name}'})
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
# Delete the deployment yaml file first then
|
||||
# Then delete it
|
||||
if 'test' in sys.argv:
|
||||
return super().delete(*args, **kwargs)
|
||||
gl = gitlab.Gitlab(settings.GITLAB_SERVER, oauth_token=settings.GITLAB_OAUTH_TOKEN)
|
||||
project = gl.projects.get(settings.GITLAB_PROJECT_ID)
|
||||
f_path = settings.GITLAB_YAML_DIR + f'nextcloud/{self.vm_name}.yaml'
|
||||
file = project.files.get(file_path=f_path, ref='master')
|
||||
if file:
|
||||
project.files.delete(file_path=f_path,
|
||||
commit_message=f'Delete nextcloud/{self.vm_name}', branch='master',
|
||||
author_email=settings.GITLAB_AUTHOR_EMAIL,
|
||||
author_name=settings.GITLAB_AUTHOR_NAME)
|
||||
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.id}-{self.order}"
|
||||
|
||||
@classmethod
|
||||
def delete_for_bill(cls, bill):
|
||||
bill_records = BillRecord.objects.filter(bill=bill)
|
||||
for record in bill_records:
|
||||
instances = VMMachine.objects.filter(order=record.order)
|
||||
for instance in instances:
|
||||
instance.delete()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def create_instance(cls, order):
|
||||
machine = cls.objects.filter(order=order).first()
|
||||
if not machine:
|
||||
order_config = json.loads(order.config)
|
||||
instance_config = {'cpuCores': order_config['cores'], 'ram': order_config['memory'], 'storage': order_config['storage'],
|
||||
'domain': order_config['domain']}
|
||||
cls.objects.create(owner=order.owner, order=order, vm_name=order_config['domain'],
|
||||
domain=order_config['domain'],
|
||||
config=instance_config)
|
8
nextcloud/serializers.py
Normal file
8
nextcloud/serializers.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from .models import *
|
||||
|
||||
class VMMachineSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = VMMachine
|
||||
fields = '__all__'
|
2
nextcloud/signals.py
Normal file
2
nextcloud/signals.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
|
6
nextcloud/static/nextcloud/css/bootstrap-select.min.css
vendored
Normal file
6
nextcloud/static/nextcloud/css/bootstrap-select.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
nextcloud/static/nextcloud/css/bootstrap.min.css
vendored
Normal file
7
nextcloud/static/nextcloud/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1346
nextcloud/static/nextcloud/css/common.css
Normal file
1346
nextcloud/static/nextcloud/css/common.css
Normal file
File diff suppressed because it is too large
Load diff
1
nextcloud/static/nextcloud/css/fontawesome-all.min.css
vendored
Normal file
1
nextcloud/static/nextcloud/css/fontawesome-all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
618
nextcloud/static/nextcloud/css/hosting.css
Normal file
618
nextcloud/static/nextcloud/css/hosting.css
Normal file
|
@ -0,0 +1,618 @@
|
|||
.navbar-transparent #logoWhite {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-transparent #logoBlack {
|
||||
display: block;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.topnav .navbar-fixed-top .navbar-collapse {
|
||||
max-height: 740px;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-header {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.navbar-right .highlights-dropdown .dropdown-menu {
|
||||
left: 0 !important;
|
||||
min-width: 155px;
|
||||
margin-left: 15px;
|
||||
padding: 0 5px 8px !important;
|
||||
}
|
||||
|
||||
@media(min-width: 768px) {
|
||||
.navbar-default .navbar-nav>li a,
|
||||
.navbar-right .highlights-dropdown .dropdown-menu>li a {
|
||||
font-weight: 300;
|
||||
}
|
||||
.navbar-right .highlights-dropdown .dropdown-menu {
|
||||
border-width: 0 0 1px 0;
|
||||
border-color: #e7e7e7;
|
||||
box-shadow: -8px 14px 20px -5px rgba(77, 77, 77, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-right .highlights-dropdown .dropdown-menu>li a {
|
||||
font-size: 13px;
|
||||
font-family: 'Lato', sans-serif;
|
||||
padding: 1px 10px 1px 18px !important;
|
||||
background: transparent;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.navbar-right .highlights-dropdown .dropdown-menu>li a:hover,
|
||||
.navbar-right .highlights-dropdown .dropdown-menu>li a:focus,
|
||||
.navbar-right .highlights-dropdown .dropdown-menu>li a:active {
|
||||
background: transparent;
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
|
||||
.un-icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
opacity: 0.5;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
|
||||
/***** DCL payment page **********/
|
||||
|
||||
.dcl-order-container {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.dcl-place-order-text {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.card-warning-content {
|
||||
font-weight: 300;
|
||||
border: 1px solid #a1a1a1;
|
||||
border-radius: 3px;
|
||||
padding: 5px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.card-warning-error {
|
||||
border: 1px solid #EB4D5C;
|
||||
color: #EB4D5C;
|
||||
}
|
||||
|
||||
.card-warning-addtional-margin {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.card-cvc-element label {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.card-element {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-element label {
|
||||
width: 100%;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.my-input {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.card-cvc-element .my-input {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#card-errors {
|
||||
clear: both;
|
||||
padding: 0 0 10px;
|
||||
color: #eb4d5c;
|
||||
}
|
||||
|
||||
.credit-card-goup {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.card-expiry-element {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.card-cvc-element {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#billing-form .form-control {
|
||||
box-shadow: none !important;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.dcl-order-container {
|
||||
width: 990px;
|
||||
padding: 0 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-vm p.copyright {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-nav>.open>a,
|
||||
.navbar-default .navbar-nav>.open>a:focus,
|
||||
.navbar-default .navbar-nav>.open>a:hover,
|
||||
.navbar-default .navbar-nav>.active>a,
|
||||
.navbar-default .navbar-nav>.active>a:focus,
|
||||
.navbar-default .navbar-nav>.active>a:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.navbar-default .navbar-nav .open .dropdown-menu>.active a,
|
||||
.navbar-default .navbar-nav .open .dropdown-menu>.active a:focus,
|
||||
.navbar-default .navbar-nav .open .dropdown-menu>.active a:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* bootstrap input box-shadow disable */
|
||||
|
||||
.has-error .form-control:focus,
|
||||
.has-error .form-control:active,
|
||||
.has-success .form-control:focus,
|
||||
.has-success .form-control:active {
|
||||
box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.content-dashboard {
|
||||
min-height: calc(100vh - 96px);
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
max-width: 1120px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.content-dashboard {
|
||||
padding: 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
select {
|
||||
width: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn:focus,
|
||||
.btn:active:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***********Styles for Model********************/
|
||||
|
||||
.modal-content {
|
||||
border-radius: 0px;
|
||||
font-family: Lato, "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
width: 100%;
|
||||
float: left;
|
||||
border-radius: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
min-height: 30px;
|
||||
border-bottom: 0px solid #e5e5e5;
|
||||
padding: 0px 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-header .close {
|
||||
font-size: 75px;
|
||||
font-weight: 300;
|
||||
margin-top: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 11px;
|
||||
z-index: 10;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.modal-header .close span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modal-header .close:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
float: left;
|
||||
padding: 0px 30px 15px 30px;
|
||||
}
|
||||
|
||||
.modal-body .modal-icon i {
|
||||
font-size: 80px;
|
||||
font-weight: 100;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.modal-body .modal-icon {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin: 0;
|
||||
line-height: 1.42857143;
|
||||
font-size: 25px;
|
||||
padding: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.modal-text {
|
||||
padding-top: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.modal-text p:not(:last-of-type) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.modal-title+.modal-footer {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top: 0px solid #e5e5e5;
|
||||
width: 100%;
|
||||
float: left;
|
||||
text-align: center;
|
||||
padding: 15px 15px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
width: 40%;
|
||||
margin: 15px auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.modal-dialog {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.modal-dialog {
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width: 576px) {
|
||||
.modal:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
margin-right: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ========= */
|
||||
|
||||
.btn-wide {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.choice-btn {
|
||||
min-width: 110px;
|
||||
background-color: #3C5480;
|
||||
color: #fff;
|
||||
border: 2px solid #3C5480;
|
||||
padding: 4px 10px;
|
||||
transition: 0.3s all ease-out;
|
||||
}
|
||||
|
||||
.choice-btn:focus,
|
||||
.choice-btn:hover,
|
||||
.choice-btn:active {
|
||||
color: #3C5480;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.choice-btn {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-container {
|
||||
padding-top: 70px;
|
||||
padding-bottom: 11%;
|
||||
}
|
||||
|
||||
.last-p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dcl-payment-section {
|
||||
max-width: 391px;
|
||||
margin: 0 auto 30px;
|
||||
padding: 0 10px 30px;
|
||||
border-bottom: 1px solid #edebeb;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dcl-payment-section hr {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.dcl-payment-section .top-hr {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.dcl-payment-section h3 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dcl-payment-section p {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.dcl-payment-section .card-warning-content {
|
||||
padding: 8px 10px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.dcl-payment-order strong {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.dcl-payment-order p {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.dcl-payment-section .form-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dcl-payment-section .form-control {
|
||||
box-shadow: none;
|
||||
padding: 6px 12px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.dcl-payment-user {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dcl-payment-user h4 {
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.dcl-payment-grid {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.dcl-payment-box {
|
||||
width: 50%;
|
||||
position: relative;
|
||||
padding: 0 30px;
|
||||
}
|
||||
.dcl-payment-box:nth-child(2) {
|
||||
order: 1;
|
||||
}
|
||||
.dcl-payment-box:nth-child(4) {
|
||||
order: 2;
|
||||
}
|
||||
.dcl-payment-section {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 0;
|
||||
border-bottom-width: 5px;
|
||||
}
|
||||
.dcl-payment-box:nth-child(2n) .dcl-payment-section {
|
||||
border-bottom: none;
|
||||
}
|
||||
.dcl-payment-box:nth-child(1):after,
|
||||
.dcl-payment-box:nth-child(2):after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
background: #eee;
|
||||
width: 1px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
top: 20px;
|
||||
bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#virtual_machine_create_form {
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* hosting-order */
|
||||
|
||||
.order-detail-container {
|
||||
max-width: 600px;
|
||||
margin: 100px auto 40px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 30px 30px 20px;
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
.order-detail-container .dashboard-title-thin {
|
||||
margin-top: 0;
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
.order-detail-container .dashboard-title-thin .un-icon {
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
.order-detail-container .dashboard-container-head {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin-bottom: 38px;
|
||||
}
|
||||
|
||||
.order-detail-container .order-details { |