From bc9fcda6aaf9beb92ae055acbeedf0dd6b0b6d0e Mon Sep 17 00:00:00 2001 From: Olai Date: Tue, 25 Jun 2024 23:27:34 +0200 Subject: [PATCH] Updated online detection and fixed bugs Updated online detection and fixed bugs --- app.py | 209 ++++++++++++++++++------------- src/input.css | 8 ++ static/js/chat.js | 25 +--- static/js/connection_check.js | 33 +++++ static/js/dashboard.js | 18 --- static/js/group_chat.js | 25 +--- static/js/online_detection.js | 20 +++ static/output.css | 43 +++++++ static/styles.css | 4 + templates/admin_panel.html | 15 ++- templates/chat-header-dm.html | 4 +- templates/chat-header-group.html | 4 +- templates/custom_css.html | 26 ++++ templates/dashboard.html | 53 ++++++-- templates/head.html | 13 +- templates/sidebar.html | 5 +- templates/topbar.html | 6 +- 17 files changed, 342 insertions(+), 169 deletions(-) create mode 100644 static/js/connection_check.js create mode 100644 static/js/online_detection.js create mode 100644 templates/custom_css.html diff --git a/app.py b/app.py index a8cf7ff..64f017f 100644 --- a/app.py +++ b/app.py @@ -1,13 +1,13 @@ -from PIL import Image # Import the Image module from Pillow +from PIL import Image from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory, make_response from flask_sqlalchemy import SQLAlchemy from cryptography.fernet import Fernet, InvalidToken +import hashlib import os import random import string from datetime import datetime, timedelta, timezone -from werkzeug.utils import secure_filename # Add this line -from werkzeug.security import generate_password_hash, check_password_hash +from werkzeug.utils import secure_filename from flask_socketio import SocketIO, emit, join_room, leave_room import logging import jwt @@ -67,6 +67,7 @@ class User(db.Model): is_admin = db.Column(db.Boolean, nullable=False, default=False) disabled = db.Column(db.Boolean, nullable=False, default=False) profile_picture = db.Column(db.String(150), nullable=True) + custom_css = db.Column(db.Text, nullable=True) class Message(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -124,18 +125,22 @@ class GroupMessage(db.Model): group = db.relationship('Group', foreign_keys=[group_id]) sender = db.relationship('User', foreign_keys=[sender_id]) +def generate_salt(): + return os.urandom(16).hex() + +def sha256_password_hash(password, salt=None): + if salt is None: + salt = generate_salt() + hash = hashlib.sha256((salt + password).encode()).hexdigest() + return f"{salt}${hash}" @app.route('/') def index(): - token = request.cookies.get('token') - if token: - user_id = decode_token(token) - if user_id: - user = User.query.get(user_id) - if user: - session['username'] = user.username - session['user_id'] = user.id - return redirect(url_for('dashboard')) + if 'user_id' in session: + user_id = session['user_id'] + user = User.query.get(user_id) + if user: + return redirect(url_for('dashboard')) return redirect(url_for('login')) @app.route('/register', methods=['GET', 'POST']) @@ -143,7 +148,7 @@ def register(): if request.method == 'POST': username = request.form['username'].lower() password = request.form['password'] - hashed_password = generate_password_hash(password) # Default method + hashed_password = sha256_password_hash(password) new_user = User(username=username, password=hashed_password) try: db.session.add(new_user) @@ -165,10 +170,11 @@ def login(): password = request.form['password'] user = User.query.filter_by(username=username).first() if user: + salt, stored_hash = user.password.split('$') if user.disabled: logger.warning(f"Disabled account login attempt: {username}") return jsonify({'status': 'error', 'message': 'Account Disabled. Contact Support'}) - if check_password_hash(user.password, password): + if stored_hash == hashlib.sha256((salt + password).encode()).hexdigest(): session['username'] = user.username session['user_id'] = user.id session['is_admin'] = user.is_admin @@ -182,28 +188,25 @@ def login(): return jsonify({'status': 'error', 'message': 'Invalid credentials. Please try again.'}) return render_template('login.html') - @app.route('/change_password', methods=['GET', 'POST']) def change_password(): if request.method == 'POST': username = request.form['username'].lower() current_password = request.form['current_password'] new_password = request.form['new_password'] - user = User.query.filter_by(username=username).first() - if user and check_password_hash(user.password, current_password): - hashed_new_password = generate_password_hash(new_password) - user.password = hashed_new_password - db.session.commit() - logger.info(f"Password changed for user: {username}") - return jsonify({'status': 'Password changed successfully'}) + if user: + current_salt, current_hash = user.password.split('$') + if current_hash == hashlib.sha256((current_salt + current_password).encode()).hexdigest(): + new_hashed_password = sha256_password_hash(new_password) + user.password = new_hashed_password + db.session.commit() + logger.info(f"Password changed for user: {username}") + return jsonify({'status': 'Password changed successfully'}) logger.warning(f"Password change failed for user: {username}") return jsonify({'status': 'error'}) - return render_template('change_password.html') - -# Update your admin check accordingly @app.route('/admin', methods=['GET']) def admin_panel(): if 'username' in session: @@ -217,22 +220,20 @@ def admin_panel(): def admin_change_password(): if 'username' in session: admin_user = User.query.filter_by(username=session['username']).first() - logger.debug(f"Admin User: {admin_user.username}, Is Admin: {admin_user.is_admin}") - if admin_user and admin_user.is_admin: # Check if the user is an admin + if admin_user and admin_user.is_admin: try: - data = request.get_json() # Parse the JSON data from the request + data = request.get_json() user_id = data['user_id'] new_password = data['new_password'] user = User.query.get(user_id) if user: - user.password = generate_password_hash(new_password) + user.password = sha256_password_hash(new_password) db.session.commit() return jsonify({'status': 'success'}) return jsonify({'status': 'user not found'}), 404 except Exception as e: logger.error(f"Error changing password: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 - logger.warning(f"Unauthorized attempt to access admin change password by user {session.get('username', 'unknown')}") return jsonify({'status': 'error'}), 401 @app.route('/admin/disable_account', methods=['POST']) @@ -250,7 +251,6 @@ def admin_disable_account(): except Exception as e: logger.error(f"Error disabling account: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 - logger.warning(f"Unauthorized attempt to disable account by user {session.get('username', 'unknown')}") return jsonify({'status': 'error'}), 401 @app.route('/admin/enable_account', methods=['POST']) @@ -268,7 +268,6 @@ def admin_enable_account(): except Exception as e: logger.error(f"Error enabling account: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 - logger.warning(f"Unauthorized attempt to enable account by user {session.get('username', 'unknown')}") return jsonify({'status': 'error'}), 401 @app.route('/dashboard') @@ -281,9 +280,8 @@ def dashboard(): friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all() pending_messages = PendingMessage.query.filter_by(receiver=session['username']).all() - messages = Message.query.filter_by(receiver_id=user.id).all() # Updated line + messages = Message.query.filter_by(receiver_id=user.id).all() - # Fetch groups for the user group_memberships = GroupMember.query.filter_by(user_id=user.id).all() group_ids = [membership.group_id for membership in group_memberships] groups = Group.query.filter(Group.id.in_(group_ids)).all() @@ -315,13 +313,12 @@ def chat(friend_id): friend_user = User.query.filter_by(id=friend_id).first() user = User.query.filter_by(id=current_user_id).first() friends = Friend.query.filter_by(user_id=user.id).all() - profile_picture = user.profile_picture # Assuming this is the path to the profile picture + profile_picture = user.profile_picture friend_ids = [f.friend_id for f in friends] friend_users = User.query.filter(User.id.in_(friend_ids)).all() friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all() - # Fetch groups for the user group_memberships = GroupMember.query.filter_by(user_id=user.id).all() group_ids = [membership.group_id for membership in group_memberships] groups = Group.query.filter(Group.id.in_(group_ids)).all() @@ -366,9 +363,9 @@ def send_message(receiver): decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() if content_type == 'text' else encrypted_content socketio.emit('new_message', { 'sender': session['username'], - 'receiver': receiver_user.username, # Include the receiver information + 'receiver': receiver_user.username, 'content': decrypted_content, - 'sender_profile_picture': sender_user.profile_picture, # Ensure this is correct + 'sender_profile_picture': sender_user.profile_picture, 'content_type': content_type, 'timestamp': timestamp, 'id': new_message.id @@ -380,7 +377,6 @@ def send_message(receiver): db.session.add(pending_message) db.session.commit() logger.info(f"Pending message from {session['username']} to {receiver_user.username}") - return jsonify({'status': 'Pending message sent', 'message_id': pending_message.id, 'sender_profile_picture': sender_user.profile_picture}), 200 return jsonify({'error': 'No content or file provided'}), 400 except Exception as e: @@ -430,8 +426,8 @@ def compress_image(image_path, quality=15): if img.format == 'PNG': img.save(image_path, "PNG", optimize=True) else: - img = img.convert("RGB") # Ensure the image is in RGB mode for non-PNG files - img.save(image_path, "JPEG", quality=quality) # Save with JPEG format and compression + img = img.convert("RGB") + img.save(image_path, "JPEG", quality=quality) @app.route('/upload_profile_picture', methods=['POST']) def upload_profile_picture(): @@ -446,15 +442,12 @@ def upload_profile_picture(): filename = f"{filename_parts[0]}-{random_string}.{filename_parts[1]}" file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(file_path) - - # Compress the image compress_image(file_path, quality=85) - user.profile_picture = filename db.session.commit() logger.info(f"User {session['username']} uploaded a new profile picture: {filename}") - return redirect(url_for('dashboard')) # Redirect to a valid endpoint - return redirect(url_for('dashboard')) # Redirect to a valid endpoint if not logged in + return redirect(url_for('dashboard')) + return redirect(url_for('dashboard')) @app.route('/cdn/') def uploaded_file(filename): @@ -463,23 +456,19 @@ def uploaded_file(filename): @app.route('/add_friend', methods=['POST']) def add_friend(): if 'username' in session: - friend_username = request.form['friend_username'].lower() # Convert to lowercase + friend_username = request.form['friend_username'].lower() user = User.query.filter_by(username=session['username']).first() friend = User.query.filter_by(username=friend_username).first() if friend and user != friend: - # Check if friend request already exists existing_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend.id).first() if existing_request: return jsonify({'status': 'Friend request already sent'}) - - # Check if already friends existing_friend = Friend.query.filter( ((Friend.user_id == user.id) & (Friend.friend_id == friend.id)) | ((Friend.user_id == friend.id) & (Friend.friend_id == user.id)) ).first() if existing_friend: return jsonify({'status': 'Already friends'}) - friend_request = FriendRequest(sender_id=user.id, receiver_id=friend.id) db.session.add(friend_request) db.session.commit() @@ -493,7 +482,6 @@ def add_friend(): return jsonify({'status': 'Friend not found or cannot add yourself as a friend'}) return jsonify({'status': 'Unauthorized'}), 401 - @app.route('/accept_friend/', methods=['POST']) def accept_friend(request_id): if 'username' in session: @@ -502,7 +490,6 @@ def accept_friend(request_id): friend_request.status = 'accepted' db.session.commit() - # Create friendships both ways user_id = friend_request.receiver_id friend_id = friend_request.sender_id new_friend_1 = Friend(user_id=user_id, friend_id=friend_id) @@ -511,7 +498,6 @@ def accept_friend(request_id): db.session.add(new_friend_2) db.session.commit() - # Move pending messages to messages pending_messages = PendingMessage.query.filter_by(sender=friend_request.sender.username, receiver=friend_request.receiver.username).all() for pm in pending_messages: new_message = Message(sender=pm.sender, receiver=pm.receiver, content=pm.content, content_type=pm.content_type, timestamp=pm.timestamp) @@ -524,7 +510,6 @@ def accept_friend(request_id): 'receiver': friend_request.receiver.username }, room=friend_request.sender.username) logger.info(f"Friend request accepted by {session['username']} from {friend_request.sender.username}") - return redirect(url_for('dashboard')) logger.warning(f"Friend request accept failed: Friend request not found or unauthorized access by {session['username']}") return 'Friend request not found' @@ -537,13 +522,11 @@ def reject_friend(request_id): if friend_request and friend_request.receiver.username == session['username']: db.session.delete(friend_request) db.session.commit() - socketio.emit('friend_request_rejected', { 'sender': friend_request.sender.username, 'receiver': friend_request.receiver.username }, room=friend_request.sender.username) logger.info(f"Friend request rejected by {session['username']} from {friend_request.sender.username}") - return redirect(url_for('dashboard')) logger.warning(f"Friend request reject failed: Friend request not found or unauthorized access by {session['username']}") return 'Friend request not found' @@ -560,7 +543,6 @@ def remove_friend(friend_id): if reciprocal_friend: db.session.delete(reciprocal_friend) - # Remove friend requests associated with the friend sent_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend_id).first() if sent_request: db.session.delete(sent_request) @@ -569,13 +551,11 @@ def remove_friend(friend_id): db.session.delete(received_request) db.session.commit() - socketio.emit('friend_removed', { 'sender': session['username'], 'receiver': friend.friend.username }, room=friend.friend.username) logger.info(f"Friend {friend.friend.username} removed by {session['username']}") - return jsonify({'status': 'success'}), 200 logger.warning(f"Friend removal failed: Friend not found or unauthorized access by {session['username']}") return jsonify({'status': 'error', 'message': 'Friend not found or unauthorized access'}), 404 @@ -584,30 +564,45 @@ def remove_friend(friend_id): @app.route('/create_group', methods=['POST']) def create_group(): if 'username' in session: - user = User.query.filter_by(username=session['username']).first() - group_name = request.form['group_name'] - member_usernames = [username.strip() for username in request.form['members'].split(',')] + try: + user = User.query.filter_by(username=session['username']).first() + group_name = request.form['group_name'] + member_usernames = [username.strip() for username in request.form['members'].split(',')] - # Create a new group - new_group = Group(name=group_name, admin_id=user.id) - db.session.add(new_group) - db.session.commit() + existing_group = Group.query.filter_by(name=group_name).first() + if existing_group: + return jsonify({'status': 'Error', 'message': 'A group with this name already exists.'}), 400 - # Add the group creator as a member - new_group_member = GroupMember(group_id=new_group.id, user_id=user.id) - db.session.add(new_group_member) + for username in member_usernames: + member = User.query.filter_by(username=username).first() + if not member: + return jsonify({'status': 'Error', 'message': f'User {username} does not exist.'}), 400 + friendship = Friend.query.filter( + ((Friend.user_id == user.id) & (Friend.friend_id == member.id)) | + ((Friend.user_id == member.id) & (Friend.friend_id == user.id)) + ).first() + if not friendship: + return jsonify({'status': 'Error', 'message': f'User {username} is not your friend.'}), 400 - # Add other members to the group - for username in member_usernames: - member = User.query.filter_by(username=username).first() - if member: - new_group_member = GroupMember(group_id=new_group.id, user_id=member.id) - db.session.add(new_group_member) - - db.session.commit() - - return redirect(url_for('dashboard')) - return redirect(url_for('login')) + new_group = Group(name=group_name, admin_id=user.id) + db.session.add(new_group) + db.session.flush() + + new_group_member = GroupMember(group_id=new_group.id, user_id=user.id) + db.session.add(new_group_member) + + for username in member_usernames: + member = User.query.filter_by(username=username).first() + if member: + new_group_member = GroupMember(group_id=new_group.id, user_id=member.id) + db.session.add(new_group_member) + + db.session.commit() + return jsonify({'status': 'Group created'}) + except Exception as e: + db.session.rollback() + return jsonify({'status': 'Error', 'message': str(e)}), 500 + return jsonify({'status': 'Unauthorized'}), 401 @app.route('/invite_to_group', methods=['POST']) def invite_to_group(): @@ -672,12 +667,10 @@ def send_group_message(group_id): encrypted_content = cipher.encrypt(content.encode()).decode() if content_type == 'text' else content timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc) - # Fetch the sender user object sender_user = User.query.filter_by(username=session['username']).first() if not sender_user: return jsonify({'error': 'User not found'}), 404 - # Create new group message new_message = GroupMessage(group_id=group_id, sender_id=sender_user.id, content=encrypted_content, content_type=content_type, timestamp=timestamp_dt) db.session.add(new_message) db.session.commit() @@ -687,7 +680,7 @@ def send_group_message(group_id): 'group_id': group_id, 'sender': session['username'], 'content': decrypted_content, - 'sender_profile_picture': sender_user.profile_picture, # Add profile picture + 'sender_profile_picture': sender_user.profile_picture, 'content_type': content_type, 'timestamp': timestamp, 'id': new_message.id @@ -710,8 +703,8 @@ def get_group_messages(group_id): { 'id': msg.GroupMessage.id, 'group_id': msg.GroupMessage.group_id, - 'sender': msg.User.username, # Use the username from the User table - 'sender_profile_picture': msg.User.profile_picture, # Add profile picture + 'sender': msg.User.username, + 'sender_profile_picture': msg.User.profile_picture, 'content': cipher.decrypt(msg.GroupMessage.content.encode()).decode() if msg.GroupMessage.content_type == 'text' else msg.GroupMessage.content, 'content_type': msg.GroupMessage.content_type, 'timestamp': msg.GroupMessage.timestamp.strftime("%Y-%m-%d %H:%M:%S") @@ -750,6 +743,26 @@ def settings(): return render_template('settings.html', current_theme=current_theme) return redirect(url_for('login')) +@app.route('/custom_css', methods=['GET', 'POST']) +def custom_css(): + if 'username' in session: + user = User.query.filter_by(username=session['username']).first() + if request.method == 'POST': + custom_css = request.form.get('custom_css') + user.custom_css = custom_css + db.session.commit() + return redirect(url_for('dashboard')) + + return render_template('custom_css.html', custom_css=user.custom_css if user.custom_css else "") + return redirect(url_for('login')) + +@app.context_processor +def inject_user(): + if 'username' in session: + user = User.query.filter_by(username=session['username']).first() + return dict(current_user=user) + return dict(current_user=None) + @app.route('/logout') def logout(): session.pop('username', None) @@ -759,12 +772,17 @@ def logout(): logger.info(f"User logged out") return response +@socketio.on('ping') +def handle_ping(): + emit('pong') + @socketio.on('connect') def handle_connect(): if 'username' in session: user = User.query.filter_by(username=session['username']).first() if user: user.online = True + user.last_activity = datetime.utcnow() db.session.commit() emit('user_online', {'username': user.username}, broadcast=True) logger.info(f"User {session['username']} connected") @@ -805,7 +823,24 @@ def handle_leave_group(data): leave_room(f"group_{group_id}") logger.info(f"User {username} left group room {group_id}") +@socketio.on('user_activity') +def handle_user_activity(): + if 'username' in session: + user = User.query.filter_by(username=session['username']).first() + if user: + user.last_activity = datetime.utcnow() + db.session.commit() + +def set_users_to_idle(): + with app.app_context(): + idle_threshold = datetime.utcnow() - timedelta(minutes=5) + users = User.query.filter(User.online == True, User.last_activity < idle_threshold).all() + for user in users: + user.online = False + db.session.commit() + emit('user_idle', {'username': user.username}, broadcast=True) + if __name__ == '__main__': with app.app_context(): db.create_all() - socketio.run(app, host='0.0.0.0', port=8086, debug=True, allow_unsafe_werkzeug=True) \ No newline at end of file + socketio.run(app, host='0.0.0.0', port=8086, debug=True, allow_unsafe_werkzeug=True) diff --git a/src/input.css b/src/input.css index cc6694b..d178790 100644 --- a/src/input.css +++ b/src/input.css @@ -121,4 +121,12 @@ hr{ height: 1px; border: none; @apply !bg-[#f1f1f1] dark:!bg-[#363636] +} + +.button-admin{ + @apply bg-transparent border-solid border-1 border-black dark:border-white p-2 shadow-lg +} + +.button-admin:disabled{ + @apply bg-neutral-400 text-neutral-200 dark:bg-neutral-700 dark:text-neutral-500 } \ No newline at end of file diff --git a/static/js/chat.js b/static/js/chat.js index 41da652..d3ca42f 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -18,7 +18,7 @@ document.addEventListener('DOMContentLoaded', (event) => { socket.emit('join', { username: username }); - document.title = `Chatting with ${friendUsername}`; + document.title = `${friendUsername}`; const messagesList = document.getElementById('messages'); const imageOverlay = document.getElementById('image-overlay'); @@ -147,7 +147,7 @@ document.addEventListener('DOMContentLoaded', (event) => { // Include the profile picture if available const profilePicture = data.sender_profile_picture ? `${data.sender}` : ''; - usernameElement.innerHTML = `${profilePicture} ${data.sender}:
${timestampFormatted}
`; + usernameElement.innerHTML = `${profilePicture} ${data.sender}
${timestampFormatted}
`; messagesList.appendChild(usernameElement); } @@ -179,7 +179,7 @@ document.addEventListener('DOMContentLoaded', (event) => { .then(response => response.json()) .then(data => { messagesList.innerHTML = ''; - document.getElementById('chat-with').textContent = `Chatting with ${friendUsername}`; + document.getElementById('chat-with').textContent = `${friendUsername}`; document.getElementById('send-message-form').dataset.receiver = friendId; data.messages.forEach((msg, index, messages) => { @@ -261,7 +261,7 @@ document.addEventListener('DOMContentLoaded', (event) => { usernameElement.classList.add('message-sender'); const timestampFormatted = formatLocalTime(timestamp); const profilePictureUrl = `/cdn/${profilePicture}`; // Correct reference - usernameElement.innerHTML = `${username} ${username}:
${timestampFormatted}
`; + usernameElement.innerHTML = `${username} ${username}
${timestampFormatted}
`; messagesList.appendChild(usernameElement); } @@ -436,21 +436,4 @@ document.addEventListener('DOMContentLoaded', (event) => { document.addEventListener('click', function() { contextMenu.style.display = 'none'; }); - - // Handle user online/offline status - socket.on('user_online', function(data) { - const statusIndicator = document.getElementById(`status-${data.username}`); - if (statusIndicator) { - statusIndicator.classList.remove('offline'); - statusIndicator.classList.add('online'); - } - }); - - socket.on('user_offline', function(data) { - const statusIndicator = document.getElementById(`status-${data.username}`); - if (statusIndicator) { - statusIndicator.classList.remove('online'); - statusIndicator.classList.add('offline'); - } - }); }); diff --git a/static/js/connection_check.js b/static/js/connection_check.js new file mode 100644 index 0000000..5c2f3fc --- /dev/null +++ b/static/js/connection_check.js @@ -0,0 +1,33 @@ +document.addEventListener('DOMContentLoaded', (event) => { + const socket = io(); + let isConnected = true; + + function checkConnection() { + socket.emit('ping'); + } + + socket.on('pong', function () { + isConnected = true; + }); + + setInterval(() => { + if (!isConnected) { + if (confirm('Something went wrong, please reload the website')) { + window.location.reload(); + } + } + isConnected = false; + checkConnection(); + }, 3000); + + socket.on('connect', () => { + isConnected = true; + }); + + socket.on('disconnect', () => { + isConnected = false; + if (confirm('Something went wrong, please reload the website')) { + window.location.reload(); + } + }); +}); diff --git a/static/js/dashboard.js b/static/js/dashboard.js index 7315d81..f73e8bf 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -1,24 +1,6 @@ document.addEventListener('DOMContentLoaded', (event) => { const socket = io(); - socket.on('user_online', function(data) { - const username = data.username; - const friendElement = document.querySelector(`[data-username="${username}"]`); - if (friendElement) { - friendElement.querySelector('.status-indicator').classList.remove('offline'); - friendElement.querySelector('.status-indicator').classList.add('online'); - } - }); - - socket.on('user_offline', function(data) { - const username = data.username; - const friendElement = document.querySelector(`[data-username="${username}"]`); - if (friendElement) { - friendElement.querySelector('.status-indicator').classList.remove('online'); - friendElement.querySelector('.status-indicator').classList.add('offline'); - } - }); - function checkFriendRequests() { const friendRequestsList = document.getElementById('friend-requests'); const friendRequestsSection = document.getElementById('friend-requests-section'); diff --git a/static/js/group_chat.js b/static/js/group_chat.js index 931137b..4013eb6 100644 --- a/static/js/group_chat.js +++ b/static/js/group_chat.js @@ -3,7 +3,7 @@ document.addEventListener('DOMContentLoaded', (event) => { const md = window.markdownit(); socket.emit('join_group', { group_id: groupId }); - document.title = `Group Chat: ${groupName}`; + document.title = `${groupName}`; const messagesList = document.getElementById('messages'); const imageOverlay = document.getElementById('image-overlay'); @@ -76,7 +76,7 @@ document.addEventListener('DOMContentLoaded', (event) => { const usernameElement = document.createElement('div'); usernameElement.classList.add('message-sender'); const profilePictureUrl = `/cdn/${data.sender_profile_picture}`; - usernameElement.innerHTML = `${data.sender} ${data.sender}:
${formatLocalTime(data.timestamp)}
`; + usernameElement.innerHTML = `${data.sender} ${data.sender}
${formatLocalTime(data.timestamp)}
`; messagesList.appendChild(usernameElement); } @@ -112,7 +112,7 @@ document.addEventListener('DOMContentLoaded', (event) => { }) .then(data => { messagesList.innerHTML = ''; - document.getElementById('chat-with').textContent = `Group Chat: ${groupName}`; + document.getElementById('chat-with').textContent = `${groupName}`; document.getElementById('send-message-form').dataset.receiver = groupId; data.messages.forEach((msg, index, messages) => { @@ -128,7 +128,7 @@ document.addEventListener('DOMContentLoaded', (event) => { usernameElement.classList.add('message-myself-sender'); } const profilePictureUrl = `/cdn/${msg.sender_profile_picture}`; - usernameElement.innerHTML = `${msg.sender} ${msg.sender}
${formatLocalTime(msg.timestamp)}
`; + usernameElement.innerHTML = `${msg.sender} ${msg.sender}
${formatLocalTime(msg.timestamp)}
`; messagesList.appendChild(usernameElement); } @@ -345,21 +345,4 @@ document.addEventListener('DOMContentLoaded', (event) => { document.addEventListener('click', function() { contextMenu.style.display = 'none'; }); - - // Handle user online/offline status - socket.on('user_online', function(data) { - const statusIndicator = document.getElementById(`status-${data.username}`); - if (statusIndicator) { - statusIndicator.classList.remove('offline'); - statusIndicator.classList.add('online'); - } - }); - - socket.on('user_offline', function(data) { - const statusIndicator = document.getElementById(`status-${data.username}`); - if (statusIndicator) { - statusIndicator.classList.remove('online'); - statusIndicator.classList.add('offline'); - } - }); }); diff --git a/static/js/online_detection.js b/static/js/online_detection.js new file mode 100644 index 0000000..a432daa --- /dev/null +++ b/static/js/online_detection.js @@ -0,0 +1,20 @@ +document.addEventListener('DOMContentLoaded', (event) => { + const socket = io(); + + // Handle user online/offline status + socket.on('user_online', function(data) { + const statusIndicator = document.querySelector(`.status-indicator[data-username="${data.username}"]`); + if (statusIndicator) { + statusIndicator.classList.remove('offline'); + statusIndicator.classList.add('online'); + } + }); + + socket.on('user_offline', function(data) { + const statusIndicator = document.querySelector(`.status-indicator[data-username="${data.username}"]`); + if (statusIndicator) { + statusIndicator.classList.remove('online'); + statusIndicator.classList.add('offline'); + } + }); +}); diff --git a/static/output.css b/static/output.css index 03ade64..7f4998e 100644 --- a/static/output.css +++ b/static/output.css @@ -616,6 +616,10 @@ video { margin-bottom: 1.25rem; } +.ml-10 { + margin-left: 2.5rem; +} + .mr-5 { margin-right: 1.25rem; } @@ -640,6 +644,10 @@ video { height: 400px !important; } +.\!h-\[460px\] { + height: 460px !important; +} + .\!h-\[465px\] { height: 465px !important; } @@ -686,6 +694,10 @@ video { width: 100%; } +.min-w-\[280px\] { + min-width: 280px; +} + .flex-1 { flex: 1 1 0%; } @@ -1203,6 +1215,37 @@ hr:is(.dark *) { background-color: rgb(54 54 54 / var(--tw-bg-opacity)) !important; } +.button-admin { + border-style: solid; + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); + background-color: transparent; + padding: 0.5rem; + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + border-width: 1px; +} + +.button-admin:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(255 255 255 / var(--tw-border-opacity)); +} + +.button-admin:disabled { + --tw-bg-opacity: 1; + background-color: rgb(163 163 163 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(229 229 229 / var(--tw-text-opacity)); +} + +.button-admin:disabled:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(64 64 64 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(115 115 115 / var(--tw-text-opacity)); +} + .hover\:\!text-slate-400:hover { --tw-text-opacity: 1 !important; color: rgb(148 163 184 / var(--tw-text-opacity)) !important; diff --git a/static/styles.css b/static/styles.css index 59af186..1607b0b 100644 --- a/static/styles.css +++ b/static/styles.css @@ -358,6 +358,10 @@ ul#friends-list { background-color: #80848e; } +.idle { + background-color: #f0b232; +} + /* Custom Scroll Bar */ ::-webkit-scrollbar { diff --git a/templates/admin_panel.html b/templates/admin_panel.html index 09c0009..385fb4d 100644 --- a/templates/admin_panel.html +++ b/templates/admin_panel.html @@ -69,19 +69,28 @@ + - + + + + {% for user in users %} + + + + {% endfor %} diff --git a/templates/chat-header-dm.html b/templates/chat-header-dm.html index 41d9244..80b1d56 100644 --- a/templates/chat-header-dm.html +++ b/templates/chat-header-dm.html @@ -1,3 +1,3 @@ -
-

Chatting with {{ friend_username }}

+
+

{{ friend_username }}

\ No newline at end of file diff --git a/templates/chat-header-group.html b/templates/chat-header-group.html index dac1506..dfc0119 100644 --- a/templates/chat-header-group.html +++ b/templates/chat-header-group.html @@ -1,3 +1,3 @@ -
-

Group Chat: {{ group.name }}

+
+

{{ group.name }}

\ No newline at end of file diff --git a/templates/custom_css.html b/templates/custom_css.html new file mode 100644 index 0000000..9507e61 --- /dev/null +++ b/templates/custom_css.html @@ -0,0 +1,26 @@ + + + + + Custom CSS + {% include 'head.html' %} + + + + + diff --git a/templates/dashboard.html b/templates/dashboard.html index 859d778..cf4e818 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -43,15 +43,50 @@
- + {% if is_admin %} +
+

Create Group

+
+ + + +
+ +
+ + + + + {% endif %} +
diff --git a/templates/head.html b/templates/head.html index 12e56fe..173aef5 100644 --- a/templates/head.html +++ b/templates/head.html @@ -1,5 +1,6 @@ - - + + + {% block title %}{% endblock %} @@ -9,6 +10,7 @@ + \ No newline at end of file + + {% if 'username' in session %} + + {% endif %} \ No newline at end of file diff --git a/templates/sidebar.html b/templates/sidebar.html index 79a60eb..8562d0e 100644 --- a/templates/sidebar.html +++ b/templates/sidebar.html @@ -1,3 +1,4 @@ +
ID UsernameActionsChange PasswordDisable AccountOnline?Admin?
{{ user.id }} {{ user.username }} - - + + + {{ user.online }}{{ user.is_admin }}