From 24f9eb57660cb836ba3eb3a9ac51b6533e3d268d Mon Sep 17 00:00:00 2001 From: Michael Beck Date: Fri, 13 Jun 2025 12:57:54 +0200 Subject: [PATCH] makes logger much more beautiful --- scipaperloader/blueprints/logger.py | 10 +- scipaperloader/static/js/logger-manager.js | 150 ++++++++++++++++----- scipaperloader/templates/base.html.jinja | 1 + scipaperloader/templates/logs.html.jinja | 113 +++++++++------- 4 files changed, 189 insertions(+), 85 deletions(-) diff --git a/scipaperloader/blueprints/logger.py b/scipaperloader/blueprints/logger.py index 2f8e7ea..fc47b40 100644 --- a/scipaperloader/blueprints/logger.py +++ b/scipaperloader/blueprints/logger.py @@ -15,7 +15,7 @@ def list_logs(): # The actual data loading will be handled by JavaScript via the API endpoint # Get filter parameters for initial state - category = request.args.get("category") + categories_param = request.args.getlist("category") # Get multiple categories start_date = request.args.get("start_date") end_date = request.args.get("end_date") search_term = request.args.get("search_term") @@ -28,7 +28,7 @@ def list_logs(): return render_template( "logs.html.jinja", categories=categories, - category=category, + selected_categories=categories_param, # Pass selected categories start_date=start_date, end_date=end_date, search_term=search_term, @@ -39,15 +39,15 @@ def list_logs(): @bp.route("/download") def download_logs(): # Filters - reuse logic from list_logs - category = request.args.get("category") + categories = request.args.getlist("category") # Get multiple categories 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 categories: + query = query.filter(ActivityLog.category.in_(categories)) if start_date: start_date_dt = datetime.datetime.strptime(start_date, "%Y-%m-%d") query = query.filter(ActivityLog.timestamp >= start_date_dt) diff --git a/scipaperloader/static/js/logger-manager.js b/scipaperloader/static/js/logger-manager.js index d32e47a..7744ca0 100644 --- a/scipaperloader/static/js/logger-manager.js +++ b/scipaperloader/static/js/logger-manager.js @@ -27,26 +27,27 @@ class LoggerManager { initElements() { // Form elements - this.filtersForm = document.getElementById("logFiltersForm"); - this.categorySelect = document.getElementById("category"); - this.statusSelect = document.getElementById("status"); - this.startDateInput = document.getElementById("start_date"); - this.endDateInput = document.getElementById("end_date"); - this.searchTermInput = document.getElementById("search_term"); + this.filtersForm = document.getElementById("filterForm"); + this.categoryCheckboxes = document.querySelectorAll(".category-checkbox"); + this.selectAllCategories = document.getElementById("selectAllCategories"); + this.statusSelect = document.getElementById("statusFilter"); + this.startDateInput = document.getElementById("startDate"); + this.endDateInput = document.getElementById("endDate"); + this.searchTermInput = document.getElementById("searchTerm"); this.clearFiltersBtn = document.getElementById("clearFilters"); this.downloadLogsBtn = document.getElementById("downloadLogs"); this.refreshLogsBtn = document.getElementById("refreshLogs"); // Logs display elements this.logsTableBody = document.getElementById("logsTableBody"); - this.pageSizeSelect = document.getElementById("logPageSize"); + this.pageSizeSelect = document.getElementById("pageSize"); // Pagination elements this.paginationContainer = document.getElementById("logsPagination"); - this.paginationInfo = document.getElementById("logsPaginationInfo"); - this.prevPageBtn = document.getElementById("logsPrevPage"); - this.nextPageBtn = document.getElementById("logsNextPage"); - this.currentPageSpan = document.getElementById("logsCurrentPage"); + this.paginationInfo = document.getElementById("paginationDetails"); + this.prevPageBtn = document.getElementById("prevPage"); + this.nextPageBtn = document.getElementById("nextPage"); + this.currentPageSpan = document.getElementById("currentPageSpan"); // Modal this.logModal = new ModalHandler("logDetailModal", "log-detail-content"); @@ -61,20 +62,37 @@ class LoggerManager { }); } - // Individual filter changes for immediate application - [ - this.categorySelect, - this.statusSelect, - this.startDateInput, - this.endDateInput, - ].forEach((element) => { - if (element) { - element.addEventListener("change", () => { - this.applyFilters(); + // Handle "Select All" checkbox for categories + if (this.selectAllCategories) { + this.selectAllCategories.addEventListener("change", () => { + const isChecked = this.selectAllCategories.checked; + this.categoryCheckboxes.forEach((checkbox) => { + checkbox.checked = isChecked; }); - } + this.applyFilters(); + }); + } + + // Handle individual category checkboxes + this.categoryCheckboxes.forEach((checkbox) => { + checkbox.addEventListener("change", () => { + // Update "Select All" checkbox state + this.updateSelectAllState(); + this.applyFilters(); + }); }); + // Individual filter changes for immediate application + [this.statusSelect, this.startDateInput, this.endDateInput].forEach( + (element) => { + if (element) { + element.addEventListener("change", () => { + this.applyFilters(); + }); + } + } + ); + // Search term with debounce if (this.searchTermInput) { let searchTimeout; @@ -139,11 +157,43 @@ class LoggerManager { } } - applyInitialFilters() { - // Set form values from initial filters - if (this.categorySelect && this.initialFilters.category) { - this.categorySelect.value = this.initialFilters.category; + updateSelectAllState() { + const checkedCount = Array.from(this.categoryCheckboxes).filter( + (cb) => cb.checked + ).length; + const totalCount = this.categoryCheckboxes.length; + + if (checkedCount === 0) { + this.selectAllCategories.checked = false; + this.selectAllCategories.indeterminate = false; + } else if (checkedCount === totalCount) { + this.selectAllCategories.checked = true; + this.selectAllCategories.indeterminate = false; + } else { + this.selectAllCategories.checked = false; + this.selectAllCategories.indeterminate = true; } + } + + getSelectedCategories() { + return Array.from(this.categoryCheckboxes) + .filter((checkbox) => checkbox.checked) + .map((checkbox) => checkbox.value); + } + + applyInitialFilters() { + // Set category checkboxes from initial filters + if (this.initialFilters.category) { + const selectedCategories = Array.isArray(this.initialFilters.category) + ? this.initialFilters.category + : [this.initialFilters.category]; + + this.categoryCheckboxes.forEach((checkbox) => { + checkbox.checked = selectedCategories.includes(checkbox.value); + }); + this.updateSelectAllState(); + } + if (this.startDateInput && this.initialFilters.start_date) { this.startDateInput.value = this.initialFilters.start_date; } @@ -157,8 +207,10 @@ class LoggerManager { applyFilters() { // Collect current filter values + const selectedCategories = this.getSelectedCategories(); + this.filters = { - category: this.categorySelect?.value || "", + category: selectedCategories, // Now an array status: this.statusSelect?.value || "", start_date: this.startDateInput?.value || "", end_date: this.endDateInput?.value || "", @@ -176,8 +228,15 @@ class LoggerManager { } clearAllFilters() { - // Clear all form fields - if (this.categorySelect) this.categorySelect.value = ""; + // Clear all category checkboxes and select all + this.categoryCheckboxes.forEach((checkbox) => { + checkbox.checked = true; // Default to all selected + }); + if (this.selectAllCategories) { + this.selectAllCategories.checked = true; + this.selectAllCategories.indeterminate = false; + } + if (this.statusSelect) this.statusSelect.value = ""; if (this.startDateInput) this.startDateInput.value = ""; if (this.endDateInput) this.endDateInput.value = ""; @@ -193,7 +252,7 @@ class LoggerManager { try { // Show loading state this.logsTableBody.innerHTML = - 'Loading logs...'; + '
Loading...
Loading logs...'; // Build query parameters const params = new URLSearchParams({ @@ -204,7 +263,14 @@ class LoggerManager { // Add filters to query Object.entries(this.filters).forEach(([key, value]) => { if (value) { - params.append(key, value); + if (key === "category" && Array.isArray(value)) { + // Handle multiple categories + value.forEach((cat) => { + if (cat) params.append("category", cat); + }); + } else if (value) { + params.append(key, value); + } } }); @@ -239,7 +305,7 @@ class LoggerManager { logs.forEach((log) => { const row = document.createElement("tr"); - row.className = "log-item"; + row.className = "log-entry"; row.setAttribute("data-log-id", log.id); // Format timestamp @@ -259,7 +325,7 @@ class LoggerManager { ${log.description || ""} `; - // Add click handler for details modal + // Add click handler for details modal - whole row is clickable row.addEventListener("click", () => { const url = `/logs/${log.id}/detail`; this.logModal.loadAndShow(url, "Error loading log details."); @@ -360,7 +426,14 @@ class LoggerManager { Object.entries(this.filters).forEach(([key, value]) => { if (value) { - params.append(key, value); + if (key === "category" && Array.isArray(value)) { + // Handle multiple categories + value.forEach((cat) => { + if (cat) params.append("category", cat); + }); + } else if (value) { + params.append(key, value); + } } }); @@ -376,7 +449,14 @@ class LoggerManager { Object.entries(this.filters).forEach(([key, value]) => { if (value) { - params.append(key, value); + if (key === "category" && Array.isArray(value)) { + // Handle multiple categories + value.forEach((cat) => { + if (cat) params.append("category", cat); + }); + } else if (value) { + params.append(key, value); + } } }); diff --git a/scipaperloader/templates/base.html.jinja b/scipaperloader/templates/base.html.jinja index 9ea8b75..f9a858e 100644 --- a/scipaperloader/templates/base.html.jinja +++ b/scipaperloader/templates/base.html.jinja @@ -7,6 +7,7 @@ {{ app_title }} + diff --git a/scipaperloader/templates/logs.html.jinja b/scipaperloader/templates/logs.html.jinja index 7ff7e7d..cdf1c46 100644 --- a/scipaperloader/templates/logs.html.jinja +++ b/scipaperloader/templates/logs.html.jinja @@ -41,6 +41,15 @@ font-weight: 600; } + .log-entry { + cursor: pointer; + transition: background-color 0.2s ease; + } + + .log-entry:hover { + background-color: #f8f9fa; + } + .pagination-info { font-size: 0.875rem; color: #6c757d; @@ -70,7 +79,7 @@ {% block content %}
-

Activity Logs

+

Activity Logs

{% include "partials/flash_messages.html.jinja" %} @@ -79,56 +88,66 @@
-
- - + +
+
{% for cat in categories %} - +
+ + +
{% endfor %} - +
-
- - +
+
+ + +
+
-
+
-
-
- +
+ placeholder="Search in actions and descriptions" value="{{ search_term or '' }}">
-
- -
- - -
+
+
@@ -149,10 +168,10 @@
@@ -167,13 +186,15 @@ Action Status Description - Details - - Loading logs... + +
+ Loading... +
+ Loading logs... @@ -226,15 +247,17 @@ document.addEventListener('DOMContentLoaded', function () { // Initialize the logger manager window.loggerManager = new LoggerManager({ - initialCategory: "{{ category or '' }}", - initialStartDate: "{{ start_date or '' }}", - initialEndDate: "{{ end_date or '' }}", - initialSearchTerm: "{{ search_term or '' }}" + initialFilters: { + category: {{ selected_categories | tojson }}, + start_date: "{{ start_date or '' }}", + end_date: "{{ end_date or '' }}", + search_term: "{{ search_term or '' }}" + } }); - // Set up modal handler for log details - const logModal = new ModalHandler('logDetailModal', 'log-detail-content'); - window.loggerManager.setModalHandler(logModal); + // Set up modal handler for log details + const logModal = new ModalHandler('logDetailModal', 'log-detail-content'); + window.loggerManager.setModalHandler(logModal); }); {% endblock scripts %} \ No newline at end of file