Refactoring: new multi app structure

This commit is contained in:
Stepan Zhukovsky 2023-08-15 03:13:07 +09:00
parent 30b3efa5fc
commit e45d1af857
94 changed files with 634 additions and 1548 deletions

View File

@ -3,4 +3,5 @@ from django.apps import AppConfig
class AccountConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'account'
name = 'apps.account'
verbose_name = 'Auth and account management'

View File

@ -14,7 +14,7 @@ app_name = 'account'
urlpatterns = [
# WEB LOGOUT:
path(
'accounts/logout/',
'account/logout/',
LogoutView.as_view(next_page=settings.LOGOUT_REDIRECT_URL),
name='logout'
)

View File

@ -5,7 +5,7 @@ from django_filters.rest_framework import (
)
from django_filters import widgets
from .models import Archive, Ticket
from apps.collector.models import Archive, Ticket
from .utils import DateTimeFilterMixin

View File

@ -3,7 +3,7 @@ from rest_framework import serializers
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.openapi import OpenApiTypes
from .models import Archive, Platform, Ticket
from apps.collector.models import Archive, Platform, Ticket
@extend_schema_field(OpenApiTypes.NUMBER)

View File

@ -0,0 +1,21 @@
from django.urls import path, include
from rest_framework import routers
from . import views
# ▄▀█ █▀█ █
# █▀█ █▀▀ █
# -- -- --
app_name = 'collector_api'
router = routers.DefaultRouter()
router.register(r'archives', views.ArchiveViewSet)
router.register(r'platforms', views.PlatformViewSet)
router.register(r'tickets', views.TicketViewSet)
urlpatterns = [
# CRUD:
path('v1/', include(router.urls)),
]

View File

@ -0,0 +1,20 @@
from django_filters import NumberFilter
class DateTimeFilterMixin:
year__gte = NumberFilter(
field_name='time_create',
lookup_expr='year__gte'
)
year__lte = NumberFilter(
field_name='time_create',
lookup_expr='year__lte'
)
month__gte = NumberFilter(
field_name='time_create',
lookup_expr='month__gte'
)
month__lte = NumberFilter(
field_name='time_create',
lookup_expr='month__lte'
)

View File

@ -1,9 +1,4 @@
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import FileResponse
from django.views import generic
from django.views.generic.detail import SingleObjectMixin
from django.db.models import Q
from rest_framework import status
# from rest_framework.decorators import action
@ -15,14 +10,10 @@ from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
from two_factor.views import OTPRequiredMixin
from apps.collector.models import Archive, Ticket, Platform # ???????
from .models import Archive, Ticket, Platform
from .forms import TicketForm
from .filters import ArchiveFilter, TicketFilter
from .utils import PageTitleViewMixin
from .permissions import IsGuestUpload
from .serializers import (
PublicArchiveUploadSerializer,
ArchiveSerializer,
@ -31,104 +22,6 @@ from .serializers import (
)
class ArchiveHandlerView(
OTPRequiredMixin,
LoginRequiredMixin,
SingleObjectMixin,
generic.View):
model = Archive
slug_field = 'file'
slug_url_kwarg = 'path'
def get(self, request, path):
self.object = self.get_object()
return FileResponse(self.object.file)
class CreateTicket(LoginRequiredMixin, PageTitleViewMixin, generic.CreateView):
model = Ticket
form_class = TicketForm
template_name = 'collector/ticket_create.html'
def get_title(self):
return f'{self.title} - create'
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class UpdateTicket(LoginRequiredMixin, PageTitleViewMixin, generic.UpdateView):
model = Ticket
form_class = TicketForm
template_name = 'collector/ticket_create.html'
slug_field = 'number'
slug_url_kwarg = 'ticket'
def get_title(self, **kwargs):
return f'{self.title} - {self.kwargs.get("ticket", "update")}'
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class ListAllTickets(LoginRequiredMixin, PageTitleViewMixin, generic.ListView):
model = Ticket
template_name = 'collector/tickets.html'
context_object_name = 'tickets'
paginate_by = 5
title = 'Collector - tickets'
def get_queryset(self):
search_query = self.request.GET.get('search', '')
if search_query:
query_list = []
try:
for item in search_query.split(','):
query_list.append(int(item))
except ValueError:
return super().get_queryset()
queryset = self.model.objects.filter(
Q(number__in=query_list) | Q(number__icontains=query_list[0])
)
self.paginate_by = 100 # ? fake disable pagination)
return queryset
return super().get_queryset()
class ListPlatformTickets(
LoginRequiredMixin,
PageTitleViewMixin,
generic.ListView
):
model = Ticket
template_name = 'collector/tickets.html'
context_object_name = 'tickets'
# allow_empty = False
paginate_by = 5
def get_title(self, **kwargs):
return f'{self.title} - {self.kwargs.get("platform", "tickets")}'
def get_queryset(self):
return Ticket.objects.filter(
platform__name=self.kwargs.get('platform')
)
class DetailTicket(LoginRequiredMixin, PageTitleViewMixin, generic.DetailView):
model = Ticket
template_name = 'collector/ticket.html'
context_object_name = 'ticket'
slug_field = 'number'
slug_url_kwarg = 'ticket'
def get_title(self, **kwargs):
return f'{self.title} - {self.kwargs.get("ticket", "show")}'
class ArchiveViewSet(viewsets.ModelViewSet):
queryset = Archive.objects.order_by('-time_create')
serializer_class = ArchiveSerializer

View File

@ -3,4 +3,5 @@ from django.apps import AppConfig
class CollectorConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'collector'
name = 'apps.collector'
verbose_name = 'Collector archives for analyse'

View File

@ -1,11 +1,13 @@
# Generated by Django 4.2 on 2023-07-28 14:40
# Generated by Django 4.2 on 2023-08-14 09:07
import collector.utils
import apps.collector.utils
from django.conf import settings
import django.core.files.storage
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import pathlib
import uuid
class Migration(migrations.Migration):
@ -21,7 +23,7 @@ class Migration(migrations.Migration):
name='Platform',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20)),
('name', models.CharField(max_length=20, unique=True)),
('pretty_name', models.CharField(max_length=20)),
],
),
@ -29,26 +31,29 @@ class Migration(migrations.Migration):
name='Ticket',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.IntegerField()),
('number', models.IntegerField(db_index=True, unique=True)),
('resolved', models.BooleanField(default=False)),
('note', models.TextField(blank=True)),
('token', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('attempts', models.IntegerField(default=5, validators=[django.core.validators.MaxValueValidator(10), django.core.validators.MinValueValidator(0)])),
('time_create', models.DateTimeField(auto_now_add=True)),
('time_update', models.DateTimeField(auto_now=True)),
('platform', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='collector.platform')),
('platform', models.ForeignKey(db_column='platform_name', on_delete=django.db.models.deletion.CASCADE, to='collector.platform', to_field='name')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-time_create'],
},
),
migrations.CreateModel(
name='Archive',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(blank=True, null=True, storage=django.core.files.storage.FileSystemStorage(base_url='/archives/', location=pathlib.PurePosixPath('/home/stepan/Documents/Dev/ISPsystem/logs-collector/logs_collector/archives')), upload_to=collector.utils.logs_dir_path)),
('size', models.CharField(blank=True, max_length=50)),
('sha1', models.CharField(editable=False, max_length=1024)),
('file', models.FileField(blank=True, null=True, storage=django.core.files.storage.FileSystemStorage(base_url='/archives/', location=pathlib.PurePosixPath('/home/stepan/Documents/Dev/ISPsystem/logs-collector/logs_collector/archives')), upload_to=apps.collector.utils.logs_dir_path)),
('md5', models.CharField(editable=False, max_length=1024)),
('time_create', models.DateTimeField(auto_now_add=True)),
('time_update', models.DateTimeField(auto_now=True)),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='collector.ticket')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('ticket', models.ForeignKey(db_column='ticket_number', on_delete=django.db.models.deletion.CASCADE, to='collector.ticket', to_field='number')),
],
),
]

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 495 B

