Modify: archive upload js now it check token status, HEALTHCHEK call health api endpoint

This commit is contained in:
Stepan Zhukovsky 2023-09-13 19:22:37 +09:00
parent a1679b69c3
commit a7a85b1816
8 changed files with 244 additions and 82 deletions

View File

@ -29,7 +29,8 @@ ARG VERSION=0.1.0 \
USER_NAME=collector \ USER_NAME=collector \
USER_GROUP=collector \ USER_GROUP=collector \
APP_UID=1000 \ APP_UID=1000 \
APP_GID=1000 APP_GID=1000 \
HEALTHCHECK_PATH=api/v1/check/health/
# copy app dependences # copy app dependences
COPY --from=base /usr/local/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/ 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} LABEL me.zhukovsky.logs-collector.version=v${VERSION}
# call the health check endpoint of app # 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 # run app
ENTRYPOINT [ "sh", "entrypoint.sh" ] ENTRYPOINT [ "sh", "entrypoint.sh" ]

View File

@ -11,3 +11,15 @@ class IsGuestUpload(permissions.BasePermission):
return True return True
return request.user.is_authenticated 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

View File

@ -67,3 +67,25 @@ class StorageInfoSerializer(serializers.Serializer):
free = serializers.IntegerField(read_only=True) free = serializers.IntegerField(read_only=True)
used_percent = serializers.IntegerField(read_only=True) used_percent = serializers.IntegerField(read_only=True)
status = serializers.CharField(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")

View File

@ -15,8 +15,31 @@ router.register(r'archives', views.ArchiveViewSet)
router.register(r'platforms', views.PlatformViewSet) router.register(r'platforms', views.PlatformViewSet)
router.register(r'tickets', views.TicketViewSet) 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/<str:token>',
views.TokenStateInfo.as_view(),
name='token-info'
),
]
urlpatterns = [ urlpatterns = [
# CRUD: # CRUD:
path('v1/', include(router.urls)), path('v1/', include(router.urls)),
path('v1/storage/', views.StorageInfo.as_view(), name='storage-info'), path('v1/check/', include(check_urlpatterns)),
] ]

View File

@ -10,9 +10,7 @@ from rest_framework.parsers import (
) )
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import viewsets from rest_framework import filters, generics, views, viewsets
from rest_framework import views
from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend 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 collector.utils.helpers import get_mount_fs_info
from .filters import ArchiveFilter, TicketFilter from .filters import ArchiveFilter, TicketFilter
from .permissions import IsGuestUpload from .permissions import IsGuestUpload, IsGuestCheckUrls
from .serializers import ( from .serializers import (
PublicArchiveUploadSerializer, PublicArchiveUploadSerializer,
ArchiveSerializer, ArchiveSerializer,
PlatformSerializer, PlatformSerializer,
TicketSerializer, TicketSerializer,
StorageInfoSerializer, StorageInfoSerializer,
TokenStateSerializer,
AppHealthInfoSerializer,
TokenStateRootSerializer,
) )
@ -172,3 +173,40 @@ class StorageInfo(views.APIView):
) )
def get(self, request): def get(self, request):
return Response(get_mount_fs_info(settings.DATA_DIR)) 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)

View File

@ -66,4 +66,18 @@ const updateStorageInfo = () => {
}); });
}; };
export {sizify, updateBsTooltip, updateStorageInfo}; const genAlertMessage = (
alertMessage='Success message',
alertType='success',
extraClass=''
) => {
let alertMessageHTML = [
`<div class="alert alert-${alertType} alert-dismissible ${extraClass}" role="alert">`,
` <div>${alertMessage}</div>`,
' <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
'</div>'
].join('')
return alertMessageHTML
}
export {sizify, updateBsTooltip, updateStorageInfo, genAlertMessage};

View File

@ -1,23 +1,33 @@
import {updateStorageInfo} from "./helpers.js"; import {updateStorageInfo, genAlertMessage} from "./helpers.js";
$(function () { $(function () {
// set global variables:
const uploadForm = document.getElementById('upload_form'); const uploadForm = document.getElementById('upload_form');
const input_file = document.getElementById('id_file'); const inputFile = document.getElementById('id_file');
const progress_bar = document.getElementById('progress'); const progressBar = document.getElementById('progress');
const alert_container = document.getElementById('alert'); const alertContainer = document.getElementById('alert');
// get upload form:
$("#upload_form").submit(function(e){ $("#upload_form").submit(function(e){
e.preventDefault(); e.preventDefault();
// $form = $(this) // collect request data:
let formData = new FormData(this); let formData = new FormData(this);
let upload_token = formData.get("token") let uploadToken = formData.get("token")
const media_data = input_file.files[0]; // generate the URL for token validation:
if(media_data != null){ let tokenStatusUrl = [
progress_bar.classList.remove("not-visible"); 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({ $.ajax({
type: 'POST', type: 'POST',
url: progress_bar.getAttribute("upload-url"), url: progressBar.getAttribute("upload-url"),
data: formData, data: formData,
dataType: 'json', dataType: 'json',
xhr:function(){ xhr:function(){
@ -27,7 +37,7 @@ $(function () {
if(e.lengthComputable){ if(e.lengthComputable){
const percentProgress = (e.loaded/e.total)*100; const percentProgress = (e.loaded/e.total)*100;
console.log(percentProgress); console.log(percentProgress);
progress_bar.innerHTML = ` progressBar.innerHTML = `
<div <div
class="progress-bar progress-bar-striped progress-bar-animated" class="progress-bar progress-bar-striped progress-bar-animated"
style="width: ${percentProgress}%" style="width: ${percentProgress}%"
@ -37,22 +47,21 @@ $(function () {
}); });
return xhr return xhr
}, },
// set auth method:
beforeSend: function(xhr) { beforeSend: function(xhr) {
if (upload_token) { if (uploadToken) {
xhr.setRequestHeader("Upload-Token", upload_token); xhr.setRequestHeader("Upload-Token", uploadToken);
} }
}, },
success: function(data, textStatus, jqXHR){ success: function(data, textStatus, jqXHR){
console.log(jqXHR.status); console.log(jqXHR.status);
let type = "success"; alertContainer.innerHTML = genAlertMessage(
alert_container.innerHTML = [ 'The file has been successfully uploaded to the server. Thank you!',
`<div class="alert alert-${type} alert-dismissible col-lg-6" role="alert">`, 'success',
` <div>The file has been successfully uploaded to the server. Thank you!</div>`, 'col-lg-6'
' <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>', )
'</div>'
].join('')
uploadForm.reset() uploadForm.reset()
progress_bar.classList.add('not-visible') progressBar.classList.add('not-visible')
try { try {
updateStorageInfo(); updateStorageInfo();
} catch (error) { } catch (error) {
@ -61,28 +70,70 @@ $(function () {
}, },
error: function(jqXHR, textStatus, errorThrown){ error: function(jqXHR, textStatus, errorThrown){
console.log(jqXHR); console.log(jqXHR);
let type = "danger"; let errorMessage = "Unexpected error. Try again please"
let error_message = "Unexpected error. Try again please" if (jqXHR.status === 423 || jqXHR.status === 403) {
if (jqXHR.status === 423) { errorMessage = `Error ${jqXHR.status}: ${jqXHR.responseJSON.error}`
error_message = `Error ${jqXHR.status}: ${jqXHR.responseJSON.error}`
}
if (jqXHR.status === 403) {
error_message = `Error ${jqXHR.status}: ${jqXHR.responseJSON.error}`
} }
if (jqXHR.status === 401) { if (jqXHR.status === 401) {
error_message = 'The token field cannot be empty' errorMessage = 'The token field cannot be empty'
} }
alert_container.innerHTML = [ alertContainer.innerHTML = genAlertMessage(
`<div class="alert alert-${type} alert-dismissible col-lg-6" role="alert">`, errorMessage,
` <div>${error_message}</div>`, 'danger',
' <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>', 'col-lg-6'
'</div>' )
].join('') progressBar.classList.add('not-visible')
progress_bar.classList.add('not-visible')
}, },
cache: false, cache: false,
contentType: false, contentType: false,
processData: 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'
);
}
else if (data.resolved === true) {
alertContainer.innerHTML = genAlertMessage(
`Ticket bound with token: ${uploadToken} <br> already resolved`,
'danger',
'col-lg-6'
);
} else {
alertContainer.innerHTML = genAlertMessage(
`Token: ${uploadToken} is valid. <br> Starting to upload...`,
'success',
'col-lg-6'
);
uploadFile();
};
},
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'
)
}
},
});
}); });
}); });

View File

@ -35,7 +35,8 @@
<div <div
id="progress" id="progress"
upload-url="{% url 'collector_api:archive-list' %}" upload-url="{% url 'collector_api:archive-list' %}"
class="progress" token-status-url="{% url 'collector_api:token-root' %}"
class="progress not-visible"
role="progressbar" role="progressbar"
aria-label="Example 20px high" aria-label="Example 20px high"
aria-valuenow="25" aria-valuenow="25"