diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index 293a4a7..5158dca 100644 Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ diff --git a/routes/__pycache__/auth.cpython-313.pyc b/routes/__pycache__/auth.cpython-313.pyc index 012fb5a..c91e76d 100644 Binary files a/routes/__pycache__/auth.cpython-313.pyc and b/routes/__pycache__/auth.cpython-313.pyc differ diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 41152ba..29df1e0 100644 Binary files a/routes/__pycache__/main.cpython-313.pyc and b/routes/__pycache__/main.cpython-313.pyc differ diff --git a/routes/auth.py b/routes/auth.py index 15fab4e..23dc561 100644 --- a/routes/auth.py +++ b/routes/auth.py @@ -2,6 +2,8 @@ from flask import render_template, request, flash, redirect, url_for from flask_login import login_user, logout_user, login_required, current_user from models import db, User from functools import wraps +from utils import log_event +from datetime import datetime def require_password_change(f): @wraps(f) @@ -26,11 +28,26 @@ def init_routes(auth_bp): user = User.query.filter_by(email=email).first() if not user or not user.check_password(password): + # Log failed login attempt + log_event('user_login', { + 'email': email, + 'success': False, + 'reason': 'invalid_credentials' + }) flash('Please check your login details and try again.', 'danger') return redirect(url_for('auth.login')) login_user(user, remember=remember) + # Log successful login + log_event('user_login', { + 'user_id': user.id, + 'email': email, + 'success': True, + 'remember': remember, + 'using_default_password': password == 'changeme' + }, user.id) + # Check if user is using default password if password == 'changeme': flash('Please change your password before continuing.', 'warning') @@ -69,6 +86,16 @@ def init_routes(auth_bp): db.session.add(new_user) db.session.commit() + # Log user registration + log_event('user_register', { + 'email': email, + 'username': username, + 'timestamp': datetime.utcnow().isoformat(), + 'ip_address': request.remote_addr, + 'user_agent': request.user_agent.string, + 'registration_method': 'web_form' + }, new_user.id) + login_user(new_user) return redirect(url_for('main.dashboard')) @@ -77,6 +104,12 @@ def init_routes(auth_bp): @auth_bp.route('/logout') @login_required def logout(): + # Log logout event + log_event('user_logout', { + 'user_id': current_user.id, + 'email': current_user.email + }, current_user.id) + logout_user() return redirect(url_for('auth.login')) @@ -98,6 +131,14 @@ def init_routes(auth_bp): current_user.set_password(new_password) db.session.commit() + + # Log password change + log_event('user_update', { + 'user_id': current_user.id, + 'email': current_user.email, + 'update_type': 'password_change' + }, current_user.id) + flash('Password changed successfully!', 'success') return redirect(url_for('main.dashboard')) diff --git a/routes/main.py b/routes/main.py index 53fe545..169f103 100644 --- a/routes/main.py +++ b/routes/main.py @@ -1,4 +1,4 @@ -from flask import render_template, Blueprint, redirect, url_for, request, flash, Response, jsonify +from flask import render_template, Blueprint, redirect, url_for, request, flash, Response, jsonify, session from flask_login import current_user, login_required from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings, Event from routes.auth import require_password_change @@ -9,7 +9,6 @@ from datetime import datetime, timedelta import logging import sys import time -from flask import session # Set up logging to show in console logging.basicConfig( @@ -356,11 +355,62 @@ def init_routes(main_bp): site_settings = SiteSettings.get_settings() active_tab = request.args.get('tab', 'colors') + + # Get events data if events tab is active + events = None + total_pages = 1 + current_page = 1 + users = [] + + if active_tab == 'events': + # 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 = 50 + + # 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) + + # Get all users for filter dropdown + users = User.query.order_by(User.username).all() + return render_template('settings/settings.html', primary_color=site_settings.primary_color, secondary_color=site_settings.secondary_color, active_tab=active_tab, - site_settings=site_settings) + site_settings=site_settings, + events=events.items if events else None, + total_pages=total_pages, + current_page=current_page, + users=users, + csrf_token=session.get('csrf_token')) @main_bp.route('/settings/colors', methods=['POST']) @login_required @@ -578,13 +628,29 @@ def init_routes(main_bp): # Get all users for filter dropdown users = User.query.order_by(User.username).all() - return render_template('settings/tabs/events.html', + # Check if this is an AJAX request + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + logger.info(f"Processing AJAX request for events. Found {len(events.items)} events") + return render_template('settings/tabs/events.html', + events=events.items, + total_pages=total_pages, + current_page=page, + event_type=event_type, + date_range=date_range, + user_id=user_id, + users=users, + csrf_token=session.get('csrf_token')) + + # For full page requests, render the full settings page + site_settings = SiteSettings.get_settings() + return render_template('settings/settings.html', + primary_color=site_settings.primary_color, + secondary_color=site_settings.secondary_color, + active_tab='events', + site_settings=site_settings, events=events.items, total_pages=total_pages, current_page=page, - event_type=event_type, - date_range=date_range, - user_id=user_id, users=users, csrf_token=session.get('csrf_token')) @@ -595,16 +661,33 @@ def init_routes(main_bp): return jsonify({'error': 'Unauthorized'}), 403 event = Event.query.get_or_404(event_id) - return jsonify({ + logger.info(f"Raw event object: {event}") + logger.info(f"Event details type: {type(event.details)}") + logger.info(f"Event details value: {event.details}") + + # Convert details to dict if it's a string + details = event.details + if isinstance(details, str): + try: + import json + details = json.loads(details) + except json.JSONDecodeError: + details = {'raw_details': details} + + # Return the raw event data + response_data = { '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 - }, - 'timestamp': event.timestamp.isoformat(), - 'details': event.details, + } if event.user else None, 'ip_address': event.ip_address, - 'user_agent': event.user_agent - }) \ No newline at end of file + 'user_agent': event.user_agent, + 'details': details + } + + logger.info(f"Sending response: {response_data}") + return jsonify(response_data) \ No newline at end of file diff --git a/static/js/events.js b/static/js/events.js index eee460b..f33b95e 100644 --- a/static/js/events.js +++ b/static/js/events.js @@ -41,15 +41,47 @@ document.addEventListener('DOMContentLoaded', function() { // Function to load event details function loadEventDetails(eventId) { + console.log('Loading details for event:', eventId); fetch(`/api/events/${eventId}`) - .then(response => response.json()) + .then(response => { + console.log('Response status:', response.status); + return response.json(); + }) .then(data => { - const formattedDetails = JSON.stringify(data.details, null, 2); - eventDetailsContent.textContent = formattedDetails; + console.log('Received event data:', data); + + // Format the details for display + const formattedDetails = { + 'Event ID': data.id, + 'Event Type': data.event_type, + 'Timestamp': new Date(data.timestamp).toLocaleString(), + 'User': data.user ? `${data.user.username} (${data.user.last_name})` : 'N/A', + 'IP Address': data.ip_address || 'N/A', + 'User Agent': data.user_agent || 'N/A' + }; + + // Handle details separately + if (data.details) { + if (typeof data.details === 'object') { + formattedDetails['Details'] = JSON.stringify(data.details, null, 2); + } else { + formattedDetails['Details'] = data.details; + } + } 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'; + eventDetailsContent.textContent = 'Error loading event details. Please try again.'; }); } diff --git a/static/js/settings.js b/static/js/settings.js index 108f182..9a6b970 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -13,9 +13,8 @@ document.addEventListener('DOMContentLoaded', function() { const secondaryColorInput = document.getElementById('secondaryColor'); const colorSettingsForm = document.getElementById('colorSettingsForm'); - // Tab persistence - const settingsTabs = document.querySelectorAll('#settingsTabs button[data-bs-toggle="tab"]'); - const tabContent = document.querySelectorAll('.tab-pane'); + // Get all tab buttons + const settingsTabs = document.querySelectorAll('[data-bs-toggle="tab"]'); /** * Activates a specific settings tab and updates the UI accordingly. @@ -24,27 +23,89 @@ document.addEventListener('DOMContentLoaded', function() { * @param {string} tabId - The ID of the tab to activate */ function activateTab(tabId) { - // Remove active class from all tabs and content + // Update URL without reloading the page + const url = new URL(window.location.href); + url.searchParams.set('tab', tabId); + window.history.pushState({}, '', url); + + // Save active tab to localStorage + localStorage.setItem('settingsActiveTab', tabId); + + // Update active state of tabs settingsTabs.forEach(tab => { - tab.classList.remove('active'); - tab.setAttribute('aria-selected', 'false'); + const targetId = tab.getAttribute('data-bs-target').substring(1); + if (targetId === tabId) { + tab.classList.add('active'); + } else { + tab.classList.remove('active'); + } }); - tabContent.forEach(content => { - content.classList.remove('show', 'active'); + + // Update active state of tab panes + document.querySelectorAll('.tab-pane').forEach(pane => { + if (pane.id === tabId) { + pane.classList.add('show', 'active'); + } else { + pane.classList.remove('show', 'active'); + } }); - // Activate the selected tab - const selectedTab = document.querySelector(`#${tabId}-tab`); - const selectedContent = document.getElementById(tabId); - if (selectedTab && selectedContent) { - selectedTab.classList.add('active'); - selectedTab.setAttribute('aria-selected', 'true'); - selectedContent.classList.add('show', 'active'); - // Save to localStorage - localStorage.setItem('settingsActiveTab', tabId); + // If switching to events tab, fetch events data + if (tabId === 'events') { + fetchEvents(); } } + /** + * Fetches events data from the server and updates the events table + * @function + */ + function fetchEvents() { + const url = new URL(window.location.href); + const eventType = url.searchParams.get('event_type') || ''; + const dateRange = url.searchParams.get('date_range') || '7d'; + const userId = url.searchParams.get('user_id') || ''; + const page = url.searchParams.get('page') || 1; + + // Show loading state + const eventsTableBody = document.getElementById('eventsTableBody'); + if (eventsTableBody) { + eventsTableBody.innerHTML = 'Loading events...'; + } + + // Fetch events data + fetch(`/settings/events?event_type=${eventType}&date_range=${dateRange}&user_id=${userId}&page=${page}`, { + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }) + .then(response => response.text()) + .then(html => { + // Extract the events table content from the response + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const newEventsTable = doc.getElementById('eventsTableBody'); + + console.log('Found table body:', newEventsTable); // Debug log + + if (newEventsTable && eventsTableBody) { + eventsTableBody.innerHTML = newEventsTable.innerHTML; + console.log('Updated table content'); // Debug log + } else { + console.error('Could not find events table in response'); + if (eventsTableBody) { + eventsTableBody.innerHTML = 'Error: Could not load events'; + } + } + }) + .catch(error => { + console.error('Error fetching events:', error); + if (eventsTableBody) { + eventsTableBody.innerHTML = 'Error loading events'; + } + }); + } + // Add click event listeners to tabs settingsTabs.forEach(tab => { tab.addEventListener('click', (e) => { diff --git a/templates/settings/settings.html b/templates/settings/settings.html index 94e81c6..55034ef 100644 --- a/templates/settings/settings.html +++ b/templates/settings/settings.html @@ -73,7 +73,7 @@
- {{ events_tab(events, csrf_token) }} + {{ events_tab(events, csrf_token, users) }}
@@ -92,4 +92,5 @@ {% block extra_js %} + {% endblock %} \ No newline at end of file diff --git a/templates/settings/tabs/events.html b/templates/settings/tabs/events.html index 136ab63..66be116 100644 --- a/templates/settings/tabs/events.html +++ b/templates/settings/tabs/events.html @@ -1,4 +1,4 @@ -{% macro events_tab(events, csrf_token) %} +{% macro events_tab(events, csrf_token, users) %}
@@ -41,6 +41,9 @@
@@ -58,87 +61,93 @@ - {% for event in events %} - - {{ event.timestamp.strftime('%Y-%m-%d %H:%M:%S') }} - - {% if event.event_type == 'user_login' %} - User Login - {% elif event.event_type == 'user_logout' %} - User Logout - {% elif event.event_type == 'user_register' %} - User Registration - {% elif event.event_type == 'user_update' %} - User Update - {% elif event.event_type == 'file_upload' %} - File Upload - {% elif event.event_type == 'file_delete' %} - File Delete - {% elif event.event_type == 'file_download' %} - File Download - {% elif event.event_type == 'file_restore' %} - File Restore - {% elif event.event_type == 'file_move' %} - File Move - {% elif event.event_type == 'file_rename' %} - File Rename - {% elif event.event_type == 'file_star' %} - File Star - {% elif event.event_type == 'file_unstar' %} - File Unstar - {% elif event.event_type == 'file_delete_permanent' %} - File Delete Permanent - {% elif event.event_type == 'room_create' %} - Room Create - {% elif event.event_type == 'room_delete' %} - Room Delete - {% elif event.event_type == 'room_update' %} - Room Update - {% elif event.event_type == 'room_open' %} - Room Open - {% elif event.event_type == 'room_list_view' %} - Room List View - {% elif event.event_type == 'room_search' %} - Room Search - {% elif event.event_type == 'room_join' %} - Room Join - {% elif event.event_type == 'room_leave' %} - Room Leave - {% elif event.event_type == 'room_member_permissions_update' %} - Room Member Permissions Update - {% elif event.event_type == 'conversation_create' %} - Conversation Create - {% elif event.event_type == 'conversation_delete' %} - Conversation Delete - {% elif event.event_type == 'message_sent' %} - Message Sent - {% elif event.event_type == 'attachment_download' %} - Attachment Download - {% else %} - {{ event.event_type }} - {% endif %} - - {{ event.user.username }} {% if event.user.last_name %}{{ event.user.last_name }}{% endif %} - - - - {{ event.ip_address or '-' }} - - {% endfor %} + {% if events %} + {% for event in events %} + + {{ event.timestamp.strftime('%Y-%m-%d %H:%M:%S') }} + + {% if event.event_type == 'user_login' %} + User Login + {% elif event.event_type == 'user_logout' %} + User Logout + {% elif event.event_type == 'user_register' %} + User Registration + {% elif event.event_type == 'user_update' %} + User Update + {% elif event.event_type == 'file_upload' %} + File Upload + {% elif event.event_type == 'file_delete' %} + File Delete + {% elif event.event_type == 'file_download' %} + File Download + {% elif event.event_type == 'file_restore' %} + File Restore + {% elif event.event_type == 'file_move' %} + File Move + {% elif event.event_type == 'file_rename' %} + File Rename + {% elif event.event_type == 'file_star' %} + File Star + {% elif event.event_type == 'file_unstar' %} + File Unstar + {% elif event.event_type == 'file_delete_permanent' %} + File Delete Permanent + {% elif event.event_type == 'room_create' %} + Room Create + {% elif event.event_type == 'room_delete' %} + Room Delete + {% elif event.event_type == 'room_update' %} + Room Update + {% elif event.event_type == 'room_open' %} + Room Open + {% elif event.event_type == 'room_list_view' %} + Room List View + {% elif event.event_type == 'room_search' %} + Room Search + {% elif event.event_type == 'room_join' %} + Room Join + {% elif event.event_type == 'room_leave' %} + Room Leave + {% elif event.event_type == 'room_member_permissions_update' %} + Room Member Permissions Update + {% elif event.event_type == 'conversation_create' %} + Conversation Create + {% elif event.event_type == 'conversation_delete' %} + Conversation Delete + {% elif event.event_type == 'message_sent' %} + Message Sent + {% elif event.event_type == 'attachment_download' %} + Attachment Download + {% else %} + {{ event.event_type }} + {% endif %} + + {{ event.user.username if event.user else 'Unknown' }} + + + + {{ event.ip_address or '-' }} + + {% endfor %} + {% else %} + + No events found + + {% endif %}
- - Page 1 of 1 - + + Page {{ current_page }} of {{ total_pages }} +
@@ -160,6 +169,6 @@ {% endmacro %} -{% block extra_js %} - +{% block content %} +{{ events_tab(events, csrf_token, users) }} {% endblock %} \ No newline at end of file