From e98e1422a073e0063fda81d013bbeddd8fa48d10 Mon Sep 17 00:00:00 2001 From: pjpatil12 Date: Sat, 1 Aug 2026 15:41:26 +0530 Subject: [PATCH] Add Activity log Service and model --- app/__init__.py | 4 +- app/routes/activity_routes.py | 59 +++++++ app/services/activity_service.py | 56 ++++++ app/templates/activity/activity_log.html | 208 +++++++++++++++++++++++ app/templates/base.html | 51 ++---- app/utils/file_utils.py | 15 +- 6 files changed, 350 insertions(+), 43 deletions(-) create mode 100644 app/routes/activity_routes.py create mode 100644 app/services/activity_service.py create mode 100644 app/templates/activity/activity_log.html diff --git a/app/__init__.py b/app/__init__.py index eb152cb..99e90a9 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -37,6 +37,7 @@ def register_blueprints(app): from app.routes.file_format import file_format_bp # new + from app.routes.activity_routes import activity_bp from app.routes.engineering import engi_bp app.register_blueprint(auth_bp) @@ -49,8 +50,9 @@ def register_blueprints(app): app.register_blueprint(file_format_bp) # new + app.register_blueprint(activity_bp) app.register_blueprint(engi_bp) - + def register_error_handlers(app): diff --git a/app/routes/activity_routes.py b/app/routes/activity_routes.py new file mode 100644 index 0000000..afd1906 --- /dev/null +++ b/app/routes/activity_routes.py @@ -0,0 +1,59 @@ +from flask import Blueprint, render_template, request, send_file, abort +import os + +from app.utils.helpers import login_required +from app.utils.file_utils import get_logs_folder , ALLOWED_LOG_FILE +from app.services.activity_service import ActivityService + +activity_bp = Blueprint("activity", __name__, url_prefix="/activity") + +# call activity_log page +@activity_bp.route("/") +@login_required +def activity(): + file_name = request.args.get("file", "app.log") + search = request.args.get("search", "") + level = request.args.get("level", "").upper() + user = request.args.get("user", "") + from_date = request.args.get("from_date", "") + to_date = request.args.get("to_date", "") + + logs = ActivityService.read_logs( + file_name=file_name, + search=search, + level=level, + user=user, + from_date=from_date, + to_date=to_date + ) + + return render_template( + "activity/activity_log.html", + logs=logs, + file_name=file_name, + search=search, + level=level, + user=user, + from_date=from_date, + to_date=to_date + ) + +# Download activity_log files +@activity_bp.route("/logs") +@login_required +def download_log(): + + filename = request.args.get("file", "app.log") + if filename not in ALLOWED_LOG_FILE: + abort(400, "Invalid log file.") + + file_path = os.path.join(get_logs_folder(), filename) + if not os.path.isfile(file_path): + abort(404, "Log file not found.") + + return send_file( + file_path, + as_attachment=True, + download_name=filename, + mimetype="text/plain" + ) \ No newline at end of file diff --git a/app/services/activity_service.py b/app/services/activity_service.py new file mode 100644 index 0000000..a44bb4f --- /dev/null +++ b/app/services/activity_service.py @@ -0,0 +1,56 @@ +import re +from pathlib import Path + +# Activity log Service +class ActivityService: + + @staticmethod + def read_logs(file_name="", search="", level="", user="", from_date="", to_date=""): + + file = Path("logs") / file_name + + if not file.exists(): + return [] + + logs = [] + + with open(file, "r", encoding="utf-8") as f: + + for line in reversed(f.readlines()): + # Example: + # 2026-08-01 10:15:55 | INFO | admin | Dashboard | Dashboard Opened + parts = [x.strip() for x in line.split("|")] + + if len(parts) < 5: + continue + + record = { + "date": parts[0], + "level": parts[1], + "user": parts[2], + "module": parts[3], + "message": "|".join(parts[4:]) + } + + # Search Filter + if search and search.lower() not in line.lower(): + continue + + # Level Filter + if level and record["level"] != level: + continue + + # User Filter + if user and user.lower() not in record["user"].lower(): + continue + + # Date Filter + if from_date and record["date"][:10] < from_date: + continue + + if to_date and record["date"][:10] > to_date: + continue + + logs.append(record) + + return logs \ No newline at end of file diff --git a/app/templates/activity/activity_log.html b/app/templates/activity/activity_log.html new file mode 100644 index 0000000..14451c4 --- /dev/null +++ b/app/templates/activity/activity_log.html @@ -0,0 +1,208 @@ +{% extends "base.html" %} +{% block content %} + +
+ + +
+
+
+
+

Activity Logs

+ View application logs, search records and download log files. +
+ + + +
+
+
+ + +
+ +
+
+ Filters +
+
+ +
+ +
+
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ +
+ + +
+ + + + + +
+ +
+
+ +
+ + +
+ +
+
+
+ + Log Entries +
+ + + {{ logs|length }} Records + +
+
+ + +
+
+ + + + + + + + + + + + + + + + {% for log in logs %} + + + + + + + + + + {% endfor %} + + +
Sr NoDate & TimeUserLevelModuleActivity
{{ loop.index }}{{ log.date }}{{ log.user }} + {% if log.level=="INFO" %} + INFO + {% elif log.level=="WARNING" %} + WARNING + {% else %} + ERROR + {% endif %} + {{ log.module }}{{ log.message }}
+ +
+
+
+
+ + + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 17bb1cc..d8c9c17 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -168,6 +168,12 @@ Dashboard +
  • + + + Activity Log + +
  • @@ -188,8 +194,7 @@
    -
    +
    {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} @@ -230,17 +235,13 @@
    -
    +
    -
    @@ -298,80 +299,54 @@ diff --git a/app/utils/file_utils.py b/app/utils/file_utils.py index 18a422b..2c992c9 100644 --- a/app/utils/file_utils.py +++ b/app/utils/file_utils.py @@ -5,7 +5,14 @@ from flask import current_app # file extension ALLOWED_EXTENSIONS = {"xlsx", "xls", "csv"} +# .log file allowed +ALLOWED_LOG_FILE = { + "app.log", + "debug.log", + "error.log" + } +# Get path of format excel folder def get_download_format_folder(): return os.path.join( current_app.root_path, @@ -14,6 +21,7 @@ def get_download_format_folder(): "format" ) +# Get path of uploads folder def get_uploads_folder(): return os.path.join( current_app.root_path, @@ -26,8 +34,7 @@ def ensure_upload_folder(): os.makedirs(get_uploads_folder()) +# Get path of logs folder def get_logs_folder(): - return os.path.join( - current_app.root_path, - "logs" - ) \ No newline at end of file + project_root = os.path.dirname(current_app.root_path) + return os.path.join(project_root, "logs") \ No newline at end of file