Status api view

This commit is contained in:
shamoon 2024-02-10 00:58:15 -08:00
parent 23ceb2a5ec
commit e482aa4e92
4 changed files with 118 additions and 1 deletions

View File

@ -59,7 +59,8 @@ ARG GS_VERSION=10.02.1
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
# Ignore warning from Whitenoise
PYTHONWARNINGS="ignore:::django.http.response:517"
PYTHONWARNINGS="ignore:::django.http.response:517" \
PNGX_CONTAINERIZED=1
#
# Begin installation and configuration

View 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"])

View File

@ -2,6 +2,7 @@ import itertools
import json
import logging
import os
import platform
import re
import tempfile
import urllib
@ -15,6 +16,9 @@ from urllib.parse import quote
import pathvalidate
from django.conf import settings
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 Count
from django.db.models import IntegerField
@ -40,6 +44,7 @@ from django.views.generic import TemplateView
from django_filters.rest_framework import DjangoFilterBackend
from langdetect import detect
from packaging import version as packaging_version
from redis import Redis
from rest_framework import parsers
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound
@ -1539,3 +1544,74 @@ class CustomFieldViewSet(ModelViewSet):
model = CustomField
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,
},
},
)

View File

@ -32,6 +32,7 @@ from documents.views import SharedLinkView
from documents.views import ShareLinkViewSet
from documents.views import StatisticsView
from documents.views import StoragePathViewSet
from documents.views import SystemStatusView
from documents.views import TagViewSet
from documents.views import TasksViewSet
from documents.views import UiSettingsView
@ -147,6 +148,11 @@ urlpatterns = [
ProfileView.as_view(),
name="profile_view",
),
re_path(
"^status/",
SystemStatusView.as_view(),
name="system_status",
),
*api_router.urls,
],
),