View File

Before

Width:  |  Height:  |  Size: 930 B

After

Width:  |  Height:  |  Size: 930 B

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,30 @@
{% extends 'base.html' %}
{% load static %}
{% block collector_head %}
<title>{% block title %}{% endblock title %}</title>
{% endblock collector_head %}
{% block collector_content %}
<header>
<section>
{% include 'includes/navigation.html' %}
</section>
</header>
<main>
<section>
{% block main %}{% endblock main %}
</section>
</main>
<footer>
<section>
{% block footer %}{% endblock footer %}
</section>
</footer>
{% endblock collector_content %}
{% block collector_scripts %}
<script src="{% static 'collector/js/jquery-3.7.0.min.js' %}"></script>
{% block bs %}{% endblock bs %}
{% block jquery %}{% endblock jquery %}
{% endblock collector_scripts %}

View File

@ -30,7 +30,7 @@
>Cancel
</button>
<a
href="{% url 'collector:archive-detail' archive.id %}"
href="{% url 'collector_api:archive-detail' archive.id %}"
type="button"
class="btn btn-danger btn-archive-eraser"
data-bs-dismiss="modal"

View File

@ -27,7 +27,7 @@
</button>
<a
type="button"
href="{% url 'collector:ticket-detail' ticket.number %}"
href="{% url 'collector_api:ticket-detail' ticket.number %}"
class="btn btn-danger btn-ticket-del"
data-bs-dismiss="modal"
data-jq-ticket-del-target="#div-ticket-{{ ticket.number }}"

View File

@ -9,7 +9,7 @@
type="checkbox"
role="switch"
name="ticket-state"
ticket-state-url="{% url 'collector:ticket-detail' ticket.number %}"
ticket-state-url="{% url 'collector_api:ticket-detail' ticket.number %}"
{% if ticket.resolved %} ticket-state-switch="1" {% endif %}
{% if ticket.resolved %} checked {% endif %}>
</div>

View File

@ -2,7 +2,7 @@ import markdown as md
from django import template
from django.template.defaultfilters import stringfilter
from collector.models import Platform
from apps.collector.models import Platform
register = template.Library()

View File

@ -1,6 +1,4 @@
from django.urls import path, include
from rest_framework import routers
from django.urls import path
from . import views
@ -50,17 +48,3 @@ urlpatterns = [
name='update'
),
]
# ▄▀█ █▀█ █
# █▀█ █▀▀ █
# -- -- --
router = routers.DefaultRouter()
router.register(r'archives', views.ArchiveViewSet)
router.register(r'platforms', views.PlatformViewSet)
router.register(r'tickets', views.TicketViewSet)
urlpatterns += [
# CRUD:
path('api/v1/', include(router.urls)),
]

View File

@ -1,5 +1,4 @@
import os
from django_filters import NumberFilter
def logs_dir_path(instance, filename):
@ -10,6 +9,7 @@ def logs_dir_path(instance, filename):
return f'{instance.ticket.number}/{filename}'
# deprecated
def get_file_size(file_path, unit='bytes'):
file_size = os.path.getsize(file_path)
exponents_map = {'bytes': 0, 'kb': 1, 'mb': 2, 'gb': 3}
@ -21,6 +21,7 @@ def get_file_size(file_path, unit='bytes'):
return round(size, 3)
# deprecated
def is_ajax(request):
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return True
@ -40,22 +41,3 @@ class PageTitleViewMixin:
context = super().get_context_data(**kwargs)
context['title'] = self.get_title()
return context
class DateTimeFilterMixin:
year__gte = NumberFilter(
field_name='time_create',
lookup_expr='year__gte'
)
year__lte = NumberFilter(
field_name='time_create',
lookup_expr='year__lte'
)
month__gte = NumberFilter(
field_name='time_create',
lookup_expr='month__gte'
)
month__lte = NumberFilter(
field_name='time_create',
lookup_expr='month__lte'
)

View File

@ -0,0 +1,109 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import FileResponse
from django.views import generic
from django.views.generic.detail import SingleObjectMixin
from django.db.models import Q
from two_factor.views import OTPRequiredMixin
from .forms import TicketForm
from .models import Archive, Ticket
from .utils import PageTitleViewMixin
class ArchiveHandlerView(
OTPRequiredMixin,
LoginRequiredMixin,
SingleObjectMixin,
generic.View):
model = Archive
slug_field = 'file'
slug_url_kwarg = 'path'
def get(self, request, path):
self.object = self.get_object()
return FileResponse(self.object.file)
class CreateTicket(LoginRequiredMixin, PageTitleViewMixin, generic.CreateView):
model = Ticket
form_class = TicketForm
template_name = 'collector/ticket_create.html'
def get_title(self):
return f'{self.title} - create'
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class UpdateTicket(LoginRequiredMixin, PageTitleViewMixin, generic.UpdateView):
model = Ticket
form_class = TicketForm
template_name = 'collector/ticket_create.html'
slug_field = 'number'
slug_url_kwarg = 'ticket'
def get_title(self, **kwargs):
return f'{self.title} - {self.kwargs.get("ticket", "update")}'
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class ListAllTickets(LoginRequiredMixin, PageTitleViewMixin, generic.ListView):
model = Ticket
template_name = 'collector/tickets.html'
context_object_name = 'tickets'
paginate_by = 5
title = 'Collector - tickets'
def get_queryset(self):
search_query = self.request.GET.get('search', '')
if search_query:
query_list = []
try:
for item in search_query.split(','):
query_list.append(int(item))
except ValueError:
return super().get_queryset()
queryset = self.model.objects.filter(
Q(number__in=query_list) | Q(number__icontains=query_list[0])
)
self.paginate_by = 100 # ? fake disable pagination)
return queryset
return super().get_queryset()
class ListPlatformTickets(
LoginRequiredMixin,
PageTitleViewMixin,
generic.ListView
):
model = Ticket
template_name = 'collector/tickets.html'
context_object_name = 'tickets'
# allow_empty = False
paginate_by = 5
def get_title(self, **kwargs):
return f'{self.title} - {self.kwargs.get("platform", "tickets")}'
def get_queryset(self):
return Ticket.objects.filter(
platform__name=self.kwargs.get('platform')
)
class DetailTicket(LoginRequiredMixin, PageTitleViewMixin, generic.DetailView):
model = Ticket
template_name = 'collector/ticket.html'
context_object_name = 'ticket'
slug_field = 'number'
slug_url_kwarg = 'ticket'
def get_title(self, **kwargs):
return f'{self.title} - {self.kwargs.get("ticket", "show")}'

