diff --git a/scipaperloader/blueprints/scraper.py b/scipaperloader/blueprints/scraper.py index b73e17b..2fae038 100644 --- a/scipaperloader/blueprints/scraper.py +++ b/scipaperloader/blueprints/scraper.py @@ -238,10 +238,51 @@ def get_status(): @bp.route("/logs") def get_logs(): - """Get recent activity logs.""" + """Get recent activity logs with pagination support.""" try: - limit = request.args.get('limit', 50, type=int) - logs = ActivityLog.query.order_by(ActivityLog.timestamp.desc()).limit(limit).all() + # Pagination parameters + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 20, type=int) + + # Legacy limit parameter for backward compatibility + limit = request.args.get('limit', type=int) + if limit and not request.args.get('page'): + # Legacy mode: use limit without pagination + logs = ActivityLog.query.order_by(ActivityLog.timestamp.desc()).limit(limit).all() + return jsonify({ + "success": True, + "logs": [{ + "id": log.id, + "timestamp": log.timestamp.isoformat(), + "action": log.action, + "status": log.status, + "description": log.description, + "category": log.category + } for log in logs] + }) + + # Ensure reasonable per_page limits + per_page = min(per_page, 100) # Cap at 100 items per page + + # Build query with optional filtering + query = ActivityLog.query + + # Filter by categories if specified + categories = request.args.getlist('category') + if categories: + query = query.filter(ActivityLog.category.in_(categories)) + + # Filter by status if specified + status = request.args.get('status') + if status: + query = query.filter(ActivityLog.status == status) + + # Order by most recent first and paginate + pagination = query.order_by(ActivityLog.timestamp.desc()).paginate( + page=page, + per_page=per_page, + error_out=False + ) return jsonify({ "success": True, @@ -251,8 +292,18 @@ def get_logs(): "action": log.action, "status": log.status, "description": log.description, - "category": log.category.name if log.category else None - } for log in logs] + "category": log.category + } for log in pagination.items], + "pagination": { + "page": pagination.page, + "pages": pagination.pages, + "per_page": pagination.per_page, + "total": pagination.total, + "has_next": pagination.has_next, + "has_prev": pagination.has_prev, + "next_num": pagination.next_num if pagination.has_next else None, + "prev_num": pagination.prev_num if pagination.has_prev else None + } }) except Exception as e: diff --git a/scipaperloader/static/js/activity-monitor.js b/scipaperloader/static/js/activity-monitor.js index 120cc72..8831f29 100644 --- a/scipaperloader/static/js/activity-monitor.js +++ b/scipaperloader/static/js/activity-monitor.js @@ -9,6 +9,22 @@ class ActivityMonitor { this.notificationsEnabled = true; this.lastPaperTimestamp = new Date().toISOString(); + // Pagination state + this.currentPage = 1; + this.perPage = 20; + this.statusFilter = ""; + this.totalPages = 1; + this.totalEntries = 0; + + // Pagination elements + this.paginationContainer = document.getElementById("activityPagination"); + this.paginationInfo = document.getElementById("activityPaginationInfo"); + this.prevPageBtn = document.getElementById("activityPrevPage"); + this.nextPageBtn = document.getElementById("activityNextPage"); + this.currentPageSpan = document.getElementById("activityCurrentPage"); + this.pageSizeSelect = document.getElementById("activityPageSize"); + this.statusFilterSelect = document.getElementById("activityStatusFilter"); + this.initEventListeners(); this.setupWebSocket(); } @@ -38,6 +54,45 @@ class ActivityMonitor { } }); }); + + // Pagination event listeners + if (this.prevPageBtn) { + this.prevPageBtn.addEventListener("click", (e) => { + e.preventDefault(); + if (this.currentPage > 1) { + this.currentPage--; + this.loadRecentActivity(); + } + }); + } + + if (this.nextPageBtn) { + this.nextPageBtn.addEventListener("click", (e) => { + e.preventDefault(); + if (this.currentPage < this.totalPages) { + this.currentPage++; + this.loadRecentActivity(); + } + }); + } + + // Page size change + if (this.pageSizeSelect) { + this.pageSizeSelect.addEventListener("change", () => { + this.perPage = parseInt(this.pageSizeSelect.value); + this.currentPage = 1; // Reset to first page + this.loadRecentActivity(); + }); + } + + // Status filter change + if (this.statusFilterSelect) { + this.statusFilterSelect.addEventListener("change", () => { + this.statusFilter = this.statusFilterSelect.value; + this.currentPage = 1; // Reset to first page + this.loadRecentActivity(); + }); + } } /** @@ -47,16 +102,35 @@ class ActivityMonitor { if (!this.activityLog) return; try { - const data = await apiRequest( - "/api/activity_logs?category=scraper_activity&category=scraper_command&limit=50" - ); - this.renderActivityLog(data); - console.log("Activity log refreshed with latest data"); + // Build query parameters for pagination + const params = new URLSearchParams({ + page: this.currentPage, + per_page: this.perPage, + }); + + // Add multiple category parameters + params.append("category", "scraper_activity"); + params.append("category", "scraper_command"); + + if (this.statusFilter) { + params.append("status", this.statusFilter); + } + + const data = await apiRequest(`/scraper/logs?${params.toString()}`); + + if (data.success) { + this.renderActivityLog(data.logs); + this.updatePagination(data.pagination); + console.log("Activity log refreshed with latest data"); + } else { + throw new Error(data.message || "Failed to load logs"); + } } catch (error) { console.error("Failed to load activity logs:", error); // If the API endpoint doesn't exist, just show a message this.activityLog.innerHTML = '