NextCloud Integration

This commit is contained in:
amalelshihaby 2021-09-10 09:58:42 +02:00
parent 02ad7a9441
commit 5f4b10cb82
113 changed files with 18992 additions and 235 deletions

View file

@ -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']

View 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'),
),
]

View file

@ -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)

View file

@ -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>

View file

@ -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 %}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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)

View file

@ -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'),

View file

@ -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

View file

@ -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
View file

4
nextcloud/admin.py Normal file
View file

@ -0,0 +1,4 @@
from django.contrib import admin
from .models import VMMachine
admin.site.register(VMMachine)

9
nextcloud/apps.py Normal file
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View 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 {