diff --git a/logs_collector/collector/api/serializers.py b/logs_collector/collector/api/serializers.py index 064bb83..b1dc0bc 100644 --- a/logs_collector/collector/api/serializers.py +++ b/logs_collector/collector/api/serializers.py @@ -19,6 +19,7 @@ class JsTimestampField(serializers.Field): class PublicArchiveUploadSerializer(serializers.ModelSerializer): + class Meta: model = Archive fields = ['file', 'ticket'] diff --git a/logs_collector/collector/api/views.py b/logs_collector/collector/api/views.py index bd78c74..04ebbdd 100644 --- a/logs_collector/collector/api/views.py +++ b/logs_collector/collector/api/views.py @@ -10,6 +10,9 @@ from rest_framework import filters from django_filters.rest_framework import DjangoFilterBackend +from drf_spectacular.utils import extend_schema +from drf_spectacular.openapi import OpenApiParameter + from collector.models import Archive, Ticket, Platform from .filters import ArchiveFilter, TicketFilter @@ -30,15 +33,37 @@ class ArchiveViewSet(viewsets.ModelViewSet): filter_backends = [DjangoFilterBackend] filterset_class = ArchiveFilter + @extend_schema( + operation_id='upload_file', + request={ + 'multipart/form-data': { + 'type': 'object', + 'properties': { + 'file': { + 'type': 'string', + 'format': 'binary' + } + } + } + }, + parameters=[ + OpenApiParameter( + name='Upload-Token', + type=str, + location=OpenApiParameter.HEADER, + description="upload permission token", + ), + ] + ) def create(self, request, *args, **kwargs): # ! upload-token protection: upload_token = request.headers.get('upload-token', '') - if not request.user.is_authenticated and upload_token: + if upload_token: try: bound_ticket = Ticket.objects.get(token=upload_token) if bound_ticket.resolved: return Response( - {'error': f'ticket {upload_token} already resolved'}, + {'error': f'ticket {bound_ticket} already resolved'}, status=status.HTTP_423_LOCKED ) if bound_ticket.attempts <= 0: @@ -51,13 +76,14 @@ class ArchiveViewSet(viewsets.ModelViewSet): # ? mixin bound ticket number to request.data from user request.data['ticket'] = bound_ticket.number # ? change serializer for guest user - self.serializer_class = PublicArchiveUploadSerializer + if not request.user.is_authenticated: + self.serializer_class = PublicArchiveUploadSerializer except (ValidationError, ObjectDoesNotExist,): return Response( {'error': f'token {upload_token} is not valid'}, status=status.HTTP_403_FORBIDDEN ) - elif not request.user.is_authenticated: + else: return Response( {'error': 'Header Upload-Token is required'}, status=status.HTTP_401_UNAUTHORIZED diff --git a/logs_collector/collector/forms.py b/logs_collector/collector/forms.py index b2b0cd6..a608224 100644 --- a/logs_collector/collector/forms.py +++ b/logs_collector/collector/forms.py @@ -3,7 +3,7 @@ from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Submit, Div from crispy_bootstrap5.bootstrap5 import FloatingField -from .models import Ticket +from .models import Ticket, Archive class TicketForm(forms.ModelForm): @@ -29,3 +29,25 @@ class TicketForm(forms.ModelForm): Div('note', css_class='col-lg-6'), Submit('submit', 'Save', css_class='btn btn-primary'), ) + + +class ArchiveForm(forms.ModelForm): + token = forms.UUIDField(required=True) + + class Meta: + model = Archive + fields = ['token', 'file'] + + def __init__(self, *args, **kwargs): + super(ArchiveForm, self).__init__(*args, **kwargs) + self.helper = FormHelper(self) + self.helper.form_id = 'upload_form' + + self.helper.layout = Layout( + Div( + FloatingField('token'), + 'file', + css_class='col-lg-6' + ), + Submit('submit', 'Upload', css_class='btn btn-primary'), + ) diff --git a/logs_collector/collector/static/collector/js/jq.upload.progress.js b/logs_collector/collector/static/collector/js/jq.upload.progress.js new file mode 100644 index 0000000..eea0bd8 --- /dev/null +++ b/logs_collector/collector/static/collector/js/jq.upload.progress.js @@ -0,0 +1,80 @@ +$(function () { + const uploadForm = document.getElementById('upload_form'); + const input_file = document.getElementById('id_file'); + const progress_bar = document.getElementById('progress'); + const alert_container = document.getElementById('alert'); + + $("#upload_form").submit(function(e){ + e.preventDefault(); + $form = $(this) + let formData = new FormData(this); + let upload_token = formData.get("token") + const media_data = input_file.files[0]; + if(media_data != null){ + progress_bar.classList.remove("not-visible"); + } + $.ajax({ + type: 'POST', + url: progress_bar.getAttribute("upload-url"), + data: formData, + dataType: 'json', + xhr:function(){ + const xhr = new window.XMLHttpRequest(); + xhr.upload.addEventListener('progress', e=>{ + if(e.lengthComputable){ + const percentProgress = (e.loaded/e.total)*100; + console.log(percentProgress); + progress_bar.innerHTML = ` +
` + } + }); + return xhr + }, + beforeSend: function(xhr) { + if (upload_token) { + xhr.setRequestHeader("Upload-Token", upload_token); + } + }, + success: function(data, textStatus, jqXHR){ + console.log(jqXHR.status); + let type = "success"; + alert_container.innerHTML = [ + `