Status api view
This commit is contained in:
parent
23ceb2a5ec
commit
e482aa4e92
@ -59,7 +59,8 @@ ARG GS_VERSION=10.02.1
|
|||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
# Ignore warning from Whitenoise
|
# Ignore warning from Whitenoise
|
||||||
PYTHONWARNINGS="ignore:::django.http.response:517"
|
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
||||||
|
PNGX_CONTAINERIZED=1
|
||||||
|
|
||||||
#
|
#
|
||||||
# Begin installation and configuration
|
# Begin installation and configuration
|
||||||
|
34
src/documents/tests/test_api_status.py
Normal file
34
src/documents/tests/test_api_status.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from paperless import version
|
||||||
|
|
||||||
|
|
||||||
|
class TestSystemStatusView(APITestCase):
|
||||||
|
ENDPOINT = "/api/status/"
|
||||||
|
|
||||||
|
def test_system_status_insufficient_permissions(self):
|
||||||
|
response = self.client.get(self.ENDPOINT)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
def test_system_status(self):
|
||||||
|
user = User.objects.create_superuser(
|
||||||
|
username="temp_admin",
|
||||||
|
)
|
||||||
|
self.client.force_login(user)
|
||||||
|
response = self.client.get(self.ENDPOINT)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data["pngx_version"], version.__full_version_str__)
|
||||||
|
self.assertIsNotNone(response.data["server_os"])
|
||||||
|
self.assertEqual(response.data["install_type"], "bare-metal")
|
||||||
|
self.assertIsNotNone(response.data["storage"]["total"])
|
||||||
|
self.assertIsNotNone(response.data["storage"]["available"])
|
||||||
|
self.assertEqual(response.data["database"]["type"], "sqlite")
|
||||||
|
self.assertIsNotNone(response.data["database"]["url"])
|
||||||
|
self.assertEqual(response.data["database"]["status"], "OK")
|
||||||
|
self.assertIsNone(response.data["database"]["error"])
|
||||||
|
self.assertIsNotNone(response.data["database"]["migration_status"])
|
||||||
|
self.assertEqual(response.data["redis"]["url"], "redis://localhost:6379")
|
||||||
|
self.assertEqual(response.data["redis"]["status"], "ERROR")
|
||||||
|
self.assertIsNotNone(response.data["redis"]["error"])
|
@ -2,6 +2,7 @@ import itertools
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
import urllib
|
import urllib
|
||||||
@ -15,6 +16,9 @@ from urllib.parse import quote
|
|||||||
import pathvalidate
|
import pathvalidate
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import connections
|
||||||
|
from django.db.migrations.loader import MigrationLoader
|
||||||
|
from django.db.migrations.recorder import MigrationRecorder
|
||||||
from django.db.models import Case
|
from django.db.models import Case
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.db.models import IntegerField
|
from django.db.models import IntegerField
|
||||||
@ -40,6 +44,7 @@ from django.views.generic import TemplateView
|
|||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from langdetect import detect
|
from langdetect import detect
|
||||||
from packaging import version as packaging_version
|
from packaging import version as packaging_version
|
||||||
|
from redis import Redis
|
||||||
from rest_framework import parsers
|
from rest_framework import parsers
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import NotFound
|
from rest_framework.exceptions import NotFound
|
||||||
@ -1539,3 +1544,74 @@ class CustomFieldViewSet(ModelViewSet):
|
|||||||
model = CustomField
|
model = CustomField
|
||||||
|
|
||||||
queryset = CustomField.objects.all().order_by("-created")
|
queryset = CustomField.objects.all().order_by("-created")
|
||||||
|
|
||||||
|
|
||||||
|
class SystemStatusView(GenericAPIView, PassUserMixin):
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
|
def get(self, request, format=None):
|
||||||
|
if not request.user.has_perm("admin.view_logentry"):
|
||||||
|
return HttpResponseForbidden("Insufficient permissions")
|
||||||
|
|
||||||
|
current_version = version.__full_version_str__
|
||||||
|
|
||||||
|
media_stats = os.statvfs(settings.MEDIA_ROOT)
|
||||||
|
|
||||||
|
db_conn = connections["default"]
|
||||||
|
db_url = db_conn.settings_dict["NAME"]
|
||||||
|
loader = MigrationLoader(connection=db_conn)
|
||||||
|
all_migrations = [f"{app}.{name}" for app, name in loader.graph.nodes]
|
||||||
|
applied_migrations = [
|
||||||
|
f"{m.app}.{m.name}"
|
||||||
|
for m in MigrationRecorder.Migration.objects.all().order_by("id")
|
||||||
|
]
|
||||||
|
db_error = None
|
||||||
|
try:
|
||||||
|
db_conn.cursor()
|
||||||
|
db_status = "OK"
|
||||||
|
except Exception as e:
|
||||||
|
db_status = "ERROR"
|
||||||
|
db_error = str(e)
|
||||||
|
|
||||||
|
redis_url = settings._CELERY_REDIS_URL
|
||||||
|
redis_error = None
|
||||||
|
with Redis.from_url(url=redis_url) as client:
|
||||||
|
try:
|
||||||
|
client.ping()
|
||||||
|
redis_status = "OK"
|
||||||
|
except Exception as e:
|
||||||
|
redis_status = "ERROR"
|
||||||
|
redis_error = str(e)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"pngx_version": current_version,
|
||||||
|
"server_os": platform.platform(),
|
||||||
|
"install_type": (
|
||||||
|
"containerized"
|
||||||
|
if os.environ.get("PNGX_CONTAINERIZED") == "1"
|
||||||
|
else "bare-metal"
|
||||||
|
),
|
||||||
|
"storage": {
|
||||||
|
"total": media_stats.f_frsize * media_stats.f_blocks,
|
||||||
|
"available": media_stats.f_frsize * media_stats.f_bavail,
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"type": db_conn.vendor,
|
||||||
|
"url": db_url,
|
||||||
|
"status": db_status,
|
||||||
|
"error": db_error,
|
||||||
|
"migration_status": {
|
||||||
|
"latest_migration": applied_migrations[-1],
|
||||||
|
"unapplied_migrations": [
|
||||||
|
m for m in all_migrations if m not in applied_migrations
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"redis": {
|
||||||
|
"url": redis_url,
|
||||||
|
"status": redis_status,
|
||||||
|
"error": redis_error,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@ -32,6 +32,7 @@ from documents.views import SharedLinkView
|
|||||||
from documents.views import ShareLinkViewSet
|
from documents.views import ShareLinkViewSet
|
||||||
from documents.views import StatisticsView
|
from documents.views import StatisticsView
|
||||||
from documents.views import StoragePathViewSet
|
from documents.views import StoragePathViewSet
|
||||||
|
from documents.views import SystemStatusView
|
||||||
from documents.views import TagViewSet
|
from documents.views import TagViewSet
|
||||||
from documents.views import TasksViewSet
|
from documents.views import TasksViewSet
|
||||||
from documents.views import UiSettingsView
|
from documents.views import UiSettingsView
|
||||||
@ -147,6 +148,11 @@ urlpatterns = [
|
|||||||
ProfileView.as_view(),
|
ProfileView.as_view(),
|
||||||
name="profile_view",
|
name="profile_view",
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
"^status/",
|
||||||
|
SystemStatusView.as_view(),
|
||||||
|
name="system_status",
|
||||||
|
),
|
||||||
*api_router.urls,
|
*api_router.urls,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user