From a7a85b1816458316c5f6bb20d48633a10c67c353 Mon Sep 17 00:00:00 2001 From: MOIS3Y Date: Wed, 13 Sep 2023 19:22:37 +0900 Subject: [PATCH] Modify: archive upload js now it check token status, HEALTHCHEK call health api endpoint --- Dockerfile | 5 +- logs_collector/collector/api/permissions.py | 12 ++ logs_collector/collector/api/serializers.py | 22 ++ logs_collector/collector/api/urls.py | 25 ++- logs_collector/collector/api/views.py | 46 +++- .../collector/static/collector/js/helpers.js | 16 +- .../static/collector/js/jq.upload.progress.js | 197 +++++++++++------- .../templates/collector/archive_upload.html | 3 +- 8 files changed, 244 insertions(+), 82 deletions(-) diff --git a/Dockerfile b/Dockerfile index d6527ce..680d0bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,8 @@ ARG VERSION=0.1.0 \ USER_NAME=collector \ USER_GROUP=collector \ APP_UID=1000 \ - APP_GID=1000 + APP_GID=1000 \ + HEALTHCHECK_PATH=api/v1/check/health/ # copy app dependences COPY --from=base /usr/local/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/ @@ -61,7 +62,7 @@ LABEL maintainer="s.zhukovskii@ispsystem.com" LABEL me.zhukovsky.logs-collector.version=v${VERSION} # call the health check endpoint of app -HEALTHCHECK CMD curl --fail http://localhost:${WEB_PORT} || exit 1 +HEALTHCHECK CMD curl --fail http://localhost:${WEB_PORT}/${HEALTHCHECK_PATH} || exit 1 # run app ENTRYPOINT [ "sh", "entrypoint.sh" ] diff --git a/logs_collector/collector/api/permissions.py b/logs_collector/collector/api/permissions.py index 7248552..cb2d3bb 100644 --- a/logs_collector/collector/api/permissions.py +++ b/logs_collector/collector/api/permissions.py @@ -11,3 +11,15 @@ class IsGuestUpload(permissions.BasePermission): return True return request.user.is_authenticated + + +class IsGuestCheckUrls(permissions.BasePermission): + """ + Special permission class for the ability to upload attachments + to an unauthorized user using a ticket token + """ + def has_permission(self, request, view): + if request.method in ('HEAD', 'OPTIONS', 'GET',): + return True + + return request.user.is_authenticated diff --git a/logs_collector/collector/api/serializers.py b/logs_collector/collector/api/serializers.py index ca09798..411c744 100644 --- a/logs_collector/collector/api/serializers.py +++ b/logs_collector/collector/api/serializers.py @@ -67,3 +67,25 @@ class StorageInfoSerializer(serializers.Serializer): free = serializers.IntegerField(read_only=True) used_percent = serializers.IntegerField(read_only=True) status = serializers.CharField(read_only=True) + + +class TokenStateRootSerializer(serializers.Serializer): + info = serializers.CharField(read_only=True, default="manual message") + + +class TokenStateSerializer(serializers.ModelSerializer): + token = serializers.UUIDField(read_only=True) + attempts = serializers.IntegerField(read_only=True) + resolved = serializers.BooleanField(read_only=True) + + class Meta: + model = Ticket + fields = [ + 'token', + 'attempts', + 'resolved' + ] + + +class AppHealthInfoSerializer(serializers.Serializer): + status = serializers.CharField(read_only=True, default="ok") diff --git a/logs_collector/collector/api/urls.py b/logs_collector/collector/api/urls.py index 1a34d27..eb1a911 100644 --- a/logs_collector/collector/api/urls.py +++ b/logs_collector/collector/api/urls.py @@ -15,8 +15,31 @@ router.register(r'archives', views.ArchiveViewSet) router.register(r'platforms', views.PlatformViewSet) router.register(r'tickets', views.TicketViewSet) +check_urlpatterns = [ + path( + 'health/', + views.AppHealthInfo.as_view(), + name='app-info' + ), + path( + 'storage/', + views.StorageInfo.as_view(), + name='storage-info' + ), + path( + 'token/', + views.TokenStateRoot.as_view(), + name='token-root' + ), + path( + 'token/', + views.TokenStateInfo.as_view(), + name='token-info' + ), +] + urlpatterns = [ # CRUD: path('v1/', include(router.urls)), - path('v1/storage/', views.StorageInfo.as_view(), name='storage-info'), + path('v1/check/', include(check_urlpatterns)), ] diff --git a/logs_collector/collector/api/views.py b/logs_collector/collector/api/views.py index cc489b3..4667a33 100644 --- a/logs_collector/collector/api/views.py +++ b/logs_collector/collector/api/views.py @@ -10,9 +10,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 rest_framework import filters, generics, views, viewsets from django_filters.rest_framework import DjangoFilterBackend @@ -23,13 +21,16 @@ 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 +from .permissions import IsGuestUpload, IsGuestCheckUrls from .serializers import ( PublicArchiveUploadSerializer, ArchiveSerializer, PlatformSerializer, TicketSerializer, StorageInfoSerializer, + TokenStateSerializer, + AppHealthInfoSerializer, + TokenStateRootSerializer, ) @@ -172,3 +173,40 @@ class StorageInfo(views.APIView): ) def get(self, request): return Response(get_mount_fs_info(settings.DATA_DIR)) + + +class TokenStateRoot(views.APIView): + """ Show the message of a specific upload token URL""" + permission_classes = (IsGuestCheckUrls,) + + @extend_schema( + responses=TokenStateRootSerializer, + summary='Show info message how get token status' + ) + def get(self, request): + message = "to find out the status of the token, place it in the URL" + return Response({"detail": message}, status=status.HTTP_303_SEE_OTHER) + + +@extend_schema_view( + get=extend_schema( + summary='Show the status of a specific upload token' + ) +) +class TokenStateInfo(generics.RetrieveAPIView): + """ Show the status of a specific upload token""" + queryset = Ticket.objects.order_by('-time_create') + lookup_field = 'token' + serializer_class = TokenStateSerializer + permission_classes = (IsGuestCheckUrls,) + + +class AppHealthInfo(views.APIView): + permission_classes = (IsGuestCheckUrls,) + + @extend_schema( + responses=AppHealthInfoSerializer, + summary='Show app status' + ) + def get(self, request): + return Response({'status': 'ok'}, status=status.HTTP_200_OK) diff --git a/logs_collector/collector/static/collector/js/helpers.js b/logs_collector/collector/static/collector/js/helpers.js index df1b231..3022c45 100644 --- a/logs_collector/collector/static/collector/js/helpers.js +++ b/logs_collector/collector/static/collector/js/helpers.js @@ -66,4 +66,18 @@ const updateStorageInfo = () => { }); }; -export {sizify, updateBsTooltip, updateStorageInfo}; +const genAlertMessage = ( + alertMessage='Success message', + alertType='success', + extraClass='' + ) => { + let alertMessageHTML = [ + `' + ].join('') + return alertMessageHTML +} + +export {sizify, updateBsTooltip, updateStorageInfo, genAlertMessage}; 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 45a324a..7ccb26e 100644 --- a/logs_collector/collector/static/collector/js/jq.upload.progress.js +++ b/logs_collector/collector/static/collector/js/jq.upload.progress.js @@ -1,88 +1,139 @@ -import {updateStorageInfo} from "./helpers.js"; +import {updateStorageInfo, genAlertMessage} from "./helpers.js"; $(function () { + // set global variables: 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'); - + const inputFile = document.getElementById('id_file'); + const progressBar = document.getElementById('progress'); + const alertContainer = document.getElementById('alert'); + // get upload form: $("#upload_form").submit(function(e){ e.preventDefault(); - // $form = $(this) + // collect request data: 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.timeout = 3600000; // increase request timeout to 1 hour - xhr.upload.addEventListener('progress', e=>{ - if(e.lengthComputable){ - const percentProgress = (e.loaded/e.total)*100; - console.log(percentProgress); - progress_bar.innerHTML = ` -
-
` + let uploadToken = formData.get("token") + // generate the URL for token validation: + let tokenStatusUrl = [ + progressBar.getAttribute('token-status-url'), + uploadToken + ].join('') + // init upload file func: + const uploadFile = () => { + // toggle visible progress bar: + const mediaData = inputFile.files[0]; + if(mediaData != null){ + progressBar.classList.remove("not-visible"); + } + // upload file (chunk) xrh request: + $.ajax({ + type: 'POST', + url: progressBar.getAttribute("upload-url"), + data: formData, + dataType: 'json', + xhr:function(){ + const xhr = new window.XMLHttpRequest(); + xhr.timeout = 3600000; // increase request timeout to 1 hour + xhr.upload.addEventListener('progress', e=>{ + if(e.lengthComputable){ + const percentProgress = (e.loaded/e.total)*100; + console.log(percentProgress); + progressBar.innerHTML = ` +
+
` + } + }); + return xhr + }, + // set auth method: + beforeSend: function(xhr) { + if (uploadToken) { + xhr.setRequestHeader("Upload-Token", uploadToken); } - }); - return xhr - }, - beforeSend: function(xhr) { - if (upload_token) { - xhr.setRequestHeader("Upload-Token", upload_token); + }, + success: function(data, textStatus, jqXHR){ + console.log(jqXHR.status); + alertContainer.innerHTML = genAlertMessage( + 'The file has been successfully uploaded to the server. Thank you!', + 'success', + 'col-lg-6' + ) + uploadForm.reset() + progressBar.classList.add('not-visible') + try { + updateStorageInfo(); + } catch (error) { + console.log(error) + }; + }, + error: function(jqXHR, textStatus, errorThrown){ + console.log(jqXHR); + let errorMessage = "Unexpected error. Try again please" + if (jqXHR.status === 423 || jqXHR.status === 403) { + errorMessage = `Error ${jqXHR.status}: ${jqXHR.responseJSON.error}` + } + if (jqXHR.status === 401) { + errorMessage = 'The token field cannot be empty' + } + alertContainer.innerHTML = genAlertMessage( + errorMessage, + 'danger', + 'col-lg-6' + ) + progressBar.classList.add('not-visible') + }, + cache: false, + contentType: false, + processData: false, + }); + } + // check token status and upload file if token valid: + $.ajax({ + type: 'GET', + url: tokenStatusUrl, + dataType: "json", + success: function (data, textStatus, jqXHR) { + if (data.attempts === 0) { + alertContainer.innerHTML = genAlertMessage( + `Token: ${uploadToken} expired`, + 'danger', + 'col-lg-6' + ); } - }, - success: function(data, textStatus, jqXHR){ - console.log(jqXHR.status); - let type = "success"; - alert_container.innerHTML = [ - `' - ].join('') - uploadForm.reset() - progress_bar.classList.add('not-visible') - try { - updateStorageInfo(); - } catch (error) { - console.log(error) + else if (data.resolved === true) { + alertContainer.innerHTML = genAlertMessage( + `Ticket bound with token: ${uploadToken}
already resolved`, + 'danger', + 'col-lg-6' + ); + } else { + alertContainer.innerHTML = genAlertMessage( + `Token: ${uploadToken} is valid.
Starting to upload...`, + 'success', + 'col-lg-6' + ); + uploadFile(); }; }, - error: function(jqXHR, textStatus, errorThrown){ - console.log(jqXHR); - let type = "danger"; - let error_message = "Unexpected error. Try again please" - if (jqXHR.status === 423) { - error_message = `Error ${jqXHR.status}: ${jqXHR.responseJSON.error}` + error: function(jqXHR){ + console.log(jqXHR) + console.log(jqXHR.responseJSON.detail) + if (jqXHR.responseJSON.detail) { + alertContainer.innerHTML = genAlertMessage( + `Token: ${uploadToken} is not valid`, + 'danger', + 'col-lg-6' + ) + } else { + alertContainer.innerHTML = genAlertMessage( + `Unexpected error. Try again please`, + 'danger', + 'col-lg-6' + ) } - if (jqXHR.status === 403) { - error_message = `Error ${jqXHR.status}: ${jqXHR.responseJSON.error}` - } - if (jqXHR.status === 401) { - error_message = 'The token field cannot be empty' - } - alert_container.innerHTML = [ - `' - ].join('') - progress_bar.classList.add('not-visible') }, - cache: false, - contentType: false, - processData: false, }); }); }); diff --git a/logs_collector/collector/templates/collector/archive_upload.html b/logs_collector/collector/templates/collector/archive_upload.html index 506e29a..4ad4941 100644 --- a/logs_collector/collector/templates/collector/archive_upload.html +++ b/logs_collector/collector/templates/collector/archive_upload.html @@ -35,7 +35,8 @@