View File

@ -1,27 +0,0 @@
# Generated by Django 4.2 on 2023-08-05 11:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('collector', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='ticket',
options={'ordering': ['-time_create']},
),
migrations.AlterField(
model_name='archive',
name='size',
field=models.CharField(blank=True, editable=False, max_length=50),
),
migrations.AlterField(
model_name='ticket',
name='number',
field=models.IntegerField(db_index=True, unique=True),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 4.2 on 2023-08-08 05:52
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('collector', '0002_alter_ticket_options_alter_archive_size_and_more'),
]
operations = [
migrations.AlterField(
model_name='archive',
name='ticket',
field=models.ForeignKey(db_column='ticket_number', on_delete=django.db.models.deletion.CASCADE, to='collector.ticket', to_field='number'),
),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 4.2 on 2023-08-08 09:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('collector', '0003_alter_archive_ticket'),
]
operations = [
migrations.RenameField(
model_name='archive',
old_name='sha1',
new_name='md5',
),
migrations.RemoveField(
model_name='archive',
name='size',
),
]

View File

@ -1,33 +0,0 @@
# Generated by Django 4.2 on 2023-08-08 11:16
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('collector', '0004_rename_sha1_archive_md5_remove_archive_size'),
]
operations = [
migrations.CreateModel(
name='Token',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('expires', models.IntegerField(default=5, validators=[django.core.validators.MaxValueValidator(100), django.core.validators.MinValueValidator(1)])),
('blocked', models.BooleanField(default=False)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='archive',
name='token',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='collector.token'),
preserve_default=False,
),
]

View File

@ -1,47 +0,0 @@
# Generated by Django 4.2 on 2023-08-08 16:52
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('collector', '0005_token_archive_token'),
]
operations = [
migrations.RemoveField(
model_name='archive',
name='token',
),
migrations.RemoveField(
model_name='archive',
name='user',
),
migrations.AddField(
model_name='ticket',
name='token',
field=models.UUIDField(default=uuid.uuid4, editable=False),
),
migrations.AddField(
model_name='ticket',
name='upload',
field=models.IntegerField(default=5, validators=[django.core.validators.MaxValueValidator(10), django.core.validators.MinValueValidator(1)]),
),
migrations.AlterField(
model_name='platform',
name='name',
field=models.CharField(max_length=20, unique=True),
),
migrations.AlterField(
model_name='ticket',
name='platform',
field=models.ForeignKey(db_column='platform_name', on_delete=django.db.models.deletion.CASCADE, to='collector.platform', to_field='name'),
),
migrations.DeleteModel(
name='Token',
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2 on 2023-08-08 17:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('collector', '0006_remove_archive_token_remove_archive_user_and_more'),
]
operations = [
migrations.RenameField(
model_name='ticket',
old_name='upload',
new_name='attempts',
),
]

View File

@ -1,25 +0,0 @@
# Generated by Django 4.2 on 2023-08-10 03:24
import django.core.validators
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('collector', '0007_rename_upload_ticket_attempts'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='attempts',
field=models.IntegerField(default=5, validators=[django.core.validators.MaxValueValidator(10), django.core.validators.MinValueValidator(0)]),
),
migrations.AlterField(
model_name='ticket',
name='token',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
]

View File

@ -1,30 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="en" data-bs-theme="auto">
<head>
{% include 'collector/includes/metalinks.html' %}
<title>{% block title %}{% endblock title %}</title>
</head>
<body>
<header>
<section>
{% include 'collector/includes/navigation.html' %}
</section>
</header>
<main>
<section>
{% block main %}{% endblock main %}
</section>
</main>
<footer>
<section>
{% block footer %}{% endblock footer %}
</section>
</footer>
<script src="{% static 'collector/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'collector/js/bs.theme.mode.js' %}"></script>
<script src="{% static 'collector/js/jquery-3.7.0.min.js' %}"></script>
{% block bs %}{% endblock bs %}
{% block jquery %}{% endblock jquery %}
</body>
</html>

View File

@ -1,33 +0,0 @@
{% load static %}
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="{% static 'collector/css/bootstrap.min.css' %}"
rel="stylesheet"
>
<link
rel="apple-touch-icon"
sizes="180x180"
href="{% static 'collector/img/apple-touch-icon.png' %}"
>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="{% static 'collector/img/favicon-32x32.png' %}"
>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="{% static 'collector/img/favicon-16x16.png' %}"
>
<link
rel="manifest"
href="{% static 'collector/img/site.webmanifest' %}"
>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"
>

View File

@ -35,8 +35,8 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'collector.apps.CollectorConfig', # main app
'account.apps.AccountConfig', # account app
'apps.collector.apps.CollectorConfig', # main app
'apps.account.apps.AccountConfig', # account app
'rest_framework',
'rest_framework_simplejwt',
'django_filters',
@ -129,6 +129,9 @@ USE_TZ = True
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / "static",
]
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

View File

