Added events system

This commit is contained in:
2025-05-29 14:27:15 +02:00
parent 3174f8fa5b
commit f00d569db3
24 changed files with 1186 additions and 114 deletions

View File

@@ -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.event_logger import log_event
from models import EventType
def require_password_change(f):
@wraps(f)
@@ -31,6 +33,13 @@ def init_routes(auth_bp):
login_user(user, remember=remember)
# Log successful login
log_event(
event_type=EventType.USER_LOGIN,
user_id=user.id,
details={'remember': remember}
)
# Check if user is using default password
if password == 'changeme':
flash('Please change your password before continuing.', 'warning')
@@ -69,6 +78,13 @@ def init_routes(auth_bp):
db.session.add(new_user)
db.session.commit()
# Log user creation
log_event(
event_type=EventType.USER_CREATE,
user_id=new_user.id,
details={'email': email, 'username': username}
)
login_user(new_user)
return redirect(url_for('main.dashboard'))
@@ -77,6 +93,11 @@ def init_routes(auth_bp):
@auth_bp.route('/logout')
@login_required
def logout():
# Log logout before actually logging out
log_event(
event_type=EventType.USER_LOGOUT,
user_id=current_user.id
)
logout_user()
return redirect(url_for('auth.login'))
@@ -98,6 +119,14 @@ def init_routes(auth_bp):
current_user.set_password(new_password)
db.session.commit()
# Log password change
log_event(
event_type=EventType.USER_UPDATE,
user_id=current_user.id,
details={'action': 'password_change'}
)
flash('Password changed successfully!', 'success')
return redirect(url_for('main.dashboard'))

View File

@@ -1,8 +1,9 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, send_file
from flask_login import login_required, current_user
from models import db, Conversation, User, Message, MessageAttachment
from models import db, Conversation, User, Message, MessageAttachment, EventType
from forms import ConversationForm
from routes.auth import require_password_change
from utils.event_logger import log_event
import os
from werkzeug.utils import secure_filename
from datetime import datetime
@@ -84,6 +85,17 @@ def create_conversation():
db.session.add(conversation)
db.session.commit()
# Log conversation creation
log_event(
event_type=EventType.CONVERSATION_CREATE,
user_id=current_user.id,
details={
'conversation_id': conversation.id,
'conversation_name': conversation.name,
'member_count': len(conversation.members)
}
)
flash('Conversation created successfully!', 'success')
return redirect(url_for('conversations.conversations'))
return render_template('conversations/create_conversation.html', form=form)
@@ -227,6 +239,18 @@ def delete_conversation(conversation_id):
conversation = Conversation.query.get_or_404(conversation_id)
# Log conversation deletion before deleting
log_event(
event_type=EventType.CONVERSATION_DELETE,
user_id=current_user.id,
details={
'conversation_id': conversation_id,
'conversation_name': conversation.name,
'message_count': len(conversation.messages),
'member_count': len(conversation.members)
}
)
# Delete all messages in the conversation
Message.query.filter_by(conversation_id=conversation_id).delete()
@@ -324,6 +348,18 @@ def send_message(conversation_id):
db.session.commit()
# Log message sent
log_event(
event_type=EventType.MESSAGE_SENT,
user_id=current_user.id,
details={
'conversation_id': conversation_id,
'message_id': message.id,
'has_attachments': len(attachments) > 0,
'attachment_count': len(attachments)
}
)
# Prepare message data for response
message_data = {
'id': message.id,
@@ -358,6 +394,19 @@ def download_attachment(message_id, attachment_index):
try:
attachment = message.attachments[attachment_index]
# Log attachment download
log_event(
event_type=EventType.ATTACHMENT_DOWNLOAD,
user_id=current_user.id,
details={
'conversation_id': conversation.id,
'message_id': message_id,
'attachment_name': attachment.name,
'attachment_size': attachment.size
}
)
return send_file(
attachment.path,
as_attachment=True,

View File

@@ -1,7 +1,8 @@
from flask import render_template, Blueprint, redirect, url_for, request, flash, Response
from flask import render_template, Blueprint, redirect, url_for, request, flash, Response, jsonify
from flask_login import current_user, login_required
from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings
from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings, Event, EventType
from routes.auth import require_password_change
from utils.event_logger import log_event
import os
from werkzeug.utils import secure_filename
from sqlalchemy import func, case, literal_column, text
@@ -279,6 +280,14 @@ def init_routes(main_bp):
os.remove(old_picture_path)
current_user.profile_picture = None
db.session.commit()
# Log profile picture removal
log_event(
event_type=EventType.USER_UPDATE,
user_id=current_user.id,
details={'action': 'remove_profile_picture'}
)
flash('Profile picture removed successfully!', 'success')
return redirect(url_for('main.profile'))
@@ -289,6 +298,10 @@ def init_routes(main_bp):
if existing_user:
flash('A user with this email already exists.', 'error')
return render_template('profile/profile.html')
# Track changes for event logging
changes = {}
# Handle profile picture upload
file = request.files.get('profile_picture')
if file and file.filename:
@@ -296,14 +309,31 @@ def init_routes(main_bp):
file_path = os.path.join(UPLOAD_FOLDER, filename)
file.save(file_path)
current_user.profile_picture = filename
changes['profile_picture'] = True
# Update user information
current_user.username = request.form.get('first_name')
current_user.last_name = request.form.get('last_name')
current_user.email = new_email
current_user.phone = request.form.get('phone')
current_user.company = request.form.get('company')
current_user.position = request.form.get('position')
current_user.notes = request.form.get('notes')
if current_user.username != request.form.get('first_name'):
current_user.username = request.form.get('first_name')
changes['username'] = True
if current_user.last_name != request.form.get('last_name'):
current_user.last_name = request.form.get('last_name')
changes['last_name'] = True
if current_user.email != new_email:
current_user.email = new_email
changes['email'] = True
if current_user.phone != request.form.get('phone'):
current_user.phone = request.form.get('phone')
changes['phone'] = True
if current_user.company != request.form.get('company'):
current_user.company = request.form.get('company')
changes['company'] = True
if current_user.position != request.form.get('position'):
current_user.position = request.form.get('position')
changes['position'] = True
if current_user.notes != request.form.get('notes'):
current_user.notes = request.form.get('notes')
changes['notes'] = True
# Handle password change if provided
new_password = request.form.get('new_password')
confirm_password = request.form.get('confirm_password')
@@ -312,9 +342,20 @@ def init_routes(main_bp):
flash('Passwords do not match.', 'error')
return render_template('profile/profile.html')
current_user.set_password(new_password)
changes['password'] = True
flash('Password updated successfully.', 'success')
try:
db.session.commit()
# Log profile update if any changes were made
if changes:
log_event(
event_type=EventType.USER_UPDATE,
user_id=current_user.id,
details={'changes': changes}
)
flash('Profile updated successfully!', 'success')
except Exception as e:
db.session.rollback()
@@ -355,11 +396,18 @@ def init_routes(main_bp):
site_settings = SiteSettings.get_settings()
active_tab = request.args.get('tab', 'colors')
# Get events for the events tab
events = []
if active_tab == 'events':
events = Event.query.order_by(Event.timestamp.desc()).limit(50).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)
@main_bp.route('/settings/colors', methods=['POST'])
@login_required
@@ -530,4 +578,105 @@ def init_routes(main_bp):
logger.info(f"[Dynamic Colors] Generated CSS with primary color: {primary_color}")
logger.info(f"[Dynamic Colors] Cache version: {site_settings.updated_at.timestamp()}")
return Response(css, mimetype='text/css')
return Response(css, mimetype='text/css')
@main_bp.route('/api/events')
@login_required
def get_events():
if not current_user.is_admin:
return jsonify({'success': False, 'error': 'Unauthorized'}), 403
# Get filter parameters
page = request.args.get('page', 1, type=int)
event_type = request.args.get('eventType')
date_range = request.args.get('dateRange', '24h')
user_id = request.args.get('userId')
# Build query
query = Event.query
# Apply filters
if event_type:
query = query.filter_by(event_type=event_type)
if user_id:
query = query.filter_by(user_id=user_id)
# Apply date range filter
if date_range != 'all':
now = datetime.utcnow()
if date_range == '24h':
start_date = now - timedelta(days=1)
elif date_range == '7d':
start_date = now - timedelta(days=7)
elif date_range == '30d':
start_date = now - timedelta(days=30)
query = query.filter(Event.timestamp >= start_date)
# Get total count for pagination
total_count = query.count()
per_page = 50
total_pages = (total_count + per_page - 1) // per_page
# Get paginated results
events = query.order_by(Event.timestamp.desc())\
.offset((page - 1) * per_page)\
.limit(per_page)\
.all()
return jsonify({
'success': True,
'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 or ''
},
'ip_address': event.ip_address,
'details': event.details
} for event in events],
'total_pages': total_pages
})
@main_bp.route('/api/events/<int:event_id>')
@login_required
def get_event_details(event_id):
if not current_user.is_admin:
return jsonify({'success': False, 'error': 'Unauthorized'}), 403
event = Event.query.get_or_404(event_id)
return jsonify({
'success': True,
'event': {
'id': event.id,
'event_type': event.event_type,
'timestamp': event.timestamp.isoformat(),
'user': {
'id': event.user.id,
'username': event.user.username
},
'ip_address': event.ip_address,
'user_agent': event.user_agent,
'details': event.details
}
})
@main_bp.route('/api/users')
@login_required
def get_users():
if not current_user.is_admin:
return jsonify({'success': False, 'error': 'Unauthorized'}), 403
users = User.query.order_by(User.username).all()
return jsonify({
'success': True,
'users': [{
'id': user.id,
'username': user.username,
'last_name': user.last_name or ''
} for user in users]
})