From 8f24e21d5db585c2959f7765893f1cf55b04eebe Mon Sep 17 00:00:00 2001 From: Kobe Date: Thu, 29 May 2025 22:33:05 +0200 Subject: [PATCH] user logging --- __pycache__/app.cpython-313.pyc | Bin 5477 -> 5477 bytes routes/__pycache__/auth.cpython-313.pyc | Bin 6359 -> 7610 bytes routes/__pycache__/main.cpython-313.pyc | Bin 39170 -> 42369 bytes routes/auth.py | 41 ++++++ routes/main.py | 109 ++++++++++++++-- static/js/events.js | 40 +++++- static/js/settings.js | 95 +++++++++++--- templates/settings/settings.html | 3 +- templates/settings/tabs/events.html | 165 +++++++++++++----------- 9 files changed, 340 insertions(+), 113 deletions(-) diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index 293a4a7d94e827fd1f0885cebd0175fd77ff1168..5158dca5d65ca75b9b2fe71f8de00a875d7f5e57 100644 GIT binary patch delta 22 bcmaE=^;C=ZGcPX}0|7&-72YMeOD@Uf&+<<{TyiZ-rYJkqPoqed9Lqt_G>%gt(N?Zg!q{%awZuXs z)!C($I!UPl1quajZDUZh2@2TlCC;IMTHSIgdTM)#1O*VWpl{8kC$i$Dy`=MIMM*LG z0DXKj^Ymu+&G)`Jocw$~bR>#?hM51avunf2N1;CUy+^s5IY#AfMBa#^sKTg>%HNRQ zkdQ=t?*M~hs*hlNRml4v-QVudv$n8aTh)xaM%5w%UrDzNt6VX56YQ%lR>1GLZ%b*i z1{mPL#sX)msdsE97jadrNAJ?j0w4%j^(-#^u-q z{@VNfv5{VWH95^X{xFkIE&exD(aY6BNjKNtt?8(QhVho~{L7;x#P-)s18JnV<*FSk zSMTcU<&w67j1sAISzkBvZbaj}W_@MFFio3BhHloX_*36^8e=2_iWA6lwwERwHQuxCh4s3%Hcn4dShFhY`4|d7 zNFM+X;05pyu=xs}7ski?VIl&+0r+6aMUF85BdBGbbB>gUi81_8$g(ot5ial&nJ^KS zBj3Xl{_o2FjP(EZQd1skW`~=ZZ*{z$ywqhp(OB2dM3Z>Sue{jh88P{Ay*ZeDJoE8P zvp++;W_q|eHnsDk{jqDE06!9H2boCx!RGd6SLDUMwuJxSFXa@n(>-Av8LMT}GSCVc zo*1+SrQ@3({5JO?+rnRPpYIw$muo^5mk9}q7kaTva1+nj5Ho!|vviTrXW(iPDnM=N zQqMd6J+lf|^qehC;0_lPLTb3cFMF1zfRBL_J{G637J7MhruR>3YlrlnUlY=3z}(@P};{B#JduM+;+F((Dmt#YlZRSavbR=Ouqj`fl|G)ohj zy+n5ZKRiQFUqlue1YeFsfLVac0CV`~;i(4YUzCh(zHY5lYn!%EHfwNX-LeD7sMIWj zo)Eo8gG>vNra<$M0q*M?M1`+&YIX&JeN|&qgMyeqa0Lv38rZ(xH`w?;;S9e(=FDI2 z%8@}W+wMDlhm2m&rBR;1 z^p1wwAtuuI;Qj6QyJ4@G?n;c9!t>Gdtb(;@zYL8?Df?&Q{F*5qYYovcw&=pYlG4HA zv1l^P5)YK(Tm_f2^y2g>pFoMJ^(ah~6WX0x5eiLBb>lxpV~d_eJ3x6s*2^oJC9bH@_uK6l~EvD;Z1nJ=mt{?`(i7c`knVl@SyL7gK%NbS3nA$l!gMJ zPqa3+i?s6)-jW3N&E1cruh`KukHVQYglFrUhX3KsO3veRHV&FGby~dB^1x&CLHzhJ z8aUTWriRb7XJnCx{@>#_d1yeC!XpV~=D3{o4R}rMa8H@Fl{LM(Y7pT+sV|A7L7MfG z4Fi8Wp?DpE&*6lcsjAe;K@ruSJu>+(CGL&o@u*Go6wfS7H0N0d4|lu+MXS z7${fEmWBu?4U=j+2!Z*8z!uZwg1@-^v)d16pBPVXKGC*r?{U)|*BCc-%BkC)9HN!MJgv%~)Y10XpY delta 1435 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

e;baJ}BMcIT^*PeF zk$U}v2m-GXjVIFRJWl5G9}=Pjdd-@cQ00`Om$`uBi;{kg{LNhz&sK0PD$y<#?(VO>xz!nU|OcjlFYtnJ3Ks*C>4*}BH_-DkRI18uX7 zO|uQF>2Jj&v$YLZ8k*mc-j;scJ=3smp~zaep3MtZ_~g}A$&SkP(EWlc;3^28+$B+c z(p#9pwVt=o1zHKK5%{jHdbULpF-^?p9fm@HM_>P{WeWx6M*_U~dWUM#gw%2)&Tun#eO*Oh-x}?jn4#I%;Ngzz{rn zdV~LWwEBC(Wx}U~a~XCc|Ujc^v{?}5<3@-g4so=o;Tre-V5)% z@!q3r#Pw`xrGDN-8h`2l5U{VM2YgyLapF=nU9Xk*?3b{#oWl(e!KE) zGMXG3kyCrP2h~B}bh$^f;30TQTr(lRuE3Hzy{UuD<;^rf4%Q!?W_=8=kWI>u q^B!EqKS7y9%o`bFSB1gL!r+WB2#$00#*c(@SaWWJEofu;2>lzb2ez{S 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