@ -11,7 +11,7 @@ from drf_spectacular.views import (
from two_factor.urls import urlpatterns as tf_urls
from logs_collector import settings
from account.utils import AdminSiteOTPRequiredMixinRedirectSetup
from apps.account.utils import AdminSiteOTPRequiredMixinRedirectSetup
# ? 2FA patch (Admin site protection)
@ -19,12 +19,29 @@ admin.site.__class__ = AdminSiteOTPRequiredMixinRedirectSetup
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('collector.urls', namespace='collector')),
path('', include(tf_urls)),
path('', include('account.urls', namespace='account'))
path(
'admin/',
admin.site.urls
),
path(
'',
include('apps.collector.urls', namespace='collector')
),
path(
'',
include(tf_urls)
),
path(
'',
include('apps.account.urls', namespace='account')
),
path(
'api/',
include('apps.collector.api.urls', namespace='collector_api')
),
]
# SWAGGER URLS:
urlpatterns += [
# API PATTERNS
path('api/v1/schema/', SpectacularAPIView.as_view(), name='schema'),

View File

@ -1,836 +0,0 @@
openapi: 3.0.3
info:
title: Logs collector API
version: 0.1.0
description: Collector of archives with log files for further analysis
paths:
/api/schema/:
get:
operationId: schema_retrieve
description: |-
OpenApi3 schema for this API. Format can be selected via content negotiation.
- YAML: application/vnd.oai.openapi
- JSON: application/vnd.oai.openapi+json
parameters:
- in: query
name: format
schema:
type: string
enum:
- json
- yaml
- in: query
name: lang
schema:
type: string
enum:
- af
- ar
- ar-dz
- ast
- az
- be
- bg
- bn
- br
- bs
- ca
- ckb
- cs
- cy
- da
- de
- dsb
- el
- en
- en-au
- en-gb
- eo
- es
- es-ar
- es-co
- es-mx
- es-ni
- es-ve
- et
- eu
- fa
- fi
- fr
- fy
- ga
- gd
- gl
- he
- hi
- hr
- hsb
- hu
- hy
- ia
- id
- ig
- io
- is
- it
- ja
- ka
- kab
- kk
- km
- kn
- ko
- ky
- lb
- lt
- lv
- mk
- ml
- mn
- mr
- ms
- my
- nb
- ne
- nl
- nn
- os
- pa
- pl
- pt
- pt-br
- ro
- ru
- sk
- sl
- sq
- sr
- sr-latn
- sv
- sw
- ta
- te
- tg
- th
- tk
- tr
- tt
- udm
- uk
- ur
- uz
- vi
- zh-hans
- zh-hant
tags:
- schema
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/vnd.oai.openapi:
schema:
type: object
additionalProperties: {}
application/yaml:
schema:
type: object
additionalProperties: {}
application/vnd.oai.openapi+json:
schema:
type: object
additionalProperties: {}
application/json:
schema:
type: object
additionalProperties: {}
description: ''
/api/v1/archives/:
get:
operationId: v1_archives_list
parameters:
- in: query
name: id
schema:
type: integer
- in: query
name: id__gte
schema:
type: integer
- in: query
name: id__in
schema:
type: array
items:
type: integer
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: id__lte
schema:
type: integer
- in: query
name: ticket
schema:
type: integer
- in: query
name: ticket__gte
schema:
type: integer
- in: query
name: ticket__in
schema:
type: array
items:
type: integer
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: ticket__lte
schema:
type: integer
- in: query
name: time_create
schema:
type: string
format: date-time
- in: query
name: time_create__gte
schema:
type: string
format: date-time
- in: query
name: time_create__lte
schema:
type: string
format: date-time
tags:
- v1
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Archive'
description: ''
post:
operationId: v1_archives_create
tags:
- v1
requestBody:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/Archive'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Archive'
required: true
security:
- cookieAuth: []
- basicAuth: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Archive'
description: ''
/api/v1/archives/{id}/:
get:
operationId: v1_archives_retrieve
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this archive.
required: true
tags:
- v1
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Archive'
description: ''
put:
operationId: v1_archives_update
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this archive.
required: true
tags:
- v1
requestBody:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/Archive'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Archive'
required: true
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Archive'
description: ''
patch:
operationId: v1_archives_partial_update
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this archive.
required: true
tags:
- v1
requestBody:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/PatchedArchive'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/PatchedArchive'
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Archive'
description: ''
delete:
operationId: v1_archives_destroy
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this archive.
required: true
tags:
- v1
security:
- cookieAuth: []
- basicAuth: []
responses:
'204':
description: No response body
/api/v1/platforms/:
get:
operationId: v1_platforms_list
tags:
- v1
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Platform'
description: ''
post:
operationId: v1_platforms_create
tags:
- v1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Platform'
text/html:
schema:
$ref: '#/components/schemas/Platform'
multipart/form-data:
schema:
$ref: '#/components/schemas/Platform'
required: true
security:
- cookieAuth: []
- basicAuth: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Platform'
description: ''
/api/v1/platforms/{name}/:
get:
operationId: v1_platforms_retrieve
parameters:
- in: path
name: name
schema:
type: string
required: true
tags:
- v1
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Platform'
description: ''
put:
operationId: v1_platforms_update
parameters:
- in: path
name: name
schema:
type: string
required: true
tags:
- v1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Platform'
text/html:
schema:
$ref: '#/components/schemas/Platform'
multipart/form-data:
schema:
$ref: '#/components/schemas/Platform'
required: true
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Platform'
description: ''
patch:
operationId: v1_platforms_partial_update
parameters:
- in: path
name: name
schema:
type: string
required: true
tags:
- v1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedPlatform'
text/html:
schema:
$ref: '#/components/schemas/PatchedPlatform'
multipart/form-data:
schema:
$ref: '#/components/schemas/PatchedPlatform'
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Platform'
description: ''
delete:
operationId: v1_platforms_destroy
parameters:
- in: path
name: name
schema:
type: string
required: true
tags:
- v1
security:
- cookieAuth: []
- basicAuth: []
responses:
'204':
description: No response body
/api/v1/tickets/:
get:
operationId: v1_tickets_list
parameters:
- in: query
name: id
schema:
type: integer
- in: query
name: id__gte
schema:
type: integer
- in: query
name: id__in
schema:
type: array
items:
type: integer
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: id__lte
schema:
type: integer
- in: query
name: number
schema:
type: integer
- in: query
name: number__contains
schema:
type: integer
- in: query
name: number__gte
schema:
type: integer
- in: query
name: number__in
schema:
type: array
items:
type: integer
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: number__lte
schema:
type: integer
- in: query
name: resolved
schema:
type: boolean
- name: search
required: false
in: query
description: A search term.
schema:
type: string
- in: query
name: user
schema:
type: string
tags:
- v1
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Ticket'
description: ''
post:
operationId: v1_tickets_create
tags:
- v1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Ticket'
text/html:
schema:
$ref: '#/components/schemas/Ticket'
multipart/form-data:
schema:
$ref: '#/components/schemas/Ticket'
required: true
security:
- cookieAuth: []
- basicAuth: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Ticket'
description: ''
/api/v1/tickets/{number}/:
get:
operationId: v1_tickets_retrieve
parameters:
- in: path
name: number
schema:
type: integer
required: true
tags:
- v1
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Ticket'
description: ''
put:
operationId: v1_tickets_update
parameters:
- in: path
name: number
schema:
type: integer
required: true
tags:
- v1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Ticket'
text/html:
schema:
$ref: '#/components/schemas/Ticket'
multipart/form-data:
schema:
$ref: '#/components/schemas/Ticket'
required: true
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Ticket'
description: ''
patch:
operationId: v1_tickets_partial_update
parameters:
- in: path
name: number
schema:
type: integer
required: true
tags:
- v1
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedTicket'
text/html:
schema:
$ref: '#/components/schemas/PatchedTicket'
multipart/form-data:
schema:
$ref: '#/components/schemas/PatchedTicket'
security:
- cookieAuth: []
- basicAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Ticket'
description: ''
delete:
operationId: v1_tickets_destroy
parameters:
- in: path
name: number
schema:
type: integer
required: true
tags:
- v1
security:
- cookieAuth: []
- basicAuth: []
responses:
'204':
description: No response body
components:
schemas:
Archive:
type: object
properties:
id:
type: integer
readOnly: true
file:
type: string
format: uri
nullable: true
ticket:
type: integer
time_create:
type: number
readOnly: true
required:
- id
- ticket
- time_create
PatchedArchive:
type: object
properties:
id:
type: integer
readOnly: true
file:
type: string
format: uri
nullable: true
ticket:
type: integer
time_create:
type: number
readOnly: true
PatchedPlatform:
type: object
properties:
id:
type: integer
readOnly: true
name:
type: string
maxLength: 20
pretty_name:
type: string
maxLength: 20
PatchedTicket:
type: object
properties:
id:
type: integer
readOnly: true
number:
type: integer
resolved:
type: boolean
token:
type: string
format: uuid
readOnly: true
attempts:
type: integer
maximum: 10
minimum: 0
platform:
type: string
time_create:
type: number
readOnly: true
time_update:
type: number
readOnly: true
user:
type: string
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
only.
readOnly: true
Platform:
type: object
properties:
id:
type: integer
readOnly: true
name:
type: string
maxLength: 20
pretty_name:
type: string
maxLength: 20
required:
- id
- name
- pretty_name
Ticket:
type: object
properties:
id:
type: integer
readOnly: true
number:
type: integer
resolved:
type: boolean
token:
type: string
format: uuid
readOnly: true
attempts:
type: integer
maximum: 10
minimum: 0
platform:
type: string
time_create:
type: number
readOnly: true
time_update:
type: number
readOnly: true
user:
type: string
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
only.
readOnly: true
required:
- id
- number
- platform
- time_create
- time_update
- token
- user
securitySchemes:
basicAuth:
type: http
scheme: basic
cookieAuth:
type: apiKey
in: cookie
name: sessionid

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,90 @@
/*!
* Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
*/
;(() => {
"use strict"
const getStoredTheme = () => localStorage.getItem("theme")
const setStoredTheme = (theme) => localStorage.setItem("theme", theme)
const getPreferredTheme = () => {
const storedTheme = getStoredTheme()
if (storedTheme) {
return storedTheme
}
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light"
}
const setTheme = (theme) => {
if (
theme === "auto" &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
document.documentElement.setAttribute("data-bs-theme", "dark")
} else {
document.documentElement.setAttribute("data-bs-theme", theme)
}
}
setTheme(getPreferredTheme())
const showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector("#bd-theme")
if (!themeSwitcher) {
return
}
const themeSwitcherText = document.querySelector("#bd-theme-text")
const activeThemeIcon = document.querySelector(".theme-icon-active use")
const btnToActive = document.querySelector(
`[data-bs-theme-value="${theme}"]`
)
const svgOfActiveBtn = btnToActive
.querySelector("svg use")
.getAttribute("href")
document.querySelectorAll("[data-bs-theme-value]").forEach((element) => {
element.classList.remove("active")
element.setAttribute("aria-pressed", "false")
})
btnToActive.classList.add("active")
btnToActive.setAttribute("aria-pressed", "true")
activeThemeIcon.setAttribute("href", svgOfActiveBtn)
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
themeSwitcher.setAttribute("aria-label", themeSwitcherLabel)
if (focus) {
themeSwitcher.focus()
}
}
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", () => {
const storedTheme = getStoredTheme()
if (storedTheme !== "light" && storedTheme !== "dark") {
setTheme(getPreferredTheme())
}
})
window.addEventListener("DOMContentLoaded", () => {
showActiveTheme(getPreferredTheme())
document.querySelectorAll("[data-bs-theme-value]").forEach((toggle) => {
toggle.addEventListener("click", () => {
const theme = toggle.getAttribute("data-bs-theme-value")
setStoredTheme(theme)
setTheme(theme)
showActiveTheme(theme, true)
})
})
})
})()

View File

@ -0,0 +1,6 @@
const tooltipTriggerList = document.querySelectorAll(
'[data-bs-toggle="tooltip"]'
)
const tooltipList = [...tooltipTriggerList].map(
(tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl)
)

View File

@ -0,0 +1,50 @@
{% load static %}
<!DOCTYPE html>
<html lang="en" data-bs-theme="auto">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="{% static '/css/bootstrap.min.css' %}"
rel="stylesheet"
>
<link
rel="apple-touch-icon"
sizes="180x180"
href="{% static '/img/apple-touch-icon.png' %}"
>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="{% static '/img/favicon-32x32.png' %}"
>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="{% static '/img/favicon-16x16.png' %}"
>
<link
rel="manifest"
href="{% static '/img/site.webmanifest' %}"
>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"
>
{% block collector_head %}{% endblock collector_head %}
{% block account_head %}{% endblock account_head %}
</head>
<body>
{% block collector_content %}{% endblock collector_content %}
{% block account_content %}{% endblock account_content %}
<script src="{% static '/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static '/js/bs.theme.mode.js' %}"></script>
{% block collector_scripts %}{% endblock collector_scripts %}
{% block account_scripts %}{% endblock account_scripts %}
</body>
</html>

View File

@ -97,7 +97,14 @@
<a class="dropdown-item" type="button" href="{% url 'swagger-ui' %}"
><i class="bi bi-braces-asterisk"></i> Swagger</a>
</li>
<li><button class="dropdown-item" type="button"><i class="bi bi-gear"></i> Settings</button></li>
<li>
<a
href="{% url 'two_factor:profile' %}"
class="dropdown-item"
type="button">
<i class="bi bi-gear"></i> Settings
</a>
</li>
<li><hr class="dropdown-divider" /></li>
<li>
<a
@ -130,7 +137,7 @@
<!-- Theme switcher-->
<li class="nav-item dropdown">
<div class="dropdown bd-mode-toggle">
{% include 'collector/includes/theme_swither.html' %}
{% include 'includes/theme_switcher.html' %}
</div>
</li>
</ul>

View File

