diff --git a/logs_collector/collector/admin.py b/logs_collector/collector/admin.py
index e205677..c5dc23a 100644
--- a/logs_collector/collector/admin.py
+++ b/logs_collector/collector/admin.py
@@ -5,7 +5,7 @@ from django.utils.html import format_html
from django.utils.translation import ngettext
from .models import Platform, Archive, Ticket
-from .utils import sizify
+from .utils.helpers import sizify
class PlatformAdmin(admin.ModelAdmin):
diff --git a/logs_collector/collector/api/urls.py b/logs_collector/collector/api/urls.py
index ab94f7b..1a34d27 100644
--- a/logs_collector/collector/api/urls.py
+++ b/logs_collector/collector/api/urls.py
@@ -18,4 +18,5 @@ router.register(r'tickets', views.TicketViewSet)
urlpatterns = [
# CRUD:
path('v1/', include(router.urls)),
+ path('v1/storage/', views.StorageInfo.as_view(), name='storage-info'),
]
diff --git a/logs_collector/collector/api/views.py b/logs_collector/collector/api/views.py
index 857b96b..00b5ca7 100644
--- a/logs_collector/collector/api/views.py
+++ b/logs_collector/collector/api/views.py
@@ -1,4 +1,5 @@
from django.core.exceptions import ValidationError, ObjectDoesNotExist
+from django.conf import settings
from rest_framework import status
# from rest_framework.decorators import action
@@ -10,6 +11,7 @@ from rest_framework.parsers import (
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import viewsets
+from rest_framework import views
from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
@@ -18,6 +20,7 @@ from drf_spectacular.utils import extend_schema
from drf_spectacular.openapi import OpenApiParameter
from collector.models import Archive, Ticket, Platform
+from collector.utils.helpers import get_mount_fs_info
from .filters import ArchiveFilter, TicketFilter
from .permissions import IsGuestUpload
@@ -122,3 +125,10 @@ class TicketViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer):
serializer.save(user=self.request.user)
+
+
+class StorageInfo(views.APIView):
+ """Info about storage total/used/free space"""
+
+ def get(self, request):
+ return Response(get_mount_fs_info(settings.MEDIA_ROOT))
diff --git a/logs_collector/collector/context_processors.py b/logs_collector/collector/context_processors.py
new file mode 100644
index 0000000..7160cf6
--- /dev/null
+++ b/logs_collector/collector/context_processors.py
@@ -0,0 +1,14 @@
+from django.conf import settings
+
+from .utils.helpers import get_mount_fs_info
+
+
+def metadata(request):
+ return {
+ "version": settings.VERSION,
+ "environment": settings.ENVIRONMENT,
+ }
+
+
+def storage_info(request):
+ return get_mount_fs_info(settings.MEDIA_ROOT)
diff --git a/logs_collector/collector/migrations/0001_initial.py b/logs_collector/collector/migrations/0001_initial.py
index fce79fc..bc56299 100644
--- a/logs_collector/collector/migrations/0001_initial.py
+++ b/logs_collector/collector/migrations/0001_initial.py
@@ -47,7 +47,7 @@ class Migration(migrations.Migration):
name='Archive',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('file', models.FileField(upload_to=collector.utils.logs_dir_path)),
+ ('file', models.FileField(upload_to=collector.utils.helpers.logs_dir_path)),
('size', models.BigIntegerField(editable=False)),
('md5', models.CharField(editable=False, max_length=1024)),
('time_create', models.DateTimeField(auto_now_add=True)),
diff --git a/logs_collector/collector/models.py b/logs_collector/collector/models.py
index 2296cbd..063351a 100644
--- a/logs_collector/collector/models.py
+++ b/logs_collector/collector/models.py
@@ -6,7 +6,7 @@ from django.contrib.auth.models import User
from django.db import models
from django.urls import reverse
-from .utils import logs_dir_path
+from .utils.helpers import logs_dir_path
class Archive(models.Model):
diff --git a/logs_collector/collector/static/collector/js/helpers.js b/logs_collector/collector/static/collector/js/helpers.js
new file mode 100644
index 0000000..f48ed67
--- /dev/null
+++ b/logs_collector/collector/static/collector/js/helpers.js
@@ -0,0 +1,69 @@
+// formatted byte size to human readable:
+const sizify = (value) => {
+ let ext = ''
+ if (value < 512000) {
+ value = value / 1024.0
+ ext = 'KB'
+ } else if (value < 4194304000) {
+ value = value / 1048576.0
+ ext = 'MB'
+ } else {
+ value = value / 1073741824.0
+ ext = 'GB'
+ };
+ return `${Math.round(value * 10) / 10} ${ext}`
+};
+
+// fix update bootstrap tooltip func:
+const updateBsTooltip = (instance) => {
+ let tt = bootstrap.Tooltip.getInstance(instance);
+ tt.dispose();
+ bootstrap.Tooltip.getOrCreateInstance(instance);
+};
+
+// update storage info widget:
+const updateStorageInfo = () => {
+ // set storage items vars:
+ let storageIcon = $("#storage_icon")
+ let storageProgressContainer = $("#storage_progress_container")
+ let storage_progress = $("#storage_progress")
+ // set API url:
+ const storageUrl = storage_progress.attr("storage-url")
+ $.ajax({
+ type: "GET",
+ url: storageUrl,
+ headers: {
+ "Content-Type":"application/json"
+ },
+ dataType: "json",
+ success: function (data, textStatus, jqXHR) {
+ // JSON answer:
+ let storage = data.storage
+ // set updated fields:
+ let storageInfoNewFields = [
+ `Total: ${sizify(storage.total)}`,
+ '
',
+ `Used: ${sizify(storage.used)}`,
+ '
',
+ `Free: ${sizify(storage.free)}`
+ ].join('')
+ // progress bar update:
+ storage_progress.attr("style", `width:${storage.used_percent}%`)
+ // progress bar color update:
+ if (storage.used_percent > 90) {
+ storage_progress.attr("class", "progress-bar bg-danger");
+ } else if (storage.used_percent > 80) {
+ storage_progress.attr("class", "progress-bar bg-warning");
+ } else {
+ storage_progress.attr("class", "progress-bar bg-success");
+ };
+ // tooltips update:
+ storageIcon.attr("data-bs-title", `Storage used: ${storage.used_percent}%`)
+ storageProgressContainer.attr("data-bs-title", storageInfoNewFields)
+ updateBsTooltip(storageIcon)
+ updateBsTooltip(storageProgressContainer)
+ }
+ });
+};
+
+export {sizify, updateBsTooltip, updateStorageInfo};
diff --git a/logs_collector/collector/static/collector/js/jq.ticket.detail.js b/logs_collector/collector/static/collector/js/jq.ticket.detail.js
index 10575fd..2cdc11b 100644
--- a/logs_collector/collector/static/collector/js/jq.ticket.detail.js
+++ b/logs_collector/collector/static/collector/js/jq.ticket.detail.js
@@ -1,3 +1,6 @@
+import {updateStorageInfo} from "./helpers.js";
+
+
$(function () {
console.log("JQ is ready to work");
@@ -24,8 +27,11 @@ $(function () {
success: function (data, textStatus, jqXHR) {
console.log(jqXHR.status);
$(archiveListElement).hide(1500);
+ setTimeout(() => {
+ updateStorageInfo();
+ }, 3000);
},
- error: function (data, textStatus, jqXHR) {
+ error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.status);
}
});
@@ -57,7 +63,7 @@ $(function () {
success: function (data, textStatus, jqXHR) {
console.log(jqXHR.status)
},
- error: function (data, textStatus, jqXHR) {
+ error: function (jqXHR, textStatus, errorThrown) {
console.log(data)
console.log(jqXHR.status)
}
@@ -82,11 +88,14 @@ $(function () {
console.log(jqXHR.status);
if (delDiv.length) {
delDiv.hide(1500);
+ setTimeout(() => {
+ updateStorageInfo();
+ }, 3000);
} else {
window.location.href = redirectUrl;
}
},
- error: function (data, textStatus, jqXHR) {
+ error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.status);
}
});
diff --git a/logs_collector/collector/static/collector/js/jq.upload.progress.js b/logs_collector/collector/static/collector/js/jq.upload.progress.js
index c4e3a21..45a324a 100644
--- a/logs_collector/collector/static/collector/js/jq.upload.progress.js
+++ b/logs_collector/collector/static/collector/js/jq.upload.progress.js
@@ -1,3 +1,5 @@
+import {updateStorageInfo} from "./helpers.js";
+
$(function () {
const uploadForm = document.getElementById('upload_form');
const input_file = document.getElementById('id_file');
@@ -6,7 +8,7 @@ $(function () {
$("#upload_form").submit(function(e){
e.preventDefault();
- $form = $(this)
+ // $form = $(this)
let formData = new FormData(this);
let upload_token = formData.get("token")
const media_data = input_file.files[0];
@@ -51,6 +53,11 @@ $(function () {
].join('')
uploadForm.reset()
progress_bar.classList.add('not-visible')
+ try {
+ updateStorageInfo();
+ } catch (error) {
+ console.log(error)
+ };
},
error: function(jqXHR, textStatus, errorThrown){
console.log(jqXHR);
diff --git a/logs_collector/collector/templates/collector/archive_upload.html b/logs_collector/collector/templates/collector/archive_upload.html
index ddad5f0..b97ce01 100644
--- a/logs_collector/collector/templates/collector/archive_upload.html
+++ b/logs_collector/collector/templates/collector/archive_upload.html
@@ -51,5 +51,5 @@
{% endblock main %}
{% block jquery %}
-
+
{% endblock jquery %}
diff --git a/logs_collector/collector/templates/collector/storage.html b/logs_collector/collector/templates/collector/storage.html
new file mode 100644
index 0000000..5f62143
--- /dev/null
+++ b/logs_collector/collector/templates/collector/storage.html
@@ -0,0 +1,39 @@
+
+
+
+
+
diff --git a/logs_collector/collector/templates/collector/ticket.html b/logs_collector/collector/templates/collector/ticket.html
index 1ba6158..366552a 100644
--- a/logs_collector/collector/templates/collector/ticket.html
+++ b/logs_collector/collector/templates/collector/ticket.html
@@ -56,5 +56,5 @@
{% endblock main %}
{% block jquery %}
-
+
{% endblock jquery %}
diff --git a/logs_collector/collector/templates/collector/tickets.html b/logs_collector/collector/templates/collector/tickets.html
index 5392c93..28cab7c 100644
--- a/logs_collector/collector/templates/collector/tickets.html
+++ b/logs_collector/collector/templates/collector/tickets.html
@@ -86,9 +86,6 @@
{% include 'collector/includes/pagination.html' %}
{% endblock main %}
-{% block bs %}
-
-{% endblock bs %}
{% block jquery %}
-
+
{% endblock jquery %}
diff --git a/logs_collector/collector/utils.py b/logs_collector/collector/utils.py
deleted file mode 100644
index 0c61f31..0000000
--- a/logs_collector/collector/utils.py
+++ /dev/null
@@ -1,43 +0,0 @@
-def logs_dir_path(instance, filename):
- """
- file will be uploaded to
- MEDIA_ROOT_FOR_SENSITIVE_FILES//
- """
- return f'{instance.ticket.number}/{filename}'
-
-
-def sizify(value: int) -> str:
- """Simple kb/mb/gb size snippet for admin panel custom field:
-
- Args:
- value (int): size of file from Filefield
-
- Returns:
- str: format human readable size like 4.2 Gb
- """
- if value < 512000:
- value = value / 1024.0
- ext = 'Kb'
- elif value < 4194304000:
- value = value / 1048576.0
- ext = 'Mb'
- else:
- value = value / 1073741824.0
- ext = 'Gb'
- return f'{round(value, 2)} {ext}'
-
-
-class PageTitleViewMixin:
- title = 'Collector'
-
- def get_title(self, *args, **kwargs):
- """
- Return the class title attr by default,
- but you can override this method to further customize
- """
- return self.title
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['title'] = self.get_title()
- return context
diff --git a/logs_collector/collector/utils/__init__.py b/logs_collector/collector/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/logs_collector/collector/utils/helpers.py b/logs_collector/collector/utils/helpers.py
new file mode 100644
index 0000000..bff3ce6
--- /dev/null
+++ b/logs_collector/collector/utils/helpers.py
@@ -0,0 +1,38 @@
+import shutil
+
+
+def logs_dir_path(instance, filename):
+ """
+ file will be uploaded to
+ MEDIA_ROOT/view/
+ """
+ return f'{instance.ticket.number}/{filename}'
+
+
+def sizify(value: int) -> str:
+ """Simple kb/mb/gb size snippet for admin panel custom field:
+
+ Args:
+ value (int): size of file from Filefield
+
+ Returns:
+ str: format human readable size like 4.2 Gb
+ """
+ if value < 512000:
+ value = value / 1024.0
+ ext = 'KB'
+ elif value < 4194304000:
+ value = value / 1048576.0
+ ext = 'MB'
+ else:
+ value = value / 1073741824.0
+ ext = 'GB'
+ return f'{round(value, 1)} {ext}'
+
+
+def get_mount_fs_info(path):
+ mount_info = shutil.disk_usage(path)._asdict()
+ mount_info['used_percent'] = round(
+ mount_info['used'] / mount_info['total'] * 100
+ )
+ return {'storage': mount_info}
diff --git a/logs_collector/collector/utils/mixins.py b/logs_collector/collector/utils/mixins.py
new file mode 100644
index 0000000..674c37f
--- /dev/null
+++ b/logs_collector/collector/utils/mixins.py
@@ -0,0 +1,23 @@
+class ExtraContextMixin:
+ """The class adds additional context
+ to all child view classes that inherit from it.
+ Overrides the get_context_data method for CBV
+ """
+
+ title = 'Collector'
+
+ def get_title(self, *args, **kwargs):
+ """
+ Return the class title attr by default,
+ but you can override this method to further customize
+ """
+ return self.title
+
+ def get_context_data(self, **kwargs):
+ context = {}
+ try:
+ context = super().get_context_data(**kwargs)
+ except Exception:
+ pass
+ context['title'] = self.get_title()
+ return context
diff --git a/logs_collector/collector/views.py b/logs_collector/collector/views.py
index 5377061..db7dd8d 100644
--- a/logs_collector/collector/views.py
+++ b/logs_collector/collector/views.py
@@ -3,25 +3,22 @@ from django.http import FileResponse
from django.views import generic
from django.views.generic.detail import SingleObjectMixin
from django.db.models import Q
-from django.shortcuts import render
from two_factor.views import OTPRequiredMixin
from .forms import TicketForm, ArchiveForm
from .models import Archive, Ticket
-from .utils import PageTitleViewMixin
+from .utils.mixins import ExtraContextMixin
-class ArchiveUploadView(PageTitleViewMixin, generic.View):
+class ArchiveUploadView(ExtraContextMixin, generic.TemplateView):
form_class = ArchiveForm()
- template = 'collector/archive_upload.html',
+ template_name = 'collector/archive_upload.html'
- def get(self, request):
- return render(
- request,
- self.template,
- context={'form': self.form_class}
- )
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['form'] = self.form_class
+ return context
def get_title(self):
return f'{self.title} - upload'
@@ -41,7 +38,7 @@ class ArchiveHandlerView(
return FileResponse(self.object.file)
-class CreateTicket(LoginRequiredMixin, PageTitleViewMixin, generic.CreateView):
+class CreateTicket(LoginRequiredMixin, ExtraContextMixin, generic.CreateView):
model = Ticket
form_class = TicketForm
template_name = 'collector/ticket_create.html'
@@ -54,7 +51,7 @@ class CreateTicket(LoginRequiredMixin, PageTitleViewMixin, generic.CreateView):
return super().form_valid(form)
-class UpdateTicket(LoginRequiredMixin, PageTitleViewMixin, generic.UpdateView):
+class UpdateTicket(LoginRequiredMixin, ExtraContextMixin, generic.UpdateView):
model = Ticket
form_class = TicketForm
template_name = 'collector/ticket_create.html'
@@ -69,7 +66,7 @@ class UpdateTicket(LoginRequiredMixin, PageTitleViewMixin, generic.UpdateView):
return super().form_valid(form)
-class ListAllTickets(LoginRequiredMixin, PageTitleViewMixin, generic.ListView):
+class ListAllTickets(LoginRequiredMixin, ExtraContextMixin, generic.ListView):
model = Ticket
template_name = 'collector/tickets.html'
context_object_name = 'tickets'
@@ -98,7 +95,7 @@ class ListAllTickets(LoginRequiredMixin, PageTitleViewMixin, generic.ListView):
return super().get_queryset()
-class ListPlatformTickets(LoginRequiredMixin, PageTitleViewMixin, generic.ListView): # noqa:E501
+class ListPlatformTickets(LoginRequiredMixin, ExtraContextMixin, generic.ListView): # noqa:E501
model = Ticket
template_name = 'collector/tickets.html'
context_object_name = 'tickets'
@@ -114,7 +111,7 @@ class ListPlatformTickets(LoginRequiredMixin, PageTitleViewMixin, generic.ListVi
)
-class DetailTicket(LoginRequiredMixin, PageTitleViewMixin, generic.DetailView):
+class DetailTicket(LoginRequiredMixin, ExtraContextMixin, generic.DetailView):
model = Ticket
template_name = 'collector/ticket.html'
context_object_name = 'ticket'
diff --git a/logs_collector/logs_collector/__init__.py b/logs_collector/logs_collector/__init__.py
index e69de29..a95ca20 100644
--- a/logs_collector/logs_collector/__init__.py
+++ b/logs_collector/logs_collector/__init__.py
@@ -0,0 +1,29 @@
+"""
+An application for uploading archives with log files
+for their subsequent download and check issues
+that have arisen with software products.
+The purpose of creating this application is
+the ability to securely exchange and store log files containing sensitive data.
+I have not found an application that would allow an unauthorized client
+to upload data without providing him with authorization credentials.
+You can use other applications for this,
+such as Google cloud, Yandex cloud, DropBox etc, but in this case,
+you do not have a tool that would allow you to automatically restrict uploads
+later until you explicitly deny access to the shared link.
+This app allows you to upload files using a unique token
+associated with a support ticket.
+This token has a limit on the number of file upload attempts.
+Also, if the ticket is resolved, then the token is invalid.
+"""
+
+
+# █▀▄▀█ █▀▀ ▀█▀ ▄▀█ ▀
+# █░▀░█ ██▄ ░█░ █▀█ ▄
+# -------------------
+__author__ = "MOIS3Y"
+__credits__ = ["Stepan Zhukovsky"]
+__license__ = "GPL v3.0"
+__version__ = "0.1.0"
+__maintainer__ = "Stepan Zhukovsky"
+__email__ = "stepan@zhukovsky.me"
+__status__ = "Development"
diff --git a/logs_collector/logs_collector/settings.py b/logs_collector/logs_collector/settings.py
index 9c72247..ba90fee 100644
--- a/logs_collector/logs_collector/settings.py
+++ b/logs_collector/logs_collector/settings.py
@@ -2,6 +2,7 @@ import environ
from pathlib import Path
from datetime import timedelta
+from . import __version__
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -9,6 +10,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# Set default environ variables:
env = environ.Env(
# set casting default value
+ VERSION=(str, __version__),
+ ENVIRONMENT=(str, 'development'),
DEBUG=(bool, False),
SECRET_KEY=(str, 'j9QGbvM9Z4otb47'),
SQLITE_URL=(str, f'sqlite:///{BASE_DIR / "data/db.sqlite3"}'),
@@ -20,6 +23,9 @@ env = environ.Env(
# Read .env file if exist:
environ.Env.read_env(BASE_DIR / '.env')
+VERSION = env('VERSION')
+ENVIRONMENT = env('ENVIRONMENT')
+
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY')
@@ -78,10 +84,14 @@ TEMPLATES = [
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
+ # default:
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
+ # collector:
+ 'collector.context_processors.metadata',
+ 'collector.context_processors.storage_info',
],
},
},
@@ -174,6 +184,7 @@ REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', # noqa:E501
# 'PAGE_SIZE': 3,
+ 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
}
if DEBUG:
diff --git a/logs_collector/collector/static/collector/js/bs.tooltip.js b/logs_collector/static/js/bs.tooltip.js
similarity index 100%
rename from logs_collector/collector/static/collector/js/bs.tooltip.js
rename to logs_collector/static/js/bs.tooltip.js
diff --git a/logs_collector/templates/base.html b/logs_collector/templates/base.html
index 0b60a93..bf2f9de 100644
--- a/logs_collector/templates/base.html
+++ b/logs_collector/templates/base.html
@@ -40,9 +40,12 @@
{% block collector_content %}{% endblock collector_content %}
{% block account_content %}{% endblock account_content %}
-
+
+
+
+
{% block collector_scripts %}{% endblock collector_scripts %}
{% block account_scripts %}{% endblock account_scripts %}
diff --git a/logs_collector/templates/includes/brand.html b/logs_collector/templates/includes/brand.html
new file mode 100644
index 0000000..0f5cbaf
--- /dev/null
+++ b/logs_collector/templates/includes/brand.html
@@ -0,0 +1,28 @@
+
+ Logs Collector
+
+
+
diff --git a/logs_collector/templates/includes/extra_menu.html b/logs_collector/templates/includes/extra_menu.html
new file mode 100644
index 0000000..323a81f
--- /dev/null
+++ b/logs_collector/templates/includes/extra_menu.html
@@ -0,0 +1,61 @@
+{% if request.user.is_authenticated %}
+
+
+
+
+
+
+
+{% else %}
+
+
+
+{% endif %}
diff --git a/logs_collector/templates/includes/menu.html b/logs_collector/templates/includes/menu.html
new file mode 100644
index 0000000..d3385e1
--- /dev/null
+++ b/logs_collector/templates/includes/menu.html
@@ -0,0 +1,47 @@
+{% load collector_extras %}
+{% get_platforms as platforms %}
+
+
+
+
\ No newline at end of file
diff --git a/logs_collector/templates/includes/navigation.html b/logs_collector/templates/includes/navigation.html
index e2899d4..df97515 100644
--- a/logs_collector/templates/includes/navigation.html
+++ b/logs_collector/templates/includes/navigation.html
@@ -1,157 +1,22 @@
-{% load collector_extras %}
-{% get_platforms as platforms %}