diff --git a/scipaperloader/blueprints/__init__.py b/scipaperloader/blueprints/__init__.py index c3b1eac..ad98b8b 100644 --- a/scipaperloader/blueprints/__init__.py +++ b/scipaperloader/blueprints/__init__.py @@ -5,6 +5,7 @@ from .main import bp as main_bp from .papers import bp as papers_bp from .upload import bp as upload_bp from .schedule import bp as schedule_bp +from .logger import bp as logger_bp def register_blueprints(app: Flask): @@ -12,4 +13,5 @@ def register_blueprints(app: Flask): app.register_blueprint(main_bp) app.register_blueprint(papers_bp, url_prefix='/papers') app.register_blueprint(upload_bp, url_prefix='/upload') - app.register_blueprint(schedule_bp, url_prefix='/schedule') \ No newline at end of file + app.register_blueprint(schedule_bp, url_prefix='/schedule') + app.register_blueprint(logger_bp, url_prefix='/logs') \ No newline at end of file diff --git a/scipaperloader/blueprints/logger.py b/scipaperloader/blueprints/logger.py new file mode 100644 index 0000000..97f4639 --- /dev/null +++ b/scipaperloader/blueprints/logger.py @@ -0,0 +1,108 @@ +"""Logger view.""" +import csv +import io +import datetime +from flask import Blueprint, render_template, request, send_file +from ..db import db +from ..models import ActivityLog, ActivityCategory + +bp = Blueprint("logger", __name__, url_prefix="/logs") + + +@bp.route("/") +def list_logs(): + page = request.args.get("page", 1, type=int) + per_page = 50 + + # Filters + category = request.args.get("category") + start_date = request.args.get("start_date") + end_date = request.args.get("end_date") + search_term = request.args.get("search_term") + + query = ActivityLog.query + + if category: + query = query.filter(ActivityLog.category == category) + if start_date: + start_date_dt = datetime.datetime.strptime(start_date, "%Y-%m-%d") + query = query.filter(ActivityLog.timestamp >= start_date_dt) + if end_date: + end_date_dt = datetime.datetime.strptime(end_date, "%Y-%m-%d") + datetime.timedelta(days=1) + query = query.filter(ActivityLog.timestamp <= end_date_dt) + if search_term: + query = query.filter(db.or_( + ActivityLog.action.contains(search_term), + ActivityLog.description.contains(search_term) + )) + + pagination = query.order_by(ActivityLog.timestamp.desc()).paginate(page=page, per_page=per_page, error_out=False) + + categories = [e.value for e in ActivityCategory] + + return render_template( + "logger.html.jinja", + logs=pagination.items, + pagination=pagination, + categories=categories, + category=category, + start_date=start_date, + end_date=end_date, + search_term=search_term, + app_title="PaperScraper", + ) + + +@bp.route("/download") +def download_logs(): + # Filters - reuse logic from list_logs + category = request.args.get("category") + start_date = request.args.get("start_date") + end_date = request.args.get("end_date") + search_term = request.args.get("search_term") + + query = ActivityLog.query + + if category: + query = query.filter(ActivityLog.category == category) + if start_date: + start_date_dt = datetime.datetime.strptime(start_date, "%Y-%m-%d") + query = query.filter(ActivityLog.timestamp >= start_date_dt) + if end_date: + end_date_dt = datetime.datetime.strptime(end_date, "%Y-%m-%d") + datetime.timedelta(days=1) + query = query.filter(ActivityLog.timestamp <= end_date_dt) + if search_term: + query = query.filter(db.or_( + ActivityLog.action.contains(search_term), + ActivityLog.description.contains(search_term) + )) + + logs = query.order_by(ActivityLog.timestamp.desc()).all() + + # Prepare CSV data + csv_data = io.StringIO() + csv_writer = csv.writer(csv_data) + csv_writer.writerow(["Timestamp", "Category", "Action", "Description", "Extra Data"]) # Header + + for log in logs: + csv_writer.writerow([ + log.timestamp, + log.category, + log.action, + log.description, + log.extra_data # Consider formatting this better + ]) + + # Create response + filename = f"logs_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + return send_file( + io.StringIO(csv_data.getvalue()), + mimetype="text/csv", + as_attachment=True, + download_name=filename + ) + +@bp.route("//detail") +def log_detail(log_id): + log = ActivityLog.query.get_or_404(log_id) + return render_template("partials/log_detail_modal.html.jinja", log=log) \ No newline at end of file diff --git a/scipaperloader/templates/logger.html.jinja b/scipaperloader/templates/logger.html.jinja new file mode 100644 index 0000000..5c0d6be --- /dev/null +++ b/scipaperloader/templates/logger.html.jinja @@ -0,0 +1,117 @@ +{% extends "base.html" %} +{% block content %} +

Activity Logs

+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + Download CSV +
+
+ + + +{% if pagination %} + +{% endif %} + + + + + +{% endblock content %} \ No newline at end of file diff --git a/scipaperloader/templates/nav.html b/scipaperloader/templates/nav.html index cf96974..bd95753 100644 --- a/scipaperloader/templates/nav.html +++ b/scipaperloader/templates/nav.html @@ -38,7 +38,7 @@