@ -22,7 +22,7 @@
/>
</symbol>
</svg>
<!--Theme switcher buttons-->
<!-- Theme switcher dropdown buttons -->
<button
class="btn btn-bd-primary py-2 dropdown-toggle d-flex align-items-center"
id="bd-theme"

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,91 +0,0 @@
<!--Theme switcher icons-->
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
<symbol id="check2" viewBox="0 0 16 16">
<path
d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"
/>
</symbol>
<symbol id="circle-half" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z" />
</symbol>
<symbol id="moon-stars-fill" fill="currentColor" viewBox="0 0 16 16">
<path
d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"
/>
<path
d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z"
/>
</symbol>
<symbol id="sun-fill" fill="currentColor" viewBox="0 0 16 16">
<path
d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"
/>
</symbol>
</svg>
<!--Theme switcher buttons-->
<button
class="btn btn-bd-primary py-2 dropdown-toggle d-flex align-items-center"
id="bd-theme"
type="button"
aria-expanded="false"
data-bs-toggle="dropdown"
aria-label="Toggle theme (auto)"
>
<svg class="bi my-1 theme-icon-active" width="1em" height="1em">
<use href="#circle-half"></use>
</svg>
<span class="visually-hidden" id="bd-theme-text">Toggle theme</span>
</button>
<ul
class="dropdown-menu dropdown-menu-end shadow"
aria-labelledby="bd-theme-text"
>
<li>
<button
type="button"
class="dropdown-item d-flex align-items-center"
data-bs-theme-value="light"
aria-pressed="false"
>
<svg class="bi me-2 opacity-50 theme-icon" width="1em" height="1em">
<use href="#sun-fill"></use>
</svg>
Light
<svg class="bi ms-auto d-none" width="1em" height="1em">
<use href="#check2"></use>
</svg>
</button>
</li>
<li>
<button
type="button"
class="dropdown-item d-flex align-items-center"
data-bs-theme-value="dark"
aria-pressed="false"
>
<svg class="bi me-2 opacity-50 theme-icon" width="1em" height="1em">
<use href="#moon-stars-fill"></use>
</svg>
Dark
<svg class="bi ms-auto d-none" width="1em" height="1em">
<use href="#check2"></use>
</svg>
</button>
</li>
<li>
<button
type="button"
class="dropdown-item d-flex align-items-center active"
data-bs-theme-value="auto"
aria-pressed="true"
>
<svg class="bi me-2 opacity-50 theme-icon" width="1em" height="1em">
<use href="#circle-half"></use>
</svg>
Auto
<svg class="bi ms-auto d-none" width="1em" height="1em">
<use href="#check2"></use>
</svg>
</button>
</li>
</ul>

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,42 +1,18 @@
{% extends 'base.html' %}
{% load static %}
<!DOCTYPE html>
<html lang="en" data-bs-theme="auto">
<head>
{% include 'collector/includes/metalinks.html' %}
<title>Collector - {% block title %}{% endblock %}</title>
{% block extra_media %}{% endblock %}
</head>
<body>
<!-- Two factor auth-->
<section>
<header>
{% block 'navigation' %}{% endblock 'navigation' %}
</header>
</section>
<section>
<main>
<div
class="d-flex min-vh-100 align-items-center py-4 bg-body-tertiary"
cz-shortcut-listen="true" >
{% block content_wrapper %}
<div class="container">
<div class="row">{% block content %}{% endblock %}</div>
</div>
{% endblock %}
</div>
</main>
</section>
<!-- Theme switcher-->
<section>
<footer>
<div
class="dropdown position-fixed bottom-0 end-0 mb-3 me-3 bd-mode-toggle"
>
{% include 'collector/includes/theme_swither.html' %}
</div>
</footer>
</section>
<script src="{% static 'collector/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'collector/js/bs.theme.mode.js' %}"></script>
</body>
</html>
{% block account_head %}
<title>Collector - {% block title %}{% endblock %}</title>
{% block extra_media %}{% endblock %}
{% endblock account_head %}
{% block account_content %}
{% block content_wrapper %}
{% block content %}{% endblock %}
{% endblock %}
<section>
<footer>
{% block theme_switcher %}{% endblock %}
</footer>
</section>
{% endblock account_content %}

View File

@ -1,11 +1,22 @@
{% extends "two_factor/_base.html" %}
{% block content_wrapper %}
<div class="container">
<div class="row">
<div class="col-md-4 offset-md-4">
{% block content %}{% endblock %}
<section>
<header>
{% block nav %}{% endblock nav %}
</header>
</section>
<section>
<main>
<div class="container mt-3">
<div class="card">
<div class="card-body">
<div class="text-center">
{% block content %}{% endblock %}
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</section>
{% endblock %}

View File

@ -1,13 +1,15 @@
{% load i18n %}
{% if cancel_url %}
<a href="{{ cancel_url }}"
class="float-right btn btn-link">{% trans "Cancel" %}</a>
<a href="{{ cancel_url }}" class="float-right btn btn-link">{% trans "Cancel" %}</a>
{% endif %}
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit"
value="{{ wizard.steps.prev }}"
class="btn btn-secondary">{% trans "Back" %}</button>
<button
name="wizard_goto_step"
type="submit"
value="{{ wizard.steps.prev }}"
class="btn btn-secondary">{% trans "Back" %}
</button>
{% else %}
<button disabled name="" type="button" class="btn">{% trans "Back" %}</button>
{% endif %}

View File

@ -1,18 +1,23 @@
{% extends "two_factor/_base_focus.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block nav %}{% include 'includes/navigation.html' %}{% endblock nav %}
{% block content %}
<h1>{% block title %}{% trans "Backup Tokens" %}{% endblock %}</h1>
<p>{% blocktrans trimmed %}Backup tokens can be used when your primary and backup
phone numbers aren't available. The backup tokens below can be used
for login verification. If you've used up all your backup tokens, you
can generate a new set of backup tokens. Only the backup tokens shown
below will be valid.{% endblocktrans %}</p>
<h1>{% block title %}{% trans "Backup Tokens" %}{% endblock %}</h1>
<div class="mb-3 d-flex justify-content-center">
<p>{% blocktrans trimmed %}Backup tokens can be used when your primary and backup
phone numbers aren't available. The backup tokens below can be used
for login verification. If you've used up all your backup tokens, you
can generate a new set of backup tokens. Only the backup tokens shown
below will be valid.{% endblocktrans %}</p>
</div>
{% if device.token_set.count %}
<ul>
<ul class="list-group">
{% for token in device.token_set.all %}
<li>{{ token.token }}</li>
<li class="list-group-item list-group-item-action">{{ token.token }}</li>
{% endfor %}
</ul>
<p>{% blocktrans %}Print these tokens and keep them somewhere safe.{% endblocktrans %}</p>
@ -20,9 +25,11 @@
<p>{% trans "You don't have any backup codes yet." %}</p>
{% endif %}
<form method="post">{% csrf_token %}{{ form.as_p }}
<a href="{% url 'two_factor:profile'%}"
class="float-right btn btn-link">{% trans "Back to Account Security" %}</a>
<button class="btn btn-primary" type="submit">{% trans "Generate Tokens" %}</button>
<form method="post">{% csrf_token %}{{ form|crispy }}
<a
href="{% url 'two_factor:profile'%}"
class="float-right btn btn-link">{% trans "Back to Account Security" %}
</a>
<button class="btn btn-outline-primary" type="submit">{% trans "Generate Tokens" %}</button>
</form>
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "two_factor/_base_focus.html" %}
{% extends "two_factor/_base.html" %}
{% load i18n %}
{% load two_factor_tags %}
@ -7,51 +7,65 @@
{% endblock %}
{% block content %}
<h1>{% block title %}{% trans "Login" %}{% endblock %}</h1>
{% if wizard.steps.current == 'auth' %}
<p>{% blocktrans %}Enter your credentials.{% endblocktrans %}</p>
{% elif wizard.steps.current == 'token' %}
<p>{{ device|as_verbose_action }}</p>
{% elif wizard.steps.current == 'backup' %}
<p>{% blocktrans trimmed %}Use this form for entering backup tokens for logging in.
These tokens have been generated for you to print and keep safe. Please
enter one of these backup tokens to login to your account.{% endblocktrans %}</p>
{% endif %}
<form action="" method="post">{% csrf_token %}
{% include "two_factor/_wizard_forms.html" %}
{# hidden submit button to enable [enter] key #}
<input type="submit" value="" hidden />
{% if other_devices %}
<p>{% trans "Or, alternatively, use one of your other authentication methods:" %}</p>
<p>
{% for other in other_devices %}
<button name="challenge_device" value="{{ other.persistent_id }}"
class="btn btn-secondary btn-block" type="submit">
{{ other|as_action }}
</button>
{% endfor %}</p>
{% endif %}
{% include "two_factor/_wizard_actions.html" %}
</form>
{% block 'backup_tokens' %}
{% if backup_tokens %}
<hr>
<div class="backup_tokens_form">
<form action="" method="post">
{% csrf_token %}
<p>{% trans "As a last resort, you can use a backup token:" %}</p>
<div class="d-flex min-vh-100 align-items-center py-4" cz-shortcut-listen="true">
<div class="container">
<div class="row">
<div class="col-md-3 offset-md-4">
<h1>{% block title %}{% trans "Login" %}{% endblock %}</h1>
{% if wizard.steps.current == 'auth' %}
<p>{% blocktrans %}Enter your credentials.{% endblocktrans %}</p>
{% elif wizard.steps.current == 'token' %}
<p>{{ device|as_verbose_action }}</p>
{% elif wizard.steps.current == 'backup' %}
<p>{% blocktrans trimmed %}Use this form for entering backup tokens for logging in.
These tokens have been generated for you to print and keep safe. Please
enter one of these backup tokens to login to your account.{% endblocktrans %}</p>
{% endif %}
<form action="" method="post">{% csrf_token %}
{% include "two_factor/_wizard_forms.html" %}
{# hidden submit button to enable [enter] key #}
<input type="submit" value="" hidden />
{% if other_devices %}
<p>{% trans "Or, alternatively, use one of your other authentication methods:" %}</p>
<p>
<button name="wizard_goto_step" type="submit" value="backup"
class="btn btn-sm btn-secondary btn-block">{% trans "Use Backup Token" %}</button>
</p>
</form>
</div>
{% endif %}
{% endblock %}
{% for other in other_devices %}
<button name="challenge_device" value="{{ other.persistent_id }}"
class="btn btn-secondary btn-block" type="submit">
{{ other|as_action }}
</button>
{% endfor %}</p>
{% endif %}
{% include "two_factor/_wizard_actions.html" %}
</form>
{% block 'backup_tokens' %}
{% if backup_tokens %}
<hr>
<div class="backup_tokens_form">
<form action="" method="post">
{% csrf_token %}
<p>{% trans "As a last resort, you can use a backup token:" %}</p>
<p>
<button name="wizard_goto_step" type="submit" value="backup"
class="btn btn-sm btn-secondary btn-block">{% trans "Use Backup Token" %}</button>
</p>
</form>
</div>
{% endif %}
{% endblock %}
</div>
</div>
</div>
</div>
{% endblock %}
<!-- Theme switcher -->
{% block theme_switcher %}
<div class="dropdown position-fixed bottom-0 end-0 mb-3 me-3 bd-mode-toggle">
{% include 'includes/theme_switcher.html' %}
</div>
{% endblock theme_switcher %}

View File

@ -1,20 +1,35 @@
{% extends "two_factor/_base_focus.html" %}
{% extends "two_factor/_base.html" %}
{% load i18n %}
{% block content %}
<h1>{% block title %}{% trans "Permission Denied" %}{% endblock %}</h1>
<div class="d-flex min-vh-100 align-items-center py-4" cz-shortcut-listen="true">
<div class="container">
<div class="row">
<div class="col-md-4 offset-md-4">
<h1>{% block title %}{% trans "Permission Denied" %}{% endblock %}</h1>
<p>{% blocktrans trimmed %}The page you requested, enforces users to verify using
two-factor authentication for security reasons. You need to enable these
security features in order to access this page.{% endblocktrans %}</p>
<p>{% blocktrans trimmed %}Two-factor authentication is not enabled for your
account. Enable two-factor authentication for enhanced account
security.{% endblocktrans %}</p>
<p>
<a href="javascript:history.go(-1)"
class="float-right btn btn-link">{% trans "Go back" %}</a>
<a href="{% url 'two_factor:setup' %}" class="btn btn-primary">
{% trans "Enable Two-Factor Authentication" %}</a>
</p>
<p>{% blocktrans trimmed %}The page you requested, enforces users to verify using
two-factor authentication for security reasons. You need to enable these
security features in order to access this page.{% endblocktrans %}</p>
<p>{% blocktrans trimmed %}Two-factor authentication is not enabled for your
account. Enable two-factor authentication for enhanced account
security.{% endblocktrans %}</p>
<p>
<a href="javascript:history.go(-1)"
class="float-right btn btn-link">{% trans "Go back" %}</a>
<a href="{% url 'two_factor:setup' %}" class="btn btn-primary">
{% trans "Enable Two-Factor Authentication" %}</a>
</p>
</div>
</div>
</div>
</div>
{% endblock %}
<!-- Theme switcher -->
{% block theme_switcher %}
<div class="dropdown position-fixed bottom-0 end-0 mb-3 me-3 bd-mode-toggle">
{% include 'includes/theme_switcher.html' %}
</div>
{% endblock theme_switcher %}

View File

@ -5,6 +5,8 @@
{{ form.media }}
{% endblock %}
{% block nav %}{% include 'includes/navigation.html' %}{% endblock nav %}
{% block content %}
<h1>{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}</h1>
{% if wizard.steps.current == 'welcome' %}
@ -52,13 +54,14 @@
token in the field below. Your YubiKey will be linked to your
account.{% endblocktrans %}</p>
{% endif %}
<div class="input-group mb-3 d-flex justify-content-center">
<form action="" method="post">{% csrf_token %}
{% include "two_factor/_wizard_forms.html" %}
<form action="" method="post">{% csrf_token %}
{% include "two_factor/_wizard_forms.html" %}
{# hidden submit button to enable [enter] key #}
<input type="submit" value="" hidden />
{# hidden submit button to enable [enter] key #}
<input type="submit" value="" hidden />
{% include "two_factor/_wizard_actions.html" %}
</form>
{% include "two_factor/_wizard_actions.html" %}
</form>
</div>
{% endblock %}

View File

@ -1,6 +1,8 @@
{% extends "two_factor/_base_focus.html" %}
{% load i18n %}
{% block nav %}{% include 'includes/navigation.html' %}{% endblock nav %}
{% block content %}
<h1>{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}</h1>

View File

@ -1,14 +1,19 @@
{% extends "two_factor/_base_focus.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block nav %}{% include 'includes/navigation.html' %}{% endblock nav %}
{% block content %}
<h1>{% block title %}{% trans "Disable Two-factor Authentication" %}{% endblock %}</h1>
<p>{% blocktrans trimmed %}You are about to disable two-factor authentication. This
weakens your account security, are you sure?{% endblocktrans %}</p>
<h1>{% block title %}{% trans "Disable Two-factor Authentication" %}{% endblock %}</h1>
<p>{% blocktrans trimmed %}You are about to disable two-factor authentication. This
weakens your account security, are you sure?{% endblocktrans %}</p>
<div class="input-group mb-3 d-flex justify-content-center">
<form method="post">
{% csrf_token %}
<table>{{ form.as_table }}</table>
<button class="btn btn-danger"
type="submit">{% trans "Disable" %}</button>
<div>{{ form|crispy }}</div>
<button class="btn btn-danger" type="submit">{% trans "Disable" %}</button>
</form>
</div>
{% endblock %}

View File

@ -1,62 +1,59 @@
{% extends "two_factor/_base.html" %}
{% extends "two_factor/_base_focus.html" %}
{% load i18n %}
{% load two_factor_tags %}
{% block 'navigation' %}
{% include 'collector/includes/navigation.html' %}
{% endblock 'navigation' %}
{% block nav %}{% include 'includes/navigation.html' %}{% endblock nav %}
{% block content %}
<h1>{% block title %}{% trans "Account Security" %}{% endblock %}</h1>
<h1>{% block title %}{% trans "Account Security" %}{% endblock %}</h1>
{% if default_device %}
<p>{% blocktrans with primary=default_device|as_action %}Primary method: {{ primary }}{% endblocktrans %}</p>
{% if default_device %}
<p>{% blocktrans with primary=default_device|as_action %}Primary method: {{ primary }}{% endblocktrans %}</p>
{% if available_phone_methods %}
<h2>{% trans "Backup Phone Numbers" %}</h2>
<p>{% blocktrans trimmed %}If your primary method is not available, we are able to
send backup tokens to the phone numbers listed below.{% endblocktrans %}</p>
<ul>
{% for phone in backup_phones %}
<li>
{{ phone|as_action }}
<form method="post" action="{% url 'two_factor:phone_delete' phone.id %}"
onsubmit="return confirm({% trans 'Are you sure?' %})">
{% csrf_token %}
<button class="btn btn-sm btn-warning"
type="submit">{% trans "Unregister" %}</button>
</form>
</li>
{% endfor %}
</ul>
<p><a href="{% url 'two_factor:phone_create' %}"
class="btn btn-info">{% trans "Add Phone Number" %}</a></p>
{% endif %}
<h2>{% trans "Backup Tokens" %}</h2>
<p>
{% blocktrans trimmed %}If you don't have any device with you, you can access
your account using backup tokens.{% endblocktrans %}
{% blocktrans trimmed count counter=backup_tokens %}
You have only one backup token remaining.
{% plural %}
You have {{ counter }} backup tokens remaining.
{% endblocktrans %}
</p>
<p><a href="{% url 'two_factor:backup_tokens' %}"
class="btn btn-info">{% trans "Show Codes" %}</a></p>
{% if available_phone_methods %}
<h2>{% trans "Backup Phone Numbers" %}</h2>
<p>{% blocktrans trimmed %}If your primary method is not available, we are able to
send backup tokens to the phone numbers listed below.{% endblocktrans %}</p>
<ul>
{% for phone in backup_phones %}
<li>
{{ phone|as_action }}
<form method="post" action="{% url 'two_factor:phone_delete' phone.id %}"
onsubmit="return confirm({% trans 'Are you sure?' %})">
{% csrf_token %}
<button class="btn btn-sm btn-warning"
type="submit">{% trans "Unregister" %}</button>
</form>
</li>
{% endfor %}
</ul>
<p><a href="{% url 'two_factor:phone_create' %}"
class="btn btn-info">{% trans "Add Phone Number" %}</a></p>
{% endif %}
<h2>{% trans "Backup Tokens" %}</h2>
<p>
{% blocktrans trimmed %}If you don't have any device with you, you can access
your account using backup tokens.{% endblocktrans %}
{% blocktrans trimmed count counter=backup_tokens %}
You have only one backup token remaining.
{% plural %}
You have {{ counter }} backup tokens remaining.
{% endblocktrans %}
</p>
<p><a href="{% url 'two_factor:backup_tokens' %}"
class="btn btn-info">{% trans "Show Codes" %}</a></p>
<h3>{% trans "Disable Two-Factor Authentication" %}</h3>
<p>{% blocktrans trimmed %}However we strongly discourage you to do so, you can
also disable two-factor authentication for your account.{% endblocktrans %}</p>
<p><a class="btn btn-secondary" href="{% url 'two_factor:disable' %}">
{% trans "Disable Two-Factor Authentication" %}</a></p>
{% else %}
<p>{% blocktrans trimmed %}Two-factor authentication is not enabled for your
account. Enable two-factor authentication for enhanced account
security.{% endblocktrans %}</p>
<p><a href="{% url 'two_factor:setup' %}" class="btn btn-primary">
{% trans "Enable Two-Factor Authentication" %}</a>
</p>
{% endif %}
<h3>{% trans "Disable Two-Factor Authentication" %}</h3>
<p>{% blocktrans trimmed %}However we strongly discourage you to do so, you can
also disable two-factor authentication for your account.{% endblocktrans %}</p>
<p><a class="btn btn-secondary" href="{% url 'two_factor:disable' %}">
{% trans "Disable Two-Factor Authentication" %}</a></p>
{% else %}
<p>{% blocktrans trimmed %}Two-factor authentication is not enabled for your
account. Enable two-factor authentication for enhanced account
security.{% endblocktrans %}</p>
<p><a href="{% url 'two_factor:setup' %}" class="btn btn-primary">
{% trans "Enable Two-Factor Authentication" %}</a>
</p>
{% endif %}
{% endblock %}