Finally got all the OTP stuff working
Finalized dbsettings views Easter egg for missing backend pages
This commit is contained in:
parent
d74a4c1b8b
commit
7708128255
27 changed files with 610 additions and 87 deletions
|
@ -9,6 +9,9 @@ class BaseOTPProvider:
|
||||||
def get_logo(self):
|
def get_logo(self):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.get_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
'''Returns True if the provider is properly configured and ready to use.'''
|
'''Returns True if the provider is properly configured and ready to use.'''
|
||||||
|
|
1
core/forms/__init__.py
Normal file
1
core/forms/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from core.forms.auth import *
|
16
core/forms/auth.py
Normal file
16
core/forms/auth.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from django.forms import Form, EmailField, CharField, PasswordInput, ChoiceField
|
||||||
|
|
||||||
|
from core.helpers.otp import get_otp_choices
|
||||||
|
|
||||||
|
class LoginForm(Form):
|
||||||
|
email = EmailField()
|
||||||
|
password = CharField(widget=PasswordInput)
|
||||||
|
|
||||||
|
class OTPSelectorForm(Form):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
otp_choices = kwargs.pop('otp_choices', [])
|
||||||
|
super(OTPSelectorForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['provider'] = ChoiceField(choices=otp_choices)
|
||||||
|
|
||||||
|
class OTPVerificationForm(Form):
|
||||||
|
token = CharField()
|
25
core/helpers/otp.py
Normal file
25
core/helpers/otp.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from core.models import OTPUser
|
||||||
|
from core.modules.otp import providers
|
||||||
|
|
||||||
|
def get_user_otps(user):
|
||||||
|
try:
|
||||||
|
all_otps = OTPUser.objects.filter(user=user)
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
user_providers = [otp.provider for otp in all_otps]
|
||||||
|
active_providers = {}
|
||||||
|
|
||||||
|
for name, provider in providers.items():
|
||||||
|
if name in user_providers:
|
||||||
|
active_providers[name] = provider
|
||||||
|
|
||||||
|
return active_providers
|
||||||
|
|
||||||
|
def get_otp_by_name(name):
|
||||||
|
for pname, provider in providers.items():
|
||||||
|
if pname == name:
|
||||||
|
return provider
|
||||||
|
|
||||||
|
def get_otp_choices(user):
|
||||||
|
return [(name, provider) for name, provider in get_user_otps(user).items()]
|
|
@ -1,2 +1,3 @@
|
||||||
from core.models.files import *
|
from core.models.files import *
|
||||||
from core.models.profiles import *
|
from core.models.profiles import *
|
||||||
|
from core.models.otp import *
|
13
core/models/otp.py
Normal file
13
core/models/otp.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from django.db.models import Model, ForeignKey, CharField, DateTimeField, UUIDField, CASCADE
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
class OTPUser(Model):
|
||||||
|
user = ForeignKey(get_user_model(), CASCADE)
|
||||||
|
provider = CharField(max_length=255)
|
||||||
|
|
||||||
|
class LoginSession(Model):
|
||||||
|
uuid = UUIDField(default=uuid4, primary_key=True)
|
||||||
|
user = ForeignKey(get_user_model(), CASCADE)
|
||||||
|
creation = DateTimeField(auto_now_add=True)
|
|
@ -2,12 +2,12 @@ import importlib
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
providers = []
|
providers = {}
|
||||||
|
|
||||||
for module in settings.EXPEPHALON_MODULES:
|
for module in settings.EXPEPHALON_MODULES:
|
||||||
try:
|
try:
|
||||||
moo = importlib.import_module(f"{module}.otp")
|
moo = importlib.import_module(f"{module}.otp")
|
||||||
for provider in moo.OTPPROVIDERS:
|
for name, provider in moo.OTPPROVIDERS.items():
|
||||||
providers.append(provider)
|
providers[name] = provider
|
||||||
except (AttributeError, ModuleNotFoundError):
|
except (AttributeError, ModuleNotFoundError):
|
||||||
continue
|
continue
|
|
@ -3,7 +3,16 @@ import importlib
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
URLPATTERNS = []
|
from core.views import DashboardView, LoginView, OTPSelectorView, LogoutView, OTPValidatorView, BackendNotImplementedView
|
||||||
|
|
||||||
|
URLPATTERNS = [
|
||||||
|
path('login/', LoginView.as_view(), name="login"),
|
||||||
|
path('login/otp/select/', OTPSelectorView.as_view(), name="otpselector"),
|
||||||
|
path('login/otp/validate/', OTPValidatorView.as_view(), name="otpvalidator"),
|
||||||
|
path('logout/', LogoutView.as_view(), name="logout"),
|
||||||
|
path('admin/', DashboardView.as_view(), name="dashboard"),
|
||||||
|
path('admin/oops/', BackendNotImplementedView.as_view(), name="backendni")
|
||||||
|
]
|
||||||
|
|
||||||
for module in settings.EXPEPHALON_MODULES:
|
for module in settings.EXPEPHALON_MODULES:
|
||||||
try:
|
try:
|
||||||
|
@ -13,6 +22,11 @@ for module in settings.EXPEPHALON_MODULES:
|
||||||
except (AttributeError, ModuleNotFoundError):
|
except (AttributeError, ModuleNotFoundError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if "dbsettings" in settings.INSTALLED_APPS:
|
try:
|
||||||
from core.views import DBSettingsListView
|
from core.views import DBSettingsListView, DBSettingsEditView, DBSettingsDeleteView, DBSettingsCreateView
|
||||||
URLPATTERNS.append(path("admin/dbsettings/", DBSettingsListView.as_view(), name="dbsettings"))
|
URLPATTERNS.append(path("admin/dbsettings/", DBSettingsListView.as_view(), name="dbsettings"))
|
||||||
|
URLPATTERNS.append(path("admin/dbsettings/<pk>/delete/", DBSettingsDeleteView.as_view(), name="dbsettings_delete"))
|
||||||
|
URLPATTERNS.append(path("admin/dbsettings/<pk>/edit/", DBSettingsEditView.as_view(), name="dbsettings_edit"))
|
||||||
|
URLPATTERNS.append(path("admin/dbsettings/create/", DBSettingsCreateView.as_view(), name="dbsettings_create"))
|
||||||
|
except:
|
||||||
|
pass
|
|
@ -6,7 +6,7 @@ from django.conf import settings
|
||||||
|
|
||||||
dashboard_section = NavSection("Dashboard", "")
|
dashboard_section = NavSection("Dashboard", "")
|
||||||
|
|
||||||
dashboard_item = NavItem("Dashboard", "fa-rocket", "backend")
|
dashboard_item = NavItem("Dashboard", "fa-rocket", "dashboard")
|
||||||
|
|
||||||
dashboard_section.add_item(dashboard_item)
|
dashboard_section.add_item(dashboard_item)
|
||||||
|
|
||||||
|
@ -16,10 +16,10 @@ navigations["backend_main"].add_section(dashboard_section)
|
||||||
|
|
||||||
clients_section = NavSection("Clients", "")
|
clients_section = NavSection("Clients", "")
|
||||||
|
|
||||||
client_list_item = NavItem("List Clients", "fa-user-tag", "backend")
|
client_list_item = NavItem("List Clients", "fa-user-tag", "backendni")
|
||||||
client_add_item = NavItem("Add Client", "fa-user-edit", "backend")
|
client_add_item = NavItem("Add Client", "fa-user-edit", "backendni")
|
||||||
client_groups_item = NavItem("Client Groups", "fa-users", "backend")
|
client_groups_item = NavItem("Client Groups", "fa-users", "backendni")
|
||||||
client_leads_item = NavItem("Leads", "fa-blender-phone", "backend")
|
client_leads_item = NavItem("Leads", "fa-blender-phone", "backendni")
|
||||||
|
|
||||||
clients_section.add_item(client_list_item)
|
clients_section.add_item(client_list_item)
|
||||||
clients_section.add_item(client_add_item)
|
clients_section.add_item(client_add_item)
|
||||||
|
@ -32,8 +32,8 @@ navigations["backend_main"].add_section(clients_section)
|
||||||
|
|
||||||
quotes_section = NavSection("Quotes", "")
|
quotes_section = NavSection("Quotes", "")
|
||||||
|
|
||||||
quote_list_item = NavItem("List Quotes", "fa-file-invoice-dollar", "backend")
|
quote_list_item = NavItem("List Quotes", "fa-file-invoice-dollar", "backendni")
|
||||||
quote_create_item = NavItem("Create Quote", "fa-plus-square", "backend")
|
quote_create_item = NavItem("Create Quote", "fa-plus-square", "backendni")
|
||||||
|
|
||||||
quotes_section.add_item(quote_list_item)
|
quotes_section.add_item(quote_list_item)
|
||||||
quotes_section.add_item(quote_create_item)
|
quotes_section.add_item(quote_create_item)
|
||||||
|
@ -44,11 +44,11 @@ navigations["backend_main"].add_section(quotes_section)
|
||||||
|
|
||||||
billing_section = NavSection("Billing", "")
|
billing_section = NavSection("Billing", "")
|
||||||
|
|
||||||
invoice_list_item = NavItem("List Invoices", "fa-file-invoice-dollar", "backend")
|
invoice_list_item = NavItem("List Invoices", "fa-file-invoice-dollar", "backendni")
|
||||||
invoice_create_item = NavItem("Create Invoice", "fa-plus-square", "backend")
|
invoice_create_item = NavItem("Create Invoice", "fa-plus-square", "backendni")
|
||||||
billable_list_item = NavItem("List Billable Items", "fa-hand-holding-usd", "backend")
|
billable_list_item = NavItem("List Billable Items", "fa-hand-holding-usd", "backendni")
|
||||||
billable_create_item = NavItem("Create Billable Item", "fa-plus-square", "backend")
|
billable_create_item = NavItem("Create Billable Item", "fa-plus-square", "backendni")
|
||||||
list_transaction_item = NavItem("Transaction List", "fa-funnel-dollar", "backend")
|
list_transaction_item = NavItem("Transaction List", "fa-funnel-dollar", "backendni")
|
||||||
|
|
||||||
billing_section.add_item(invoice_list_item)
|
billing_section.add_item(invoice_list_item)
|
||||||
billing_section.add_item(invoice_create_item)
|
billing_section.add_item(invoice_create_item)
|
||||||
|
@ -62,9 +62,9 @@ navigations["backend_main"].add_section(billing_section)
|
||||||
|
|
||||||
support_section = NavSection("Support", "")
|
support_section = NavSection("Support", "")
|
||||||
|
|
||||||
ticket_view_item = NavItem("View Tickets", "fa-life-ring", "backend")
|
ticket_view_item = NavItem("View Tickets", "fa-life-ring", "backendni")
|
||||||
ticket_add_item = NavItem("Add Ticket", "fa-plus-square", "backend")
|
ticket_add_item = NavItem("Add Ticket", "fa-plus-square", "backendni")
|
||||||
conversation_add_item = NavItem("Add Conversation", "fa-comments", "backend")
|
conversation_add_item = NavItem("Add Conversation", "fa-comments", "backendni")
|
||||||
|
|
||||||
support_section.add_item(ticket_view_item)
|
support_section.add_item(ticket_view_item)
|
||||||
support_section.add_item(ticket_add_item)
|
support_section.add_item(ticket_add_item)
|
||||||
|
@ -76,8 +76,8 @@ navigations["backend_main"].add_section(support_section)
|
||||||
|
|
||||||
reports_section = NavSection("Reports", "")
|
reports_section = NavSection("Reports", "")
|
||||||
|
|
||||||
report_period_item = NavItem("Income by period", "fa-chart-bar", "backend")
|
report_period_item = NavItem("Income by period", "fa-chart-bar", "backendni")
|
||||||
report_forecast_item = NavItem("Income Forecast", "fa-chart-area", "backend")
|
report_forecast_item = NavItem("Income Forecast", "fa-chart-area", "backendni")
|
||||||
|
|
||||||
reports_section.add_item(report_period_item)
|
reports_section.add_item(report_period_item)
|
||||||
reports_section.add_item(report_forecast_item)
|
reports_section.add_item(report_forecast_item)
|
||||||
|
@ -88,14 +88,14 @@ navigations["backend_main"].add_section(reports_section)
|
||||||
|
|
||||||
administration_section = NavSection("Administration", "")
|
administration_section = NavSection("Administration", "")
|
||||||
|
|
||||||
user_administration_item = NavItem("Administrator Users", "fa-users-cog", "backend")
|
user_administration_item = NavItem("Administrator Users", "fa-users-cog", "backendni")
|
||||||
brand_administration_item = NavItem("Brands", "fa-code-branch", "backend")
|
brand_administration_item = NavItem("Brands", "fa-code-branch", "backendni")
|
||||||
sms_administration_item = NavItem("SMS Gateway", "fa-sms", "backend")
|
sms_administration_item = NavItem("SMS Gateway", "fa-sms", "backendni")
|
||||||
otp_administration_item = NavItem("Two-Factor Authentication", "fa-id-badge", "backend")
|
otp_administration_item = NavItem("Two-Factor Authentication", "fa-id-badge", "backendni")
|
||||||
backup_administration_item = NavItem("Backups", "fa-shield-alt", "backend")
|
backup_administration_item = NavItem("Backups", "fa-shield-alt", "backendni")
|
||||||
product_administration_item = NavItem("Products", "fa-cube", "backend")
|
product_administration_item = NavItem("Products", "fa-cube", "backendni")
|
||||||
pgroup_administration_item = NavItem("Product Groups", "fa-cubes", "backend")
|
pgroup_administration_item = NavItem("Product Groups", "fa-cubes", "backendni")
|
||||||
payment_administration_item = NavItem("Payment Gateways", "fa-credit-card", "backend")
|
payment_administration_item = NavItem("Payment Gateways", "fa-credit-card", "backendni")
|
||||||
dbsettings_item = NavItem("Database Settings", "fa-database", "dbsettings")
|
dbsettings_item = NavItem("Database Settings", "fa-database", "dbsettings")
|
||||||
|
|
||||||
administration_section.add_item(user_administration_item)
|
administration_section.add_item(user_administration_item)
|
||||||
|
|
11
core/urls.py
11
core/urls.py
|
@ -1,10 +1 @@
|
||||||
from django.urls import path, include
|
from core.modules.urls import URLPATTERNS as urlpatterns
|
||||||
|
|
||||||
from core.views import DashboardView
|
|
||||||
from core.modules.urls import URLPATTERNS
|
|
||||||
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('admin/', DashboardView.as_view(), name="backend"),
|
|
||||||
] + URLPATTERNS
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views.generic import TemplateView, ListView
|
from django.views.generic import TemplateView
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from core.views.dbsettings import *
|
||||||
|
from core.views.auth import *
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
class IndexView(TemplateView):
|
class IndexView(TemplateView):
|
||||||
|
@ -10,12 +13,5 @@ class IndexView(TemplateView):
|
||||||
class DashboardView(TemplateView):
|
class DashboardView(TemplateView):
|
||||||
template_name = f"{settings.EXPEPHALON_BACKEND}/index.html"
|
template_name = f"{settings.EXPEPHALON_BACKEND}/index.html"
|
||||||
|
|
||||||
try:
|
class BackendNotImplementedView(TemplateView):
|
||||||
from dbsettings.models import Setting
|
template_name = f"{settings.EXPEPHALON_BACKEND}/notimplemented.html"
|
||||||
|
|
||||||
class DBSettingsListView(ListView):
|
|
||||||
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings.html"
|
|
||||||
model = Setting
|
|
||||||
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
pass
|
|
113
core/views/auth.py
Normal file
113
core/views/auth.py
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.views.generic import FormView, View
|
||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
from core.forms import LoginForm, OTPSelectorForm, OTPVerificationForm
|
||||||
|
from core.models.otp import LoginSession
|
||||||
|
from core.helpers.otp import get_user_otps, get_otp_choices, get_otp_by_name
|
||||||
|
|
||||||
|
class LoginView(FormView):
|
||||||
|
template_name = f"{settings.EXPEPHALON_BACKEND}/login.html"
|
||||||
|
form_class = LoginForm
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
return redirect(request.GET.get("next", "dashboard"))
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
user = authenticate(username=form.cleaned_data['email'],password=form.cleaned_data['password'])
|
||||||
|
if user:
|
||||||
|
if not get_user_otps(user):
|
||||||
|
login(self.request, user)
|
||||||
|
return redirect("dashboard")
|
||||||
|
session = LoginSession.objects.create(user=user)
|
||||||
|
self.request.session["otpsession"] = str(session.uuid)
|
||||||
|
self.request.session["next"] = self.request.GET.get("next", "dashboard")
|
||||||
|
return redirect("otpselector")
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
class OTPSelectorView(FormView):
|
||||||
|
template_name = f"{settings.EXPEPHALON_BACKEND}/otp_selector.html"
|
||||||
|
form_class = OTPSelectorForm
|
||||||
|
|
||||||
|
def clean_session(self):
|
||||||
|
for key in ("otpsession", "otpprovider", "next"):
|
||||||
|
try:
|
||||||
|
del self.request.session[key]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
try:
|
||||||
|
assert self.request.session["otpsession"]
|
||||||
|
except:
|
||||||
|
raise PermissionDenied()
|
||||||
|
user = LoginSession.objects.get(uuid=self.request.session["otpsession"]).user
|
||||||
|
kwargs["otp_choices"] = get_otp_choices(user)
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
self.request.session["otpprovider"] = form.cleaned_data["provider"]
|
||||||
|
return redirect("otpvalidator")
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
self.clean_session()
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
class OTPValidatorView(FormView):
|
||||||
|
template_name = f"{settings.EXPEPHALON_BACKEND}/otp_verifier.html"
|
||||||
|
form_class = OTPVerificationForm
|
||||||
|
|
||||||
|
def clean_session(self):
|
||||||
|
for key in ("otpsession", "otpprovider", "next"):
|
||||||
|
try:
|
||||||
|
del self.request.session[key]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def validate_session(self, request):
|
||||||
|
try:
|
||||||
|
assert request.session["otpsession"]
|
||||||
|
assert request.session["otpprovider"]
|
||||||
|
user = LoginSession.objects.get(uuid=request.session["otpsession"]).user
|
||||||
|
assert request.session["otpprovider"] in get_user_otps(user).keys()
|
||||||
|
provider = get_otp_by_name(request.session["otpprovider"])()
|
||||||
|
return user, provider
|
||||||
|
except:
|
||||||
|
self.clean_session()
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
user, provider = self.validate_session(request)
|
||||||
|
response = provider.start_authentication(user)
|
||||||
|
messages.info(request, response)
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
self.validate_session(request)
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
self.clean_session()
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
user, provider = self.validate_session(self.request)
|
||||||
|
if provider.validate_token(user, form.cleaned_data["token"]):
|
||||||
|
login(self.request, user)
|
||||||
|
ret = redirect(self.request.session.get("next", "dashboard"))
|
||||||
|
self.clean_session()
|
||||||
|
return ret
|
||||||
|
self.clean_session()
|
||||||
|
messages.error(self.request, "Incorrect token entered. Please try again. If the issue persists, contact support to regain access to your account.")
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
class LogoutView(View):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
logout(request)
|
||||||
|
return redirect("login")
|
30
core/views/dbsettings.py
Normal file
30
core/views/dbsettings.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.views.generic import ListView, UpdateView, DeleteView, CreateView
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
|
try:
|
||||||
|
from dbsettings.models import Setting
|
||||||
|
|
||||||
|
class DBSettingsListView(ListView):
|
||||||
|
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings.html"
|
||||||
|
model = Setting
|
||||||
|
|
||||||
|
class DBSettingsEditView(UpdateView):
|
||||||
|
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings_update.html"
|
||||||
|
model = Setting
|
||||||
|
success_url = reverse_lazy("dbsettings")
|
||||||
|
fields = ["key", "value"]
|
||||||
|
|
||||||
|
class DBSettingsDeleteView(DeleteView):
|
||||||
|
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings_delete.html"
|
||||||
|
model = Setting
|
||||||
|
success_url = reverse_lazy("dbsettings")
|
||||||
|
|
||||||
|
class DBSettingsCreateView(CreateView):
|
||||||
|
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings_create.html"
|
||||||
|
model = Setting
|
||||||
|
success_url = reverse_lazy("dbsettings")
|
||||||
|
fields = ["key", "value"]
|
||||||
|
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
|
@ -16,6 +16,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'polymorphic',
|
'polymorphic',
|
||||||
'phonenumber_field',
|
'phonenumber_field',
|
||||||
|
'bootstrap4',
|
||||||
'core',
|
'core',
|
||||||
'dbsettings',
|
'dbsettings',
|
||||||
] + EXPEPHALON_MODULES
|
] + EXPEPHALON_MODULES
|
||||||
|
|
|
@ -5,3 +5,4 @@ boto3
|
||||||
Pillow
|
Pillow
|
||||||
django-polymorphic
|
django-polymorphic
|
||||||
django-phonenumber-field[phonenumbers]
|
django-phonenumber-field[phonenumbers]
|
||||||
|
django-bootstrap4
|
||||||
|
|
|
@ -37,4 +37,4 @@ class SMSOTP(BaseOTPProvider):
|
||||||
except OTPToken.DoesNotExist:
|
except OTPToken.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
OTPPROVIDERS = [SMSOTP]
|
OTPPROVIDERS = {"smsotp": SMSOTP}
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 4e6402443679e0a9d12c7401ac8783ef4646657f
|
|
46
templates/backend/auth_base.html
Normal file
46
templates/backend/auth_base.html
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{% load static %}
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta http-equiv="Content-Language" content="en">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<title>Login - ArchitectUI HTML Bootstrap 4 Dashboard Template</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no"
|
||||||
|
/>
|
||||||
|
<meta name="description" content="ArchitectUI HTML Bootstrap 4 Dashboard Template">
|
||||||
|
|
||||||
|
<!-- Disable tap highlight on IE -->
|
||||||
|
<meta name="msapplication-tap-highlight" content="no">
|
||||||
|
|
||||||
|
<link href="{% static "backend/css/main.css" %}" rel="stylesheet"></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="app-container app-theme-white body-tabs-shadow">
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="h-100">
|
||||||
|
<div class="h-100 no-gutters row">
|
||||||
|
<div class="d-none d-lg-block col-lg-4">
|
||||||
|
<div class="slider-light">
|
||||||
|
<div class="slick-slider">
|
||||||
|
<div>
|
||||||
|
<div class="position-relative h-100 d-flex justify-content-center align-items-center bg-plum-plate" tabindex="-1">
|
||||||
|
<div class="slide-img-bg" style="background-image: url('assets/images/originals/city.jpg');"></div>
|
||||||
|
<div class="slider-content"><h3>Perfect Balance</h3>
|
||||||
|
<p>ArchitectUI is like a dream. Some think it's too good to be true! Extensive collection of unified React Boostrap Components and Elements.</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-100 d-flex bg-white justify-content-center align-items-center col-md-12 col-lg-8">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="{% static "backend/scripts/main.js" %}"></script></body>
|
||||||
|
</html>
|
|
@ -102,16 +102,16 @@
|
||||||
<h6 tabindex="-1" class="dropdown-header">Header</h6>
|
<h6 tabindex="-1" class="dropdown-header">Header</h6>
|
||||||
<button type="button" tabindex="0" class="dropdown-item">Actions</button>
|
<button type="button" tabindex="0" class="dropdown-item">Actions</button>
|
||||||
<div tabindex="-1" class="dropdown-divider"></div>
|
<div tabindex="-1" class="dropdown-divider"></div>
|
||||||
<button type="button" tabindex="0" class="dropdown-item">Dividers</button>
|
<a href="{% url "logout" %}" type="button" tabindex="0" class="dropdown-item">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="widget-content-left ml-3 header-user-info">
|
<div class="widget-content-left ml-3 header-user-info">
|
||||||
<div class="widget-heading">
|
<div class="widget-heading">
|
||||||
Klaus-Uwe Mitterer
|
{{ request.user.get_full_name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="widget-subheading">
|
<div class="widget-subheading">
|
||||||
Chief Expephalon Officer
|
{{ request.user.profile.role }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="widget-content-right header-user-info ml-3">
|
<div class="widget-content-right header-user-info ml-3">
|
||||||
|
|
57
templates/backend/dbsettings/create.html
Normal file
57
templates/backend/dbsettings/create.html
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{% extends "backend/base.html" %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="app-page-title">
|
||||||
|
<div class="page-title-wrapper">
|
||||||
|
<div class="page-title-heading">
|
||||||
|
<div class="page-title-icon">
|
||||||
|
<i class="fa fa-database">
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div>Database Settings - Create Setting
|
||||||
|
<div class="page-title-subheading">Create a new key-value setting
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-title-actions">
|
||||||
|
<button type="button" data-toggle="tooltip" title="New Setting" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||||
|
<i class="fa fa-plus"></i> New Setting
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 col-lg-0">
|
||||||
|
<div class="mb-3 card">
|
||||||
|
<div class="card-header-tab card-header-tab-animation card-header">
|
||||||
|
<div class="card-header-title">
|
||||||
|
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||||
|
Create Setting
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% buttons %}
|
||||||
|
<button type="button" class="btn-shadow mr-3 btn btn-success">
|
||||||
|
<i class="fa fa-check"></i> Save
|
||||||
|
</button>
|
||||||
|
<a href="{% url "dbsettings" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||||
|
<i class="fa fa-times"></i> Cancel
|
||||||
|
</a>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
57
templates/backend/dbsettings/delete.html
Normal file
57
templates/backend/dbsettings/delete.html
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{% extends "backend/base.html" %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="app-page-title">
|
||||||
|
<div class="page-title-wrapper">
|
||||||
|
<div class="page-title-heading">
|
||||||
|
<div class="page-title-icon">
|
||||||
|
<i class="fa fa-database">
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div>Database Settings - Edit Setting
|
||||||
|
<div class="page-title-subheading">Edit key and value of a setting
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-title-actions">
|
||||||
|
<button type="button" data-toggle="tooltip" title="New Setting" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||||
|
<i class="fa fa-plus"></i> New Setting
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 col-lg-0">
|
||||||
|
<div class="mb-3 card">
|
||||||
|
<div class="card-header-tab card-header-tab-animation card-header">
|
||||||
|
<div class="card-header-title">
|
||||||
|
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||||
|
Deleting {{ form.key.value }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
Are you sure you wish to delete {{ form.key.value }}?
|
||||||
|
{% buttons %}
|
||||||
|
<button type="button" class="btn-shadow mr-3 btn btn-success">
|
||||||
|
<i class="fa fa-check"></i> Save
|
||||||
|
</button>
|
||||||
|
<a href="{% url "dbsettings" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||||
|
<i class="fa fa-times"></i> Cancel
|
||||||
|
</a>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -13,9 +13,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="page-title-actions">
|
<div class="page-title-actions">
|
||||||
<button type="button" data-toggle="tooltip" title="New Setting" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
<a href="{% url "dbsettings_create" %}" type="button" data-toggle="tooltip" title="New Setting" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||||
<i class="fa fa-plus"></i> New Setting
|
<i class="fa fa-plus"></i> New Setting
|
||||||
</button>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,8 +45,8 @@
|
||||||
{% for setting in object_list %}
|
{% for setting in object_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ setting.key }}</th>
|
<th scope="row">{{ setting.key }}</th>
|
||||||
<td><a href="javascript:void(0);">Click to display...</a></td>
|
<td><a href="javascript:alert('{{ setting.value }}');">Click to display...</a></td>
|
||||||
<td><a href="/devices/20/edit"><i class="fas fa-edit" title="Edit Setting"></i></a> <a href="#"><i style="color: darkred;" onclick="askdelete(20);" class="fas fa-trash-alt" title="Delete Setting"></i></a></td>
|
<td><a href="{% url "dbsettings_edit" setting.key %}"><i class="fas fa-edit" title="Edit Setting"></i></a> <a href="#"><i style="color: darkred;" onclick="askdelete(20);" class="fas fa-trash-alt" title="Delete Setting"></i></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
57
templates/backend/dbsettings/update.html
Normal file
57
templates/backend/dbsettings/update.html
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{% extends "backend/base.html" %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="app-page-title">
|
||||||
|
<div class="page-title-wrapper">
|
||||||
|
<div class="page-title-heading">
|
||||||
|
<div class="page-title-icon">
|
||||||
|
<i class="fa fa-database">
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div>Database Settings - Edit Setting
|
||||||
|
<div class="page-title-subheading">Edit key and value of a setting
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-title-actions">
|
||||||
|
<button type="button" data-toggle="tooltip" title="New Setting" data-placement="bottom" class="btn-shadow mr-3 btn btn-success">
|
||||||
|
<i class="fa fa-plus"></i> New Setting
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 col-lg-0">
|
||||||
|
<div class="mb-3 card">
|
||||||
|
<div class="card-header-tab card-header-tab-animation card-header">
|
||||||
|
<div class="card-header-title">
|
||||||
|
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||||
|
Editing {{ form.key.value }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% buttons %}
|
||||||
|
<button type="button" class="btn-shadow mr-3 btn btn-success">
|
||||||
|
<i class="fa fa-check"></i> Save
|
||||||
|
</button>
|
||||||
|
<a href="{% url "dbsettings" %}" class="btn-shadow mr-3 btn btn-danger">
|
||||||
|
<i class="fa fa-times"></i> Cancel
|
||||||
|
</a>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -1,26 +1,34 @@
|
||||||
<html>
|
{% extends "backend/auth_base.html" %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
<head>
|
{% block content %}
|
||||||
<link rel="stylesheet" href="{% static "backend/css/login.css">
|
<div class="mx-auto app-login-box col-sm-12 col-md-10 col-lg-9">
|
||||||
<link href="https://fontproxy.kumi.systems/css?family=Ubuntu" rel="stylesheet">
|
<div class="app-logo"></div>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<h4 class="mb-0">
|
||||||
<link rel="stylesheet" href="https://fa.kumi.systems/css/all.css">
|
<span class="d-block">Welcome back,</span>
|
||||||
<title>Sign in</title>
|
<span>Please sign in to your account.</span></h4>
|
||||||
</head>
|
<h6 class="mt-3">No account? <a href="javascript:void(0);" class="text-primary">Sign up now</a></h6>
|
||||||
|
{% bootstrap_messages %}
|
||||||
<body>
|
<div class="divider row"></div>
|
||||||
<div class="main">
|
<div>
|
||||||
<p class="sign" align="center">Sign in</p>
|
<form method="POST" class="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<form class="form1">
|
<div class="form-row">
|
||||||
<input class="un " type="text" align="center" name="username" placeholder="Username">
|
<div class="col-md-6">
|
||||||
<input class="pass" type="password" align="center" name="password" placeholder="Password">
|
<div class="position-relative form-group"><label for="exampleEmail" class="">Email</label><input name="email" id="exampleEmail" placeholder="Email here..." type="email" class="form-control"></div>
|
||||||
<a class="submit" align="center">Sign in</a>
|
|
||||||
<p class="forgot" align="center"><a href="#">Forgot Password?</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
</body>
|
<div class="position-relative form-group"><label for="examplePassword" class="">Password</label><input name="password" id="examplePassword" placeholder="Password here..." type="password"
|
||||||
|
class="form-control"></div>
|
||||||
</html>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="position-relative form-check"><input name="check" id="exampleCheck" type="checkbox" class="form-check-input"><label for="exampleCheck" class="form-check-label">Keep me logged in</label></div>
|
||||||
|
<div class="divider row"></div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="ml-auto"><a href="javascript:void(0);" class="btn-lg btn btn-link">Recover Password</a>
|
||||||
|
<button class="btn btn-primary btn-lg">Login to Dashboard</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
40
templates/backend/notimplemented.html
Normal file
40
templates/backend/notimplemented.html
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{% extends "backend/base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="app-page-title">
|
||||||
|
<div class="page-title-wrapper">
|
||||||
|
<div class="page-title-heading">
|
||||||
|
<div class="page-title-icon">
|
||||||
|
<i class="fa fa-times">
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div>Oops!
|
||||||
|
<div class="page-title-subheading">This is not implemented yet...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-title-actions">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 col-lg-0">
|
||||||
|
<div class="mb-3 card">
|
||||||
|
<div class="card-header-tab card-header-tab-animation card-header">
|
||||||
|
<div class="card-header-title">
|
||||||
|
<i class="header-icon lnr-apartment icon-gradient bg-love-kiss"> </i>
|
||||||
|
But here's something for you:
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane fade show active" id="tabs-eg-77">
|
||||||
|
<div class="card mb-3 widget-chart widget-chart2 text-left w-100">
|
||||||
|
<iframe width="1020" height="630" src="https://www.youtube-nocookie.com/embed/VFZNvj-HfBU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
27
templates/backend/otp_selector.html
Normal file
27
templates/backend/otp_selector.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{% extends "backend/auth_base.html" %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="mx-auto app-login-box col-sm-12 col-md-10 col-lg-9">
|
||||||
|
<div class="app-logo"></div>
|
||||||
|
<h4 class="mb-0">
|
||||||
|
<span class="d-block">Welcome back,</span>
|
||||||
|
<span>Please select your Two-Factor Authentication provider</span></h4>
|
||||||
|
{% bootstrap_messages %}
|
||||||
|
<div class="divider row"></div>
|
||||||
|
<div>
|
||||||
|
<form method="POST" class="">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-row">
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
</div>
|
||||||
|
<div class="position-relative form-check"><input name="check" id="exampleCheck" type="checkbox" class="form-check-input"><label for="exampleCheck" class="form-check-label">Keep me logged in</label></div>
|
||||||
|
<div class="divider row"></div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="ml-auto"><a href="javascript:void(0);" class="btn-lg btn btn-link">Recover Password</a>
|
||||||
|
<button class="btn btn-primary btn-lg">Login to Dashboard</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
27
templates/backend/otp_verifier.html
Normal file
27
templates/backend/otp_verifier.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{% extends "backend/auth_base.html" %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="mx-auto app-login-box col-sm-12 col-md-10 col-lg-9">
|
||||||
|
<div class="app-logo"></div>
|
||||||
|
<h4 class="mb-0">
|
||||||
|
<span class="d-block">Welcome back,</span>
|
||||||
|
<span>Please enter your OTP Token</span></h4>
|
||||||
|
{% bootstrap_messages %}
|
||||||
|
<div class="divider row"></div>
|
||||||
|
<div>
|
||||||
|
<form method="POST" class="">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-row">
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
</div>
|
||||||
|
<div class="position-relative form-check"><input name="check" id="exampleCheck" type="checkbox" class="form-check-input"><label for="exampleCheck" class="form-check-label">Keep me logged in</label></div>
|
||||||
|
<div class="divider row"></div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="ml-auto"><a href="javascript:void(0);" class="btn-lg btn btn-link">Recover Password</a>
|
||||||
|
<button class="btn btn-primary btn-lg">Login to Dashboard</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue