mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-13 10:03:49 -05:00
Change: tweaks to system status (#6008)
This commit is contained in:
parent
fc74da9b82
commit
55a40708a6
@ -4281,7 +4281,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
|
||||||
<context context-type="linenumber">152</context>
|
<context context-type="linenumber">156</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="595732867213154214" datatype="html">
|
<trans-unit id="595732867213154214" datatype="html">
|
||||||
@ -4652,7 +4652,7 @@
|
|||||||
<source>Last Trained</source>
|
<source>Last Trained</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
|
||||||
<context context-type="linenumber">135</context>
|
<context context-type="linenumber">139</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6732151329960766506" datatype="html">
|
<trans-unit id="6732151329960766506" datatype="html">
|
||||||
|
@ -128,7 +128,11 @@
|
|||||||
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1" [ngbPopover]="classifierStatus" triggers="mouseenter:mouseleave"></i-bs>
|
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1" [ngbPopover]="classifierStatus" triggers="mouseenter:mouseleave"></i-bs>
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1" ngbPopover="{{status.tasks.classifier_error}}" triggers="mouseenter:mouseleave"></i-bs>
|
<i-bs name="exclamation-triangle-fill" class="ms-2 lh-1"
|
||||||
|
[class.text-danger]="status.tasks.classifier_status === SystemStatusItemStatus.ERROR"
|
||||||
|
[class.text-warning]="status.tasks.classifier_status === SystemStatusItemStatus.WARNING"
|
||||||
|
ngbPopover="{{status.tasks.classifier_error}}"
|
||||||
|
triggers="mouseenter:mouseleave"></i-bs>
|
||||||
}
|
}
|
||||||
</dd>
|
</dd>
|
||||||
<ng-template #classifierStatus>
|
<ng-template #classifierStatus>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { SystemStatus } from 'src/app/data/system-status'
|
import {
|
||||||
|
SystemStatus,
|
||||||
|
SystemStatusItemStatus,
|
||||||
|
} from 'src/app/data/system-status'
|
||||||
import { SystemStatusService } from 'src/app/services/system-status.service'
|
import { SystemStatusService } from 'src/app/services/system-status.service'
|
||||||
import { Clipboard } from '@angular/cdk/clipboard'
|
import { Clipboard } from '@angular/cdk/clipboard'
|
||||||
|
|
||||||
@ -10,6 +13,7 @@ import { Clipboard } from '@angular/cdk/clipboard'
|
|||||||
styleUrl: './system-status-dialog.component.scss',
|
styleUrl: './system-status-dialog.component.scss',
|
||||||
})
|
})
|
||||||
export class SystemStatusDialogComponent {
|
export class SystemStatusDialogComponent {
|
||||||
|
public SystemStatusItemStatus = SystemStatusItemStatus
|
||||||
public status: SystemStatus
|
public status: SystemStatus
|
||||||
|
|
||||||
public copied: boolean = false
|
public copied: boolean = false
|
||||||
|
@ -6,6 +6,7 @@ export enum InstallType {
|
|||||||
export enum SystemStatusItemStatus {
|
export enum SystemStatusItemStatus {
|
||||||
OK = 'OK',
|
OK = 'OK',
|
||||||
ERROR = 'ERROR',
|
ERROR = 'ERROR',
|
||||||
|
WARNING = 'WARNING',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SystemStatus {
|
export interface SystemStatus {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
@ -7,8 +8,11 @@ from django.test import override_settings
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from documents.classifier import ClassifierModelCorruptError
|
||||||
from documents.classifier import DocumentClassifier
|
from documents.classifier import DocumentClassifier
|
||||||
from documents.classifier import load_classifier
|
from documents.classifier import load_classifier
|
||||||
|
from documents.models import Document
|
||||||
|
from documents.models import Tag
|
||||||
from paperless import version
|
from paperless import version
|
||||||
|
|
||||||
|
|
||||||
@ -158,7 +162,7 @@ class TestSystemStatus(APITestCase):
|
|||||||
WHEN:
|
WHEN:
|
||||||
- The user requests the system status
|
- The user requests the system status
|
||||||
THEN:
|
THEN:
|
||||||
- The response contains the correct classifier status
|
- The response contains an OK classifier status
|
||||||
"""
|
"""
|
||||||
load_classifier()
|
load_classifier()
|
||||||
test_classifier = DocumentClassifier()
|
test_classifier = DocumentClassifier()
|
||||||
@ -169,18 +173,66 @@ class TestSystemStatus(APITestCase):
|
|||||||
self.assertEqual(response.data["tasks"]["classifier_status"], "OK")
|
self.assertEqual(response.data["tasks"]["classifier_status"], "OK")
|
||||||
self.assertIsNone(response.data["tasks"]["classifier_error"])
|
self.assertIsNone(response.data["tasks"]["classifier_error"])
|
||||||
|
|
||||||
def test_system_status_classifier_error(self):
|
def test_system_status_classifier_warning(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- The classifier is not found
|
- The classifier does not exist yet
|
||||||
|
- > 0 documents and tags with auto matching exist
|
||||||
WHEN:
|
WHEN:
|
||||||
- The user requests the system status
|
- The user requests the system status
|
||||||
THEN:
|
THEN:
|
||||||
- The response contains an error classifier status
|
- The response contains an WARNING classifier status
|
||||||
|
"""
|
||||||
|
with override_settings(MODEL_FILE="does_not_exist"):
|
||||||
|
Document.objects.create(
|
||||||
|
title="Test Document",
|
||||||
|
)
|
||||||
|
Tag.objects.create(name="Test Tag", matching_algorithm=Tag.MATCH_AUTO)
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
response = self.client.get(self.ENDPOINT)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data["tasks"]["classifier_status"], "WARNING")
|
||||||
|
self.assertIsNotNone(response.data["tasks"]["classifier_error"])
|
||||||
|
|
||||||
|
def test_system_status_classifier_error(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- The classifier does exist but is corrupt
|
||||||
|
- > 0 documents and tags with auto matching exist
|
||||||
|
WHEN:
|
||||||
|
- The user requests the system status
|
||||||
|
THEN:
|
||||||
|
- The response contains an ERROR classifier status
|
||||||
|
"""
|
||||||
|
does_exist = tempfile.NamedTemporaryFile(
|
||||||
|
dir="/tmp",
|
||||||
|
delete=False,
|
||||||
|
)
|
||||||
|
with override_settings(MODEL_FILE=does_exist):
|
||||||
|
with mock.patch("documents.classifier.load_classifier") as mock_load:
|
||||||
|
mock_load.side_effect = ClassifierModelCorruptError()
|
||||||
|
Document.objects.create(
|
||||||
|
title="Test Document",
|
||||||
|
)
|
||||||
|
Tag.objects.create(name="Test Tag", matching_algorithm=Tag.MATCH_AUTO)
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
response = self.client.get(self.ENDPOINT)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data["tasks"]["classifier_status"], "ERROR")
|
||||||
|
self.assertIsNotNone(response.data["tasks"]["classifier_error"])
|
||||||
|
|
||||||
|
def test_system_status_classifier_ok_no_objects(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- The classifier does not exist (and should not)
|
||||||
|
- No documents nor objects with auto matching exist
|
||||||
|
WHEN:
|
||||||
|
- The user requests the system status
|
||||||
|
THEN:
|
||||||
|
- The response contains an OK classifier status
|
||||||
"""
|
"""
|
||||||
with override_settings(MODEL_FILE="does_not_exist"):
|
with override_settings(MODEL_FILE="does_not_exist"):
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
response = self.client.get(self.ENDPOINT)
|
response = self.client.get(self.ENDPOINT)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(response.data["tasks"]["classifier_status"], "ERROR")
|
self.assertEqual(response.data["tasks"]["classifier_status"], "OK")
|
||||||
self.assertIsNotNone(response.data["tasks"]["classifier_error"])
|
|
||||||
|
@ -1581,7 +1581,9 @@ class SystemStatusView(GenericAPIView, PassUserMixin):
|
|||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
applied_migrations = []
|
applied_migrations = []
|
||||||
db_status = "ERROR"
|
db_status = "ERROR"
|
||||||
logger.exception(f"System status error connecting to database: {e}")
|
logger.exception(
|
||||||
|
f"System status detected a possible problem while connecting to the database: {e}",
|
||||||
|
)
|
||||||
db_error = "Error connecting to database, check logs for more detail."
|
db_error = "Error connecting to database, check logs for more detail."
|
||||||
|
|
||||||
media_stats = os.statvfs(settings.MEDIA_ROOT)
|
media_stats = os.statvfs(settings.MEDIA_ROOT)
|
||||||
@ -1594,7 +1596,9 @@ class SystemStatusView(GenericAPIView, PassUserMixin):
|
|||||||
redis_status = "OK"
|
redis_status = "OK"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
redis_status = "ERROR"
|
redis_status = "ERROR"
|
||||||
logger.exception(f"System status error connecting to redis: {e}")
|
logger.exception(
|
||||||
|
f"System status detected a possible problem while connecting to redis: {e}",
|
||||||
|
)
|
||||||
redis_error = "Error connecting to redis, check logs for more detail."
|
redis_error = "Error connecting to redis, check logs for more detail."
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -1615,14 +1619,40 @@ class SystemStatusView(GenericAPIView, PassUserMixin):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
index_status = "ERROR"
|
index_status = "ERROR"
|
||||||
index_error = "Error opening index, check logs for more detail."
|
index_error = "Error opening index, check logs for more detail."
|
||||||
logger.exception(f"System status error opening index: {e}")
|
logger.exception(
|
||||||
|
f"System status detected a possible problem while opening the index: {e}",
|
||||||
|
)
|
||||||
index_last_modified = None
|
index_last_modified = None
|
||||||
|
|
||||||
classifier_error = None
|
classifier_error = None
|
||||||
|
classifier_status = None
|
||||||
try:
|
try:
|
||||||
classifier = load_classifier()
|
classifier = load_classifier()
|
||||||
if classifier is None:
|
if classifier is None:
|
||||||
raise Exception("Classifier not loaded")
|
# Make sure classifier should exist
|
||||||
|
docs_queryset = Document.objects.exclude(
|
||||||
|
tags__is_inbox_tag=True,
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
docs_queryset.count() > 0
|
||||||
|
and (
|
||||||
|
Tag.objects.filter(matching_algorithm=Tag.MATCH_AUTO).exists()
|
||||||
|
or DocumentType.objects.filter(
|
||||||
|
matching_algorithm=Tag.MATCH_AUTO,
|
||||||
|
).exists()
|
||||||
|
or Correspondent.objects.filter(
|
||||||
|
matching_algorithm=Tag.MATCH_AUTO,
|
||||||
|
).exists()
|
||||||
|
or StoragePath.objects.filter(
|
||||||
|
matching_algorithm=Tag.MATCH_AUTO,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
and not os.path.isfile(settings.MODEL_FILE)
|
||||||
|
):
|
||||||
|
# if classifier file doesn't exist just classify as a warning
|
||||||
|
classifier_error = "Classifier file does not exist (yet). Re-training may be pending."
|
||||||
|
classifier_status = "WARNING"
|
||||||
|
raise FileNotFoundError(classifier_error)
|
||||||
classifier_status = "OK"
|
classifier_status = "OK"
|
||||||
task_result_model = apps.get_model("django_celery_results", "taskresult")
|
task_result_model = apps.get_model("django_celery_results", "taskresult")
|
||||||
result = (
|
result = (
|
||||||
@ -1637,10 +1667,16 @@ class SystemStatusView(GenericAPIView, PassUserMixin):
|
|||||||
)
|
)
|
||||||
classifier_last_trained = result.date_done if result else None
|
classifier_last_trained = result.date_done if result else None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
classifier_status = "ERROR"
|
if classifier_status is None:
|
||||||
|
classifier_status = "ERROR"
|
||||||
classifier_last_trained = None
|
classifier_last_trained = None
|
||||||
classifier_error = "Error loading classifier, check logs for more detail."
|
if classifier_error is None:
|
||||||
logger.exception(f"System status error loading classifier: {e}")
|
classifier_error = (
|
||||||
|
"Unable to load classifier, check logs for more detail."
|
||||||
|
)
|
||||||
|
logger.exception(
|
||||||
|
f"System status detected a possible problem while loading the classifier: {e}",
|
||||||
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user