diff --git a/rad/urls.py b/rad/urls.py index c25afb92..503ee2e0 100644 --- a/rad/urls.py +++ b/rad/urls.py @@ -93,6 +93,7 @@ urlpatterns = [ name="account_profile_update", ), path("server", views.server, name="server"), + path("logs/", views.logs_view, name="logs_view"), path("people", views.people, name="people"), path("accounts/", views.UserListTableView.as_view(), name="accounts_list"), #path("accounts/", views.UserListView.as_view(), name="accounts_list"), diff --git a/rad/views.py b/rad/views.py index 0e166177..2b2a3f89 100644 --- a/rad/views.py +++ b/rad/views.py @@ -1,5 +1,6 @@ import secrets from typing import Any, Optional +import os from django.conf import settings from django_tables2 import SingleTableMixin @@ -236,6 +237,48 @@ def account_set_supervisor(request, slug): return redirect(f"{next_url}?supervisor_action=created") +@user_passes_test(lambda u: u.is_superuser) +def logs_view(request): + """Display production logs from log.txt with search filtering. + + Scope: superusers only. + Functionality: reads log.txt, displays entries with optional text search filter. + """ + log_file_path = os.path.join(settings.BASE_DIR, "log.txt") + entries = [] + search_query = request.GET.get("q", "").strip() + + if os.path.exists(log_file_path): + try: + with open(log_file_path, "r", encoding="utf-8", errors="ignore") as f: + lines = f.readlines() + + for line in reversed(lines): + line = line.rstrip() + if not line: + continue + + if search_query and search_query.lower() not in line.lower(): + continue + + entries.append(line) + except Exception as e: + entries = [f"Error reading log file: {str(e)}"] + else: + entries = [f"Log file not found at {log_file_path}"] + + return render( + request, + "logs_viewer.html", + { + "entries": entries[:500], + "total_entries": len(entries), + "search_query": search_query, + "log_file_path": log_file_path, + }, + ) + + def cid_selector(request): return render( request, diff --git a/templates/logs_viewer.html b/templates/logs_viewer.html new file mode 100644 index 00000000..15c257d9 --- /dev/null +++ b/templates/logs_viewer.html @@ -0,0 +1,88 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+

Production Logs

+

+ Real-time log monitoring from {{ log_file_path }} +

+
+
+ + Showing {{ entries|length }}/{{ total_entries }} entries + +
+
+ +
+
+
+
+ +
+
+ + {% if search_query %} + Clear + {% endif %} +
+
+
+
+ + {% if entries %} +
+
+
+ + + {% for entry in entries %} + + + + + {% endfor %} + +
+ {{ forloop.counter }} + + {% if 'ERROR' in entry %} + ERROR + {% elif 'WARNING' in entry or 'WARN' in entry %} + WARN + {% elif 'DEBUG' in entry %} + DEBUG + {% elif 'INFO' in entry %} + INFO + {% endif %} + {{ entry }} +
+
+
+
+ +
+ + Showing most recent entries first. Filtered display limited to 500 entries. + {% if search_query %} + Search found {{ entries|length }} matching entr{{ entries|length|pluralize:"y,ies" }}. + {% endif %} +
+ {% else %} +
+ No log entries found. + {% if search_query %} + Try a different search term. + {% else %} + The log file may be empty or not yet created. + {% endif %} +
+ {% endif %} +
+{% endblock %}