mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-06-26 15:24:18 +00:00
Merge branch 'dev' into chore/paginage-task-list
This commit is contained in:
+25
@@ -142,6 +142,31 @@
|
||||
}
|
||||
</ng-template>
|
||||
</dd>
|
||||
<dt i18n>Recent Task Activity <span class="small text-muted fw-light">({{status.tasks.summary.days}} days)</span></dt>
|
||||
<dd class="mb-0">
|
||||
@if (status.tasks.summary.total_count > 0) {
|
||||
<ul class="list-group border-light mt-2">
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span class="small"><ng-container i18n>Total</ng-container>:</span>
|
||||
<span class="badge bg-light rounded-pill">{{status.tasks.summary.total_count}}</span>
|
||||
</li>
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span class="small"><ng-container i18n>Successful</ng-container>:</span>
|
||||
<span class="badge bg-primary rounded-pill">{{status.tasks.summary.success_count}}</span>
|
||||
</li>
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span class="small"><ng-container i18n>Failed</ng-container>:</span>
|
||||
<span class="badge bg-danger rounded-pill">{{status.tasks.summary.failure_count}}</span>
|
||||
</li>
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span class="small"><ng-container i18n>Pending</ng-container>:</span>
|
||||
<span class="badge bg-warning rounded-pill">{{status.tasks.summary.pending_count}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
} @else {
|
||||
<span class="small text-muted" i18n>No recent tasks</span>
|
||||
}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+7
@@ -71,6 +71,13 @@ const status: SystemStatus = {
|
||||
llmindex_status: SystemStatusItemStatus.OK,
|
||||
llmindex_last_modified: new Date().toISOString(),
|
||||
llmindex_error: null,
|
||||
summary: {
|
||||
days: 30,
|
||||
total_count: 12,
|
||||
pending_count: 1,
|
||||
success_count: 10,
|
||||
failure_count: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,13 @@ export interface SystemStatus {
|
||||
llmindex_status: SystemStatusItemStatus
|
||||
llmindex_last_modified: string // ISO date string
|
||||
llmindex_error: string
|
||||
summary: {
|
||||
days: number
|
||||
total_count: number
|
||||
pending_count: number
|
||||
success_count: number
|
||||
failure_count: number
|
||||
}
|
||||
}
|
||||
websocket_connected?: SystemStatusItemStatus // added client-side
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import override_settings
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
@@ -76,6 +78,11 @@ class TestSystemStatus(APITestCase):
|
||||
self.assertEqual(response.data["tasks"]["redis_url"], "redis://localhost:6379")
|
||||
self.assertEqual(response.data["tasks"]["redis_status"], "ERROR")
|
||||
self.assertIsNotNone(response.data["tasks"]["redis_error"])
|
||||
self.assertEqual(response.data["tasks"]["summary"]["days"], 30)
|
||||
self.assertEqual(response.data["tasks"]["summary"]["total_count"], 0)
|
||||
self.assertEqual(response.data["tasks"]["summary"]["success_count"], 0)
|
||||
self.assertEqual(response.data["tasks"]["summary"]["failure_count"], 0)
|
||||
self.assertEqual(response.data["tasks"]["summary"]["pending_count"], 0)
|
||||
|
||||
def test_system_status_insufficient_permissions(self) -> None:
|
||||
"""
|
||||
@@ -436,3 +443,32 @@ class TestSystemStatus(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data["tasks"]["llmindex_status"], "ERROR")
|
||||
self.assertIsNotNone(response.data["tasks"]["llmindex_error"])
|
||||
|
||||
def test_system_status_includes_recent_task_summary(self) -> None:
|
||||
PaperlessTaskFactory(
|
||||
task_type=PaperlessTask.TaskType.CONSUME_FILE,
|
||||
status=PaperlessTask.Status.SUCCESS,
|
||||
)
|
||||
PaperlessTaskFactory(
|
||||
task_type=PaperlessTask.TaskType.CONSUME_FILE,
|
||||
status=PaperlessTask.Status.FAILURE,
|
||||
)
|
||||
PaperlessTaskFactory(
|
||||
task_type=PaperlessTask.TaskType.SANITY_CHECK,
|
||||
status=PaperlessTask.Status.PENDING,
|
||||
)
|
||||
PaperlessTaskFactory(
|
||||
task_type=PaperlessTask.TaskType.MAIL_FETCH,
|
||||
status=PaperlessTask.Status.SUCCESS,
|
||||
date_created=timezone.now() - timedelta(days=45),
|
||||
)
|
||||
|
||||
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"]["summary"]["days"], 30)
|
||||
self.assertEqual(response.data["tasks"]["summary"]["total_count"], 3)
|
||||
self.assertEqual(response.data["tasks"]["summary"]["success_count"], 1)
|
||||
self.assertEqual(response.data["tasks"]["summary"]["failure_count"], 1)
|
||||
self.assertEqual(response.data["tasks"]["summary"]["pending_count"], 1)
|
||||
|
||||
@@ -4609,6 +4609,16 @@ class CustomFieldViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[Custom
|
||||
"redis_status": serializers.CharField(),
|
||||
"redis_error": serializers.CharField(),
|
||||
"celery_status": serializers.CharField(),
|
||||
"summary": inline_serializer(
|
||||
name="TasksSummaryOverview",
|
||||
fields={
|
||||
"days": serializers.IntegerField(),
|
||||
"total_count": serializers.IntegerField(),
|
||||
"pending_count": serializers.IntegerField(),
|
||||
"success_count": serializers.IntegerField(),
|
||||
"failure_count": serializers.IntegerField(),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
"index": inline_serializer(
|
||||
@@ -4642,6 +4652,7 @@ class CustomFieldViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[Custom
|
||||
)
|
||||
class SystemStatusView(PassUserMixin):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
TASK_SUMMARY_DAYS = 30
|
||||
|
||||
def get(self, request, format=None):
|
||||
if not has_system_status_permission(request.user):
|
||||
@@ -4808,6 +4819,29 @@ class SystemStatusView(PassUserMixin):
|
||||
last_llmindex_update.date_done if last_llmindex_update else None
|
||||
)
|
||||
|
||||
summary_cutoff = timezone.now() - timedelta(days=self.TASK_SUMMARY_DAYS)
|
||||
task_summary_agg = PaperlessTask.objects.filter(
|
||||
date_created__gte=summary_cutoff,
|
||||
).aggregate(
|
||||
total_count=Count("id"),
|
||||
pending_count=Count(
|
||||
"id",
|
||||
filter=Q(status=PaperlessTask.Status.PENDING),
|
||||
),
|
||||
success_count=Count(
|
||||
"id",
|
||||
filter=Q(status=PaperlessTask.Status.SUCCESS),
|
||||
),
|
||||
failure_count=Count(
|
||||
"id",
|
||||
filter=Q(status=PaperlessTask.Status.FAILURE),
|
||||
),
|
||||
)
|
||||
task_summary = {
|
||||
"days": self.TASK_SUMMARY_DAYS,
|
||||
**task_summary_agg,
|
||||
}
|
||||
|
||||
return Response(
|
||||
{
|
||||
"pngx_version": current_version,
|
||||
@@ -4848,6 +4882,7 @@ class SystemStatusView(PassUserMixin):
|
||||
"llmindex_status": llmindex_status,
|
||||
"llmindex_last_modified": llmindex_last_modified,
|
||||
"llmindex_error": llmindex_error,
|
||||
"summary": task_summary,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-22 20:49+0000\n"
|
||||
"POT-Creation-Date: 2026-04-23 16:12+0000\n"
|
||||
"PO-Revision-Date: 2022-02-17 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
@@ -1918,151 +1918,151 @@ msgstr ""
|
||||
msgid "paperless application settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:531
|
||||
#: paperless/settings/__init__.py:532
|
||||
msgid "English (US)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:532
|
||||
#: paperless/settings/__init__.py:533
|
||||
msgid "Arabic"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:533
|
||||
#: paperless/settings/__init__.py:534
|
||||
msgid "Afrikaans"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:534
|
||||
#: paperless/settings/__init__.py:535
|
||||
msgid "Belarusian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:535
|
||||
#: paperless/settings/__init__.py:536
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:536
|
||||
#: paperless/settings/__init__.py:537
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:537
|
||||
#: paperless/settings/__init__.py:538
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:538
|
||||
#: paperless/settings/__init__.py:539
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:539
|
||||
#: paperless/settings/__init__.py:540
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:540
|
||||
#: paperless/settings/__init__.py:541
|
||||
msgid "Greek"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:541
|
||||
#: paperless/settings/__init__.py:542
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:542
|
||||
#: paperless/settings/__init__.py:543
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:543
|
||||
#: paperless/settings/__init__.py:544
|
||||
msgid "Persian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:544
|
||||
#: paperless/settings/__init__.py:545
|
||||
msgid "Finnish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:545
|
||||
#: paperless/settings/__init__.py:546
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:546
|
||||
#: paperless/settings/__init__.py:547
|
||||
msgid "Hungarian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:547
|
||||
#: paperless/settings/__init__.py:548
|
||||
msgid "Indonesian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:548
|
||||
#: paperless/settings/__init__.py:549
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:549
|
||||
#: paperless/settings/__init__.py:550
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:550
|
||||
#: paperless/settings/__init__.py:551
|
||||
msgid "Korean"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:551
|
||||
#: paperless/settings/__init__.py:552
|
||||
msgid "Luxembourgish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:552
|
||||
#: paperless/settings/__init__.py:553
|
||||
msgid "Norwegian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:553
|
||||
#: paperless/settings/__init__.py:554
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:554
|
||||
#: paperless/settings/__init__.py:555
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:555
|
||||
#: paperless/settings/__init__.py:556
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:556
|
||||
#: paperless/settings/__init__.py:557
|
||||
msgid "Portuguese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:557
|
||||
#: paperless/settings/__init__.py:558
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:558
|
||||
#: paperless/settings/__init__.py:559
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:559
|
||||
#: paperless/settings/__init__.py:560
|
||||
msgid "Slovak"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:560
|
||||
#: paperless/settings/__init__.py:561
|
||||
msgid "Slovenian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:561
|
||||
#: paperless/settings/__init__.py:562
|
||||
msgid "Serbian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:562
|
||||
#: paperless/settings/__init__.py:563
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:563
|
||||
#: paperless/settings/__init__.py:564
|
||||
msgid "Turkish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:564
|
||||
#: paperless/settings/__init__.py:565
|
||||
msgid "Ukrainian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:565
|
||||
#: paperless/settings/__init__.py:566
|
||||
msgid "Vietnamese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:566
|
||||
#: paperless/settings/__init__.py:567
|
||||
msgid "Chinese Simplified"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings/__init__.py:567
|
||||
#: paperless/settings/__init__.py:568
|
||||
msgid "Chinese Traditional"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -463,10 +463,11 @@ SECURE_PROXY_SSL_HEADER = (
|
||||
else None
|
||||
)
|
||||
|
||||
SECRET_KEY = os.getenv("PAPERLESS_SECRET_KEY", "")
|
||||
if not SECRET_KEY: # pragma: no cover
|
||||
SECRET_KEY = os.getenv("PAPERLESS_SECRET_KEY")
|
||||
_INSECURE_SECRET_KEYS = {None, "", "change-me"}
|
||||
if not DEBUG and SECRET_KEY in _INSECURE_SECRET_KEYS: # pragma: no cover
|
||||
raise ImproperlyConfigured(
|
||||
"PAPERLESS_SECRET_KEY is not set. "
|
||||
"PAPERLESS_SECRET_KEY is not set or is the default 'change-me' value. "
|
||||
"A unique, secret key is required for secure operation. "
|
||||
'Generate one with: python3 -c "import secrets; print(secrets.token_urlsafe(64))"',
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user