makes logger much more beautiful
This commit is contained in:
parent
4a10052eae
commit
24f9eb5766
@ -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)
|
||||
|
@ -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 =
|
||||
'<tr><td colspan="5" class="text-center">Loading logs...</td></tr>';
|
||||
'<tr><td colspan="5" class="text-center"><div class="spinner-border spinner-border-sm text-primary" role="status"><span class="visually-hidden">Loading...</span></div> Loading logs...</td></tr>';
|
||||
|
||||
// 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 {
|
||||
<td>${log.description || ""}</td>
|
||||
`;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
<meta name="keywords" content="science, papers, research, management" />
|
||||
<title>{{ app_title }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Optional Alpine.js -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
|
@ -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 %}
|
||||
<div class="container-fluid mt-4">
|
||||
<h1><i class="fas fa-list-alt"></i> Activity Logs</h1>
|
||||
<h1><i class="bi bi-list-ul"></i> Activity Logs</h1>
|
||||
|
||||
<!-- Include standardized flash messages -->
|
||||
{% include "partials/flash_messages.html.jinja" %}
|
||||
@ -79,56 +88,66 @@
|
||||
<!-- Filter Panel -->
|
||||
<div class="filter-panel">
|
||||
<form id="filterForm" class="row g-3">
|
||||
<div class="col-md-2">
|
||||
<label for="categoryFilter" class="form-label">Category:</label>
|
||||
<select id="categoryFilter" class="form-select form-select-sm">
|
||||
<option value="">All Categories</option>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Categories:</label>
|
||||
<div class="category-checkbox-container p-2"
|
||||
style="max-height: 200px; overflow-y: auto; background-color: white; border: 1px solid #ced4da; border-radius: 0.375rem;">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="selectAllCategories" {% if not
|
||||
selected_categories or selected_categories|length==categories|length %}checked{% endif
|
||||
%}>
|
||||
<label class="form-check-label fw-bold" for="selectAllCategories">
|
||||
All Categories
|
||||
</label>
|
||||
</div>
|
||||
<hr class="my-2">
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat }}" {% if category==cat %}selected{% endif %}>{{ cat.replace('_', '
|
||||
').title() }}</option>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input category-checkbox" type="checkbox" id="category_{{ cat }}"
|
||||
value="{{ cat }}" {% if not selected_categories or cat in selected_categories
|
||||
%}checked{% endif %}>
|
||||
<label class="form-check-label" for="category_{{ cat }}">
|
||||
{{ cat.replace('_', ' ').title() }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label for="statusFilter" class="form-label">Status:</label>
|
||||
<select id="statusFilter" class="form-select form-select-sm">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="success">Success</option>
|
||||
<option value="error">Error</option>
|
||||
<option value="warning">Warning</option>
|
||||
<option value="info">Info</option>
|
||||
<option value="pending">Pending</option>
|
||||
</select>
|
||||
<div class="col-md-3">
|
||||
<div class="row">
|
||||
<label for="statusFilter" class="form-label">Status:</label>
|
||||
<select id="statusFilter" class="form-select form-select-sm">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="success">Success</option>
|
||||
<option value="error">Error</option>
|
||||
<option value="warning">Warning</option>
|
||||
<option value="info">Info</option>
|
||||
<option value="pending">Pending</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-3">
|
||||
<label for="startDate" class="form-label">Start Date:</label>
|
||||
<input type="date" id="startDate" class="form-control form-control-sm"
|
||||
value="{{ start_date or '' }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label for="endDate" class="form-label">End Date:</label>
|
||||
<label for="endDate" class="form-label mt-2">End Date:</label>
|
||||
<input type="date" id="endDate" class="form-control form-control-sm" value="{{ end_date or '' }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="searchTerm" class="form-label">Search:</label>
|
||||
<input type="text" id="searchTerm" class="form-control form-control-sm"
|
||||
placeholder="Search action or description..." value="{{ search_term or '' }}">
|
||||
placeholder="Search in actions and descriptions" value="{{ search_term or '' }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-1">
|
||||
<label class="form-label"> </label>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" id="applyFilters" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-filter"></i> Filter
|
||||
</button>
|
||||
<button type="button" id="clearFilters" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-12 d-flex justify-content-end mt-3">
|
||||
<button type="button" id="clearFilters" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-x"></i> Clear Filters
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -149,10 +168,10 @@
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" id="refreshLogs" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-sync-alt"></i> Refresh
|
||||
<i class="bi bi-arrow-clockwise"></i> Refresh
|
||||
</button>
|
||||
<button type="button" id="downloadLogs" class="btn btn-outline-success btn-sm">
|
||||
<i class="fas fa-download"></i> Download CSV
|
||||
<i class="bi bi-download"></i> Download CSV
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -167,13 +186,15 @@
|
||||
<th style="width: 180px;">Action</th>
|
||||
<th style="width: 100px;">Status</th>
|
||||
<th>Description</th>
|
||||
<th style="width: 60px;">Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="logsTableBody">
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4">
|
||||
<i class="fas fa-spinner fa-spin"></i> Loading logs...
|
||||
<td colspan="5" class="text-center py-4">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
Loading logs...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -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);
|
||||
});
|
||||
</script>
|
||||
{% endblock scripts %}
|
Loading…
x
Reference in New Issue
Block a user