diff --git a/app.py b/app.py index 01f48ee..f9626dc 100644 --- a/app.py +++ b/app.py @@ -7,6 +7,7 @@ from datetime import datetime, date, time, timedelta from sqlalchemy import text from user_agents import parse import io, os, base64, re, uuid, requests, json, qrcode, math, traceback, googlemaps +import time as _time # Separate from datetime.time — used for performance timing (time.time()) import openpyxl.cell.cell from PIL import Image, ImageDraw from math import radians, sin, cos, asin, sqrt @@ -14,7 +15,6 @@ from dotenv import load_dotenv # Import the logging handler from logger_handler import AppLogger, log_user_activity, log_database_operations -from single_checkin_calculator import SingleCheckInCalculator from working_hours_calculator import WorkingHoursCalculator, round_time_to_quarter_hour, convert_minutes_to_base100, round_base100_hours from payroll_excel_exporter import PayrollExcelExporter from enhanced_payroll_excel_exporter import EnhancedPayrollExcelExporter @@ -11193,18 +11193,27 @@ def create_tables(): # Create default admin user if not exists admin = User.query.filter_by(username='admin').first() if not admin: + default_password = os.environ.get('DEFAULT_ADMIN_PASSWORD', 'admin123') admin = User( full_name='System Administrator', email='admin@example.com', username='admin', role='admin' ) - admin.set_password('admin123') # Change this in production + admin.set_password(default_password) db.session.add(admin) db.session.commit() - # Log admin user creation - logger_handler.logger.info("Default admin user created during initialization") + # Warn if the insecure default password is still in use + if default_password == 'admin123': + print("⚠️ WARNING: Default admin password 'admin123' is in use. " + "Set DEFAULT_ADMIN_PASSWORD in your .env file before going to production.") + logger_handler.logger.warning( + "Default admin user created with insecure default password. " + "Set DEFAULT_ADMIN_PASSWORD environment variable." + ) + else: + logger_handler.logger.info("Default admin user created during initialization") # Initialize logging table logger_handler._create_log_table() @@ -11401,7 +11410,7 @@ def log_response_info(response): # Log slow requests (over 5 seconds) if hasattr(request, 'start_time'): - duration = time.time() - request.start_time + duration = _time.time() - request.start_time if duration > 5.0: logger_handler.logger.warning(f"Slow request: {request.path} took {duration:.2f} seconds") @@ -11475,12 +11484,12 @@ def log_slow_query_performance(): """Monitor and log slow query performance""" @app.before_request def before_request(): - g.start_time = time.time() + g.start_time = _time.time() @app.after_request def after_request(response): if hasattr(g, 'start_time'): - duration = time.time() - g.start_time + duration = _time.time() - g.start_time # Log slow requests (over 2 seconds) if duration > 2.0: @@ -11503,6 +11512,9 @@ if __name__ == '__main__': try: # Initialize database and logging create_tables() + + # Register slow query performance monitoring hooks + log_slow_query_performance() # Initialize performance optimizations print("🚀 Initializing performance optimizations...") diff --git a/logger_handler.py b/logger_handler.py index 1686036..bdc39c0 100644 --- a/logger_handler.py +++ b/logger_handler.py @@ -856,6 +856,50 @@ class AppLogger: severity='INFO' ) + def verify_log_table_exists(self): + """Verify that the log_events table exists and has the correct structure""" + try: + check_table_sql = """ + SELECT COUNT(*) as table_exists + FROM information_schema.tables + WHERE table_schema = DATABASE() + AND table_name = 'log_events' + """ + result = self.db.session.execute(text(check_table_sql)).fetchone() + if result.table_exists == 0: + print("⚠️ log_events table does not exist. Creating it now...") + self._create_log_table() + return True + count_sql = "SELECT COUNT(*) as record_count FROM log_events" + count_result = self.db.session.execute(text(count_sql)).fetchone() + print(f"✅ log_events table exists with {count_result.record_count} records") + return True + except Exception as e: + print(f"❌ Error verifying log table: {e}") + return False + + def log_modal_interaction(self, event_type, description, additional_data=None): + """Log modal interactions for debugging""" + try: + context = self._get_request_context() + event_data = { + 'interaction_type': event_type, + 'event_timestamp': datetime.now().isoformat(), + 'request_context': context + } + if additional_data: + event_data['additional_data'] = additional_data + message = f"Modal interaction: {event_type} - {description}" + self._log_to_database( + event_type='modal_interaction', + event_category='ui', + description=message, + event_data=event_data, + severity='INFO' + ) + except Exception as e: + print(f"Error logging modal interaction: {e}") + # DECORATOR FUNCTIONS FOR AUTOMATIC LOGGING def log_user_activity(activity_type): @@ -917,63 +961,6 @@ def log_database_operations(operation_name): return decorated_function return decorator - def verify_log_table_exists(self): - """Verify that the log_events table exists and has the correct structure""" - try: - # Check if table exists - check_table_sql = """ - SELECT COUNT(*) as table_exists - FROM information_schema.tables - WHERE table_schema = DATABASE() - AND table_name = 'log_events' - """ - - result = self.db.session.execute(text(check_table_sql)).fetchone() - - if result.table_exists == 0: - print("⚠️ log_events table does not exist. Creating it now...") - self._create_log_table() - return True - - # Check if table has records - count_sql = "SELECT COUNT(*) as record_count FROM log_events" - count_result = self.db.session.execute(text(count_sql)).fetchone() - - print(f"✅ log_events table exists with {count_result.record_count} records") - return True - - except Exception as e: - print(f"❌ Error verifying log table: {e}") - return False - - def log_modal_interaction(self, event_type, description, additional_data=None): - """Log modal interactions for debugging""" - try: - context = self._get_request_context() - - event_data = { - 'interaction_type': event_type, - 'event_timestamp': datetime.now().isoformat(), - 'request_context': context - } - - if additional_data: - event_data['additional_data'] = additional_data - - message = f"Modal interaction: {event_type} - {description}" - - # Log to database - self._log_to_database( - event_type='modal_interaction', - event_category='ui', - description=message, - event_data=event_data, - severity='INFO' - ) - - except Exception as e: - print(f"Error logging modal interaction: {e}") - # INITIALIZATION FUNCTION def init_logging(app, db): """Initialize the logging system with the Flask app""" diff --git a/templates/attendance_report.html b/templates/attendance_report.html index 37933c7..90eca03 100644 --- a/templates/attendance_report.html +++ b/templates/attendance_report.html @@ -811,23 +811,57 @@ document.addEventListener('DOMContentLoaded', function() { } function displayEmployeeAutocompleteResults(employees) { + // Clear previous results safely + autocompleteResultsFilter.innerHTML = ''; + if (employees.length === 0) { - autocompleteResultsFilter.innerHTML = '
No employees found
'; - positionDropdown(); - autocompleteResultsFilter.classList.add('show'); - return; + const noResult = document.createElement('div'); + noResult.className = 'autocomplete-item-filter'; + noResult.style.cssText = 'cursor:default;color:#999;'; + noResult.textContent = 'No employees found'; + autocompleteResultsFilter.appendChild(noResult); + } else { + employees.forEach(function(emp) { + const isUnregistered = (emp.lastName === '(no record)'); + const displayName = isUnregistered + ? 'ID: ' + emp.id + : emp.lastName + ', ' + emp.firstName; + + const item = document.createElement('div'); + item.className = 'autocomplete-item-filter'; + + const info = document.createElement('div'); + info.className = 'employee-info-filter'; + + const nameSpan = document.createElement('span'); + nameSpan.className = 'employee-name-filter'; + if (isUnregistered) { + nameSpan.textContent = 'ID: ' + emp.id + ' '; + const em = document.createElement('em'); + em.style.color = '#94a3b8'; + em.textContent = '(no record)'; + nameSpan.appendChild(em); + } else { + nameSpan.textContent = displayName; + } + + const idSpan = document.createElement('span'); + idSpan.className = 'employee-id-filter'; + idSpan.textContent = 'ID: ' + emp.id; + + info.appendChild(nameSpan); + info.appendChild(idSpan); + item.appendChild(info); + + // Closure captures values safely regardless of special characters in names + item.addEventListener('click', (function(id, name) { + return function() { selectEmployeeFilter(id, name); }; + }(emp.id, displayName))); + + autocompleteResultsFilter.appendChild(item); + }); } - - const html = employees.map(emp => ` -
-
- ${emp.lastName}, ${emp.firstName} - ID: ${emp.id} -
-
- `).join(''); - - autocompleteResultsFilter.innerHTML = html; + positionDropdown(); autocompleteResultsFilter.classList.add('show'); } @@ -853,19 +887,19 @@ document.addEventListener('DOMContentLoaded', function() { // Submit the form to refresh with cleared filter employeeSearchFilter.closest('form').submit(); }; + + // Reposition dropdown on scroll or resize + window.addEventListener('scroll', function() { + if (autocompleteResultsFilter.classList.contains('show')) { + positionDropdown(); + } + }, true); + + window.addEventListener('resize', function() { + if (autocompleteResultsFilter.classList.contains('show')) { + positionDropdown(); + } + }); })(); - -// Reposition dropdown on scroll or resize -window.addEventListener('scroll', function() { - if (autocompleteResultsFilter.classList.contains('show')) { - positionDropdown(); - } -}, true); - -window.addEventListener('resize', function() { - if (autocompleteResultsFilter.classList.contains('show')) { - positionDropdown(); - } -}); {% endblock %} \ No newline at end of file