unread notifs
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,18 @@
|
||||
from flask import render_template, request, flash, redirect, url_for
|
||||
from flask import render_template, request, flash, redirect, url_for, Blueprint, jsonify
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from models import db, User
|
||||
from models import db, User, Notif
|
||||
from functools import wraps
|
||||
from datetime import datetime
|
||||
from utils import log_event, create_notification
|
||||
from utils import log_event, create_notification, get_unread_count
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
@auth_bp.context_processor
|
||||
def inject_unread_notifications():
|
||||
if current_user.is_authenticated:
|
||||
unread_count = get_unread_count(current_user.id)
|
||||
return {'unread_notifications': unread_count}
|
||||
return {'unread_notifications': 0}
|
||||
|
||||
def require_password_change(f):
|
||||
@wraps(f)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, User
|
||||
from models import db, User, Notif
|
||||
from forms import UserForm
|
||||
from flask import abort
|
||||
from sqlalchemy import or_
|
||||
from routes.auth import require_password_change
|
||||
from utils import log_event, create_notification
|
||||
from utils import log_event, create_notification, get_unread_count
|
||||
import json
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
@@ -17,6 +17,13 @@ UPLOAD_FOLDER = os.path.join(os.getcwd(), 'uploads', 'profile_pics')
|
||||
if not os.path.exists(UPLOAD_FOLDER):
|
||||
os.makedirs(UPLOAD_FOLDER)
|
||||
|
||||
@contacts_bp.context_processor
|
||||
def inject_unread_notifications():
|
||||
if current_user.is_authenticated:
|
||||
unread_count = get_unread_count(current_user.id)
|
||||
return {'unread_notifications': unread_count}
|
||||
return {'unread_notifications': 0}
|
||||
|
||||
def admin_required():
|
||||
if not current_user.is_authenticated:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
@@ -3,7 +3,7 @@ from flask_login import login_required, current_user
|
||||
from models import db, Conversation, User, Message, MessageAttachment
|
||||
from forms import ConversationForm
|
||||
from routes.auth import require_password_change
|
||||
from utils import log_event, create_notification
|
||||
from utils import log_event, create_notification, get_unread_count
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
from datetime import datetime
|
||||
@@ -54,7 +54,8 @@ def conversations():
|
||||
if search:
|
||||
query = query.filter(Conversation.name.ilike(f'%{search}%'))
|
||||
conversations = query.order_by(Conversation.created_at.desc()).all()
|
||||
return render_template('conversations/conversations.html', conversations=conversations, search=search)
|
||||
unread_count = get_unread_count(current_user.id)
|
||||
return render_template('conversations/conversations.html', conversations=conversations, search=search, unread_notifications=unread_count)
|
||||
|
||||
@conversations_bp.route('/create', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
|
||||
@@ -10,7 +10,7 @@ import logging
|
||||
import sys
|
||||
import time
|
||||
from forms import CompanySettingsForm
|
||||
from utils import log_event, create_notification
|
||||
from utils import log_event, create_notification, get_unread_count
|
||||
|
||||
# Set up logging to show in console
|
||||
logging.basicConfig(
|
||||
@@ -28,6 +28,13 @@ def init_routes(main_bp):
|
||||
site_settings = SiteSettings.query.first()
|
||||
return dict(site_settings=site_settings)
|
||||
|
||||
@main_bp.context_processor
|
||||
def inject_unread_notifications():
|
||||
if current_user.is_authenticated:
|
||||
unread_count = get_unread_count(current_user.id)
|
||||
return {'unread_notifications': unread_count}
|
||||
return {'unread_notifications': 0}
|
||||
|
||||
@main_bp.route('/')
|
||||
@login_required
|
||||
@require_password_change
|
||||
|
||||
@@ -22,14 +22,14 @@ to maintain file metadata and content.
|
||||
from flask import Blueprint, jsonify, request, abort, send_from_directory, send_file
|
||||
from flask_login import login_required, current_user
|
||||
import os
|
||||
from models import Room, RoomMemberPermission, RoomFile, TrashedFile, db
|
||||
from models import Room, RoomMemberPermission, RoomFile, TrashedFile, db, User, Notif
|
||||
from werkzeug.utils import secure_filename, safe_join
|
||||
import time
|
||||
import shutil
|
||||
import io
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from utils import log_event
|
||||
from utils import log_event, create_notification, get_unread_count
|
||||
|
||||
# Blueprint for room file operations
|
||||
room_files_bp = Blueprint('room_files', __name__, url_prefix='/api/rooms')
|
||||
@@ -61,6 +61,13 @@ ALLOWED_EXTENSIONS = {
|
||||
'eml', 'msg', 'vcf', 'ics'
|
||||
}
|
||||
|
||||
@room_files_bp.context_processor
|
||||
def inject_unread_notifications():
|
||||
if current_user.is_authenticated:
|
||||
unread_count = get_unread_count(current_user.id)
|
||||
return {'unread_notifications': unread_count}
|
||||
return {'unread_notifications': 0}
|
||||
|
||||
def get_room_dir(room_id):
|
||||
"""
|
||||
Get the absolute path to a room's directory.
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
from flask import Blueprint, jsonify, request, abort
|
||||
from flask import Blueprint, jsonify, request, abort, render_template, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, Room, User, RoomMemberPermission
|
||||
from utils import user_has_permission, log_event, create_notification
|
||||
from models import db, Room, User, RoomMemberPermission, Notif
|
||||
from utils import user_has_permission, log_event, create_notification, get_unread_count
|
||||
from routes.auth import require_password_change
|
||||
from datetime import datetime
|
||||
|
||||
room_members_bp = Blueprint('room_members', __name__)
|
||||
room_members_bp = Blueprint('room_members', __name__, url_prefix='/api/rooms')
|
||||
|
||||
@room_members_bp.context_processor
|
||||
def inject_unread_notifications():
|
||||
if current_user.is_authenticated:
|
||||
unread_count = get_unread_count(current_user.id)
|
||||
return {'unread_notifications': unread_count}
|
||||
return {'unread_notifications': 0}
|
||||
|
||||
@room_members_bp.route('/<int:room_id>/members', methods=['GET'])
|
||||
@login_required
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, Room, User, RoomMemberPermission, RoomFile
|
||||
from models import db, Room, User, RoomMemberPermission, RoomFile, Notif
|
||||
from forms import RoomForm
|
||||
from routes.room_files import user_has_permission
|
||||
from routes.auth import require_password_change
|
||||
from utils import log_event, create_notification
|
||||
from utils import log_event, create_notification, get_unread_count
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
rooms_bp = Blueprint('rooms', __name__, url_prefix='/rooms')
|
||||
|
||||
@rooms_bp.context_processor
|
||||
def inject_unread_notifications():
|
||||
if current_user.is_authenticated:
|
||||
unread_count = get_unread_count(current_user.id)
|
||||
return {'unread_notifications': unread_count}
|
||||
return {'unread_notifications': 0}
|
||||
|
||||
@rooms_bp.route('/')
|
||||
@login_required
|
||||
@require_password_change
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
from flask import Blueprint, jsonify, request, abort
|
||||
from flask import Blueprint, jsonify, request, abort, render_template, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, Room, RoomFile, TrashedFile, UserStarredFile
|
||||
from utils import user_has_permission, clean_path, log_event
|
||||
from models import db, Room, RoomFile, TrashedFile, UserStarredFile, Notif
|
||||
from routes.auth import require_password_change
|
||||
from utils import user_has_permission, clean_path, log_event, create_notification, get_unread_count
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
trash_bp = Blueprint('trash', __name__)
|
||||
trash_bp = Blueprint('trash', __name__, url_prefix='/trash')
|
||||
|
||||
@trash_bp.context_processor
|
||||
def inject_unread_notifications():
|
||||
if current_user.is_authenticated:
|
||||
unread_count = get_unread_count(current_user.id)
|
||||
return {'unread_notifications': unread_count}
|
||||
return {'unread_notifications': 0}
|
||||
|
||||
@trash_bp.route('/<int:room_id>/trash', methods=['GET'])
|
||||
@login_required
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize tooltips
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
|
||||
// Initialize variables
|
||||
let currentPage = 1;
|
||||
let totalPages = parseInt(document.getElementById('totalPages').textContent) || 1;
|
||||
@@ -62,6 +68,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const data = await response.json();
|
||||
updateNotificationsTable(data.notifications);
|
||||
updatePagination(data.total_pages, data.current_page);
|
||||
|
||||
// Reinitialize tooltips after updating the table
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching notifications:', error);
|
||||
notifsTableBody.innerHTML = '<tr><td colspan="6" class="text-center">Error loading notifications</td></tr>';
|
||||
@@ -104,6 +116,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// Function to update notification counter
|
||||
function updateNotificationCounter() {
|
||||
counter.textContent = document.querySelector('.nav-link[href*="notifications"] .badge');
|
||||
}
|
||||
|
||||
// Function to mark notification as read
|
||||
async function markAsRead(notifId) {
|
||||
try {
|
||||
@@ -138,6 +155,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update the notification counter
|
||||
updateNotificationCounter();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error marking notification as read:', error);
|
||||
@@ -165,6 +184,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Remove the notification row from the table
|
||||
const notifRow = document.querySelector(`tr[data-notif-id="${notifId}"]`);
|
||||
if (notifRow) {
|
||||
// Only update counter if the notification was unread
|
||||
if (notifRow.classList.contains('table-warning')) {
|
||||
updateNotificationCounter();
|
||||
}
|
||||
notifRow.remove();
|
||||
}
|
||||
}
|
||||
@@ -206,6 +229,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
// Remove the notification counter
|
||||
const counter = document.querySelector('.nav-link[href*="notifications"] .badge');
|
||||
if (counter) {
|
||||
counter.remove();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error marking all notifications as read:', error);
|
||||
@@ -260,28 +288,30 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// Add event listeners for filters with debounce
|
||||
let filterTimeout;
|
||||
function debouncedFetch() {
|
||||
clearTimeout(filterTimeout);
|
||||
filterTimeout = setTimeout(() => {
|
||||
currentPage = 1; // Reset to first page when filters change
|
||||
// Initialize event listeners
|
||||
attachEventListeners();
|
||||
|
||||
// Add event listeners for filters
|
||||
notifTypeFilter.addEventListener('change', () => {
|
||||
currentPage = 1;
|
||||
fetchNotifications();
|
||||
}, 300);
|
||||
}
|
||||
updateURL();
|
||||
});
|
||||
|
||||
notifTypeFilter.addEventListener('change', debouncedFetch);
|
||||
dateRangeFilter.addEventListener('change', debouncedFetch);
|
||||
dateRangeFilter.addEventListener('change', () => {
|
||||
currentPage = 1;
|
||||
fetchNotifications();
|
||||
updateURL();
|
||||
});
|
||||
|
||||
// Add event listener for clear filters
|
||||
clearFiltersBtn.addEventListener('click', () => {
|
||||
notifTypeFilter.value = '';
|
||||
dateRangeFilter.value = '7d';
|
||||
currentPage = 1;
|
||||
fetchNotifications();
|
||||
updateURL();
|
||||
});
|
||||
|
||||
// Add event listener for mark all as read
|
||||
markAllReadBtn.addEventListener('click', markAllAsRead);
|
||||
|
||||
// Add event listeners for pagination
|
||||
@@ -289,6 +319,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
fetchNotifications();
|
||||
updateURL();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -296,20 +327,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (currentPage < totalPages) {
|
||||
currentPage++;
|
||||
fetchNotifications();
|
||||
updateURL();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize filters from URL parameters
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
notifTypeFilter.value = params.get('notif_type') || '';
|
||||
dateRangeFilter.value = params.get('date_range') || '7d';
|
||||
currentPage = parseInt(params.get('page')) || 1;
|
||||
|
||||
// Initial fetch if filters are set
|
||||
if (notifTypeFilter.value || dateRangeFilter.value !== '7d') {
|
||||
fetchNotifications();
|
||||
}
|
||||
|
||||
// Attach initial event listeners
|
||||
attachEventListeners();
|
||||
});
|
||||
@@ -32,8 +32,13 @@
|
||||
</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="d-none d-lg-flex align-items-center me-3">
|
||||
<a class="nav-link text-white" href="{{ url_for('main.notifications') }}">
|
||||
<a class="nav-link text-white position-relative" href="{{ url_for('main.notifications') }}">
|
||||
<i class="fas fa-bell text-xl" style="width: 2rem; height: 2rem; display: flex; align-items: center; justify-content: center;"></i>
|
||||
{% if unread_notifications > 0 %}
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="font-size: 0.6rem;">
|
||||
{{ unread_notifications }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
<th>Timestamp</th>
|
||||
<th>Type</th>
|
||||
<th>From</th>
|
||||
<th>Details</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@@ -84,14 +83,6 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ notif.sender.username + ' ' + notif.sender.last_name if notif.sender else 'System' }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#notifDetailsModal"
|
||||
data-notif-id="{{ notif.id }}">
|
||||
<i class="fas fa-info-circle"></i> View Details
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
{% if notif.read %}
|
||||
<span class="badge bg-success">Read</span>
|
||||
@@ -103,21 +94,39 @@
|
||||
<div class="btn-group">
|
||||
{% if notif.notif_type in ['room_invite', 'room_invite_removed'] and notif.details and notif.details.room_id %}
|
||||
<a href="{{ url_for('rooms.room', room_id=notif.details.room_id) }}"
|
||||
class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-door-open"></i> View Room
|
||||
class="btn btn-sm btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
title="View Room">
|
||||
<i class="fas fa-door-open"></i>
|
||||
</a>
|
||||
{% elif notif.notif_type in ['conversation_invite', 'conversation_invite_removed', 'conversation_message'] and notif.details and notif.details.conversation_id %}
|
||||
<a href="{{ url_for('conversations.conversation', conversation_id=notif.details.conversation_id) }}"
|
||||
class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-comments"></i> View Conversation
|
||||
class="btn btn-sm btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
title="View Conversation">
|
||||
<i class="fas fa-comments"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#notifDetailsModal"
|
||||
data-notif-id="{{ notif.id }}"
|
||||
data-bs-toggle="tooltip"
|
||||
title="View Details">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
</button>
|
||||
{% if not notif.read %}
|
||||
<button class="btn btn-sm btn-success mark-read" data-notif-id="{{ notif.id }}">
|
||||
<i class="fas fa-check"></i> Mark as Read
|
||||
<button class="btn btn-sm btn-success mark-read"
|
||||
data-notif-id="{{ notif.id }}"
|
||||
data-bs-toggle="tooltip"
|
||||
title="Mark as Read">
|
||||
<i class="fas fa-check"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
<button class="btn btn-sm btn-danger delete-notif" data-notif-id="{{ notif.id }}">
|
||||
<button class="btn btn-sm btn-danger delete-notif"
|
||||
data-notif-id="{{ notif.id }}"
|
||||
data-bs-toggle="tooltip"
|
||||
title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -126,7 +135,7 @@
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">No notifications found</td>
|
||||
<td colspan="5" class="text-center">No notifications found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user