diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 53c4394..ebfcdb2 100644 Binary files a/routes/__pycache__/main.cpython-313.pyc and b/routes/__pycache__/main.cpython-313.pyc differ diff --git a/routes/main.py b/routes/main.py index a3ac78f..62bd3a6 100644 --- a/routes/main.py +++ b/routes/main.py @@ -13,6 +13,7 @@ from forms import CompanySettingsForm from utils import log_event, create_notification, get_unread_count from io import StringIO import csv +from flask_wtf.csrf import generate_csrf # Set up logging to show in console logging.basicConfig( @@ -689,7 +690,8 @@ def init_routes(main_bp): current_page=current_page, users=users, email_templates=email_templates, - form=company_form) + form=company_form, + csrf_token=generate_csrf()) @main_bp.route('/settings/colors', methods=['POST']) @login_required @@ -966,7 +968,7 @@ def init_routes(main_bp): date_range=date_range, user_id=user_id, users=users, - csrf_token=session.get('csrf_token')) + csrf_token=generate_csrf()) # For full page requests, render the full settings page site_settings = SiteSettings.get_settings() @@ -979,7 +981,65 @@ def init_routes(main_bp): total_pages=total_pages, current_page=page, users=users, - csrf_token=session.get('csrf_token')) + csrf_token=generate_csrf()) + + @main_bp.route('/api/events') + @login_required + def get_events(): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + # Get filter parameters + event_type = request.args.get('event_type') + date_range = request.args.get('date_range', '7d') + user_id = request.args.get('user_id') + page = request.args.get('page', 1, type=int) + per_page = 10 + + # Calculate date range + end_date = datetime.utcnow() + if date_range == '24h': + start_date = end_date - timedelta(days=1) + elif date_range == '7d': + start_date = end_date - timedelta(days=7) + elif date_range == '30d': + start_date = end_date - timedelta(days=30) + else: + start_date = None + + # Build query + query = Event.query + + if event_type: + query = query.filter_by(event_type=event_type) + if start_date: + query = query.filter(Event.timestamp >= start_date) + if user_id: + query = query.filter_by(user_id=user_id) + + # Get total count for pagination + total_events = query.count() + total_pages = (total_events + per_page - 1) // per_page + + # Get paginated events + events = query.order_by(Event.timestamp.desc()).paginate(page=page, per_page=per_page) + + return jsonify({ + 'events': [{ + 'id': event.id, + 'event_type': event.event_type, + 'timestamp': event.timestamp.isoformat(), + 'user': { + 'id': event.user.id, + 'username': event.user.username, + 'last_name': event.user.last_name + } if event.user else None, + 'ip_address': event.ip_address, + 'details': event.details + } for event in events.items], + 'current_page': page, + 'total_pages': total_pages + }) @main_bp.route('/api/events/') @login_required @@ -1194,7 +1254,7 @@ def init_routes(main_bp): template_id=template_id, users=users, email_templates=email_templates, - csrf_token=session.get('csrf_token')) + csrf_token=generate_csrf()) # For full page requests, render the full settings page site_settings = SiteSettings.get_settings() @@ -1215,7 +1275,7 @@ def init_routes(main_bp): users=users, email_templates=email_templates, form=company_form, - csrf_token=session.get('csrf_token')) + csrf_token=generate_csrf()) @main_bp.route('/settings/mails/') @login_required diff --git a/static/js/events.js b/static/js/events.js index 46acca6..453f527 100644 --- a/static/js/events.js +++ b/static/js/events.js @@ -1,6 +1,6 @@ document.addEventListener('DOMContentLoaded', function() { // Initialize variables - let currentPage = 1; + let currentPage = parseInt(document.getElementById('currentPage').textContent) || 1; let totalPages = parseInt(document.getElementById('totalPages').textContent) || 1; let isFetching = false; @@ -32,107 +32,191 @@ document.addEventListener('DOMContentLoaded', function() { window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`); } + // Function to update pagination UI + function updatePaginationUI(page, total) { + currentPage = page; + totalPages = total; + currentPageSpan.textContent = currentPage; + totalPagesSpan.textContent = totalPages; + prevPageBtn.disabled = currentPage === 1; + nextPageBtn.disabled = currentPage === totalPages; + } + // Function to fetch filtered events function fetchEvents() { if (isFetching) return; isFetching = true; // Show loading state - eventsTableBody.innerHTML = 'Loading...'; + if (eventsTableBody) { + eventsTableBody.innerHTML = 'Loading...'; + } const params = new URLSearchParams({ - tab: 'events', - page: currentPage, event_type: eventTypeFilter.value, date_range: dateRangeFilter.value, user_id: userFilter.value, - ajax: 'true' + page: currentPage }); - fetch(`${window.location.pathname}?${params.toString()}`, { + const csrfToken = document.querySelector('meta[name="csrf-token"]').content; + fetch(`/api/events?${params.toString()}`, { headers: { - 'X-Requested-With': 'XMLHttpRequest' + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': csrfToken } }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } - return response.text(); + return response.json(); }) - .then(html => { - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const newTableBody = doc.getElementById('eventsTableBody'); + .then(data => { + console.log('Received events data:', data); - if (newTableBody) { - eventsTableBody.innerHTML = newTableBody.innerHTML; - - // Update pagination - const newCurrentPage = parseInt(doc.getElementById('currentPage').textContent) || 1; - const newTotalPages = parseInt(doc.getElementById('totalPages').textContent) || 1; - currentPage = newCurrentPage; - totalPages = newTotalPages; - currentPageSpan.textContent = currentPage; - totalPagesSpan.textContent = totalPages; - - // Update pagination buttons - prevPageBtn.disabled = currentPage <= 1; - nextPageBtn.disabled = currentPage >= totalPages; - - // Update URL - updateURL(); - } else { - console.error('Could not find events table in response'); - eventsTableBody.innerHTML = 'Error loading events'; + if (!eventsTableBody) { + console.error('Could not find events table body element'); + return; } + + // Update table content + let tableHtml = ''; + if (data.events && data.events.length > 0) { + data.events.forEach(event => { + tableHtml += ` + + ${new Date(event.timestamp).toLocaleString()} + + + ${formatEventType(event.event_type)} + + + ${event.user ? `${event.user.username} ${event.user.last_name}` : 'Unknown'} + + + + ${event.ip_address || '-'} + + `; + }); + } else { + tableHtml = 'No events found'; + } + + // Update the table body + eventsTableBody.innerHTML = tableHtml; + console.log('Updated table content with', data.events.length, 'events'); + + // Update pagination + updatePaginationUI(data.current_page, data.total_pages); + + // Update URL + updateURL(); }) .catch(error => { console.error('Error fetching events:', error); - eventsTableBody.innerHTML = 'Error loading events'; + if (eventsTableBody) { + eventsTableBody.innerHTML = 'Error loading events'; + } }) .finally(() => { isFetching = false; }); } + // Helper function to get badge class based on event type + function getEventBadgeClass(eventType) { + const badgeClasses = { + 'user_login': 'bg-info', + 'user_logout': 'bg-info', + 'user_create': 'bg-success', + 'user_delete': 'bg-danger', + 'user_update': 'bg-warning', + 'file_upload': 'bg-success', + 'file_delete': 'bg-danger', + 'file_download': 'bg-info', + 'file_preview': 'bg-info', + 'file_restore': 'bg-warning', + 'file_move': 'bg-warning', + 'file_rename': 'bg-warning', + 'file_star': 'bg-warning', + 'file_unstar': 'bg-warning', + 'file_delete_permanent': 'bg-danger', + 'folder_create': 'bg-success', + 'room_create': 'bg-success', + 'room_delete': 'bg-danger', + 'room_update': 'bg-warning', + 'room_open': 'bg-info', + 'room_member_add': 'bg-success', + 'room_member_remove': 'bg-danger', + 'room_member_permissions_update': 'bg-warning', + 'room_permission_update': 'bg-warning', + 'conversation_create': 'bg-success', + 'conversation_update': 'bg-warning', + 'conversation_delete': 'bg-danger', + 'conversation_open': 'bg-info', + 'message_create': 'bg-success', + 'attachment_download': 'bg-info' + }; + return badgeClasses[eventType] || 'bg-secondary'; + } + + // Helper function to format event type for display + function formatEventType(eventType) { + return eventType.split('_') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + } + // Function to load event details function loadEventDetails(eventId) { console.log('Loading details for event:', eventId); - fetch(`/api/events/${eventId}`) - .then(response => { - console.log('Response status:', response.status); - return response.json(); - }) - .then(data => { - console.log('Received event data:', data); - - // Format the details for display - const formattedDetails = {}; - - // Handle details separately - if (data.details) { - if (typeof data.details === 'object') { - formattedDetails['Details'] = JSON.stringify(data.details, null, 2); - } else { - formattedDetails['Details'] = data.details; - } + const csrfToken = document.querySelector('meta[name="csrf-token"]').content; + fetch(`/api/events/${eventId}`, { + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': csrfToken + } + }) + .then(response => { + console.log('Response status:', response.status); + return response.json(); + }) + .then(data => { + console.log('Received event data:', data); + + // Format the details for display + const formattedDetails = {}; + + // Handle details separately + if (data.details) { + if (typeof data.details === 'object') { + formattedDetails['Details'] = JSON.stringify(data.details, null, 2); } else { - formattedDetails['Details'] = 'No additional details'; + formattedDetails['Details'] = data.details; } - - // Convert to formatted string - const detailsText = Object.entries(formattedDetails) - .map(([key, value]) => `${key}: ${value}`) - .join('\n\n'); - - console.log('Formatted details:', detailsText); - eventDetailsContent.textContent = detailsText; - }) - .catch(error => { - console.error('Error loading event details:', error); - eventDetailsContent.textContent = 'Error loading event details. Please try again.'; - }); + } else { + formattedDetails['Details'] = 'No additional details'; + } + + // Convert to formatted string + const detailsText = Object.entries(formattedDetails) + .map(([key, value]) => `${key}: ${value}`) + .join('\n\n'); + + console.log('Formatted details:', detailsText); + eventDetailsContent.textContent = detailsText; + }) + .catch(error => { + console.error('Error loading event details:', error); + eventDetailsContent.textContent = 'Error loading event details. Please try again.'; + }); } // Add event listeners for filters with debounce @@ -187,8 +271,6 @@ document.addEventListener('DOMContentLoaded', function() { userFilter.value = urlParams.get('user_id') || ''; currentPage = parseInt(urlParams.get('page')) || 1; - // Initial fetch if filters are set - if (eventTypeFilter.value || dateRangeFilter.value !== '24h' || userFilter.value) { - fetchEvents(); - } + // Initial fetch to ensure pagination is correct + fetchEvents(); }); \ No newline at end of file diff --git a/static/js/settings.js b/static/js/settings.js index a3c748a..3d1b0d3 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -341,10 +341,12 @@ document.addEventListener('DOMContentLoaded', function() { const formData = new FormData(companyInfoForm); const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); - formData.append('csrf_token', csrfToken); fetch(companyInfoForm.action, { method: 'POST', + headers: { + 'X-CSRF-Token': csrfToken + }, body: formData }) .then(response => { diff --git a/templates/settings/settings.html b/templates/settings/settings.html index b2c0f09..c57a6e7 100644 --- a/templates/settings/settings.html +++ b/templates/settings/settings.html @@ -75,12 +75,12 @@
- {{ company_info_tab(site_settings, form) }} + {{ company_info_tab(site_settings, form, csrf_token) }}
- {{ email_templates_tab(email_templates) }} + {{ email_templates_tab(email_templates, csrf_token) }}
diff --git a/templates/settings/tabs/company_info.html b/templates/settings/tabs/company_info.html index c468131..dd9b609 100644 --- a/templates/settings/tabs/company_info.html +++ b/templates/settings/tabs/company_info.html @@ -1,11 +1,11 @@ -{% macro company_info_tab(site_settings, form) %} +{% macro company_info_tab(site_settings, form, csrf_token) %}
- {{ form.csrf_token }} +
diff --git a/templates/settings/tabs/email_templates.html b/templates/settings/tabs/email_templates.html index 4cfc7b5..4ccefd1 100644 --- a/templates/settings/tabs/email_templates.html +++ b/templates/settings/tabs/email_templates.html @@ -1,4 +1,4 @@ -{% macro email_templates_tab(templates) %} +{% macro email_templates_tab(templates, csrf_token) %}
@@ -33,24 +33,27 @@
-
+
@@ -241,7 +244,8 @@ document.addEventListener('DOMContentLoaded', function() { } // Handle template save - $('#saveTemplate').on('click', function() { + $('#templateForm').on('submit', function(event) { + event.preventDefault(); const templateId = $('#templateSelect').val(); const subject = $('#templateSubject').val(); const body = $('#templateBody').summernote('code'); @@ -252,7 +256,7 @@ document.addEventListener('DOMContentLoaded', function() { } // Show loading state - const saveButton = this; + const saveButton = this.querySelector('button[type="submit"]'); const originalText = saveButton.innerHTML; saveButton.disabled = true; saveButton.innerHTML = 'Saving...'; diff --git a/templates/settings/tabs/events.html b/templates/settings/tabs/events.html index e6418c1..d1f0a6c 100644 --- a/templates/settings/tabs/events.html +++ b/templates/settings/tabs/events.html @@ -196,140 +196,6 @@
- - {% endmacro %} {% block content %} diff --git a/templates/settings/tabs/mails.html b/templates/settings/tabs/mails.html index be013ae..59c816a 100644 --- a/templates/settings/tabs/mails.html +++ b/templates/settings/tabs/mails.html @@ -173,7 +173,12 @@