March 10 2026: fixed some bugs, and cleaned code
This commit is contained in:
@@ -7,6 +7,7 @@ from datetime import datetime, date, time, timedelta
|
|||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from user_agents import parse
|
from user_agents import parse
|
||||||
import io, os, base64, re, uuid, requests, json, qrcode, math, traceback, googlemaps
|
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
|
import openpyxl.cell.cell
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
from math import radians, sin, cos, asin, sqrt
|
from math import radians, sin, cos, asin, sqrt
|
||||||
@@ -14,7 +15,6 @@ from dotenv import load_dotenv
|
|||||||
# Import the logging handler
|
# Import the logging handler
|
||||||
from logger_handler import AppLogger, log_user_activity, log_database_operations
|
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 working_hours_calculator import WorkingHoursCalculator, round_time_to_quarter_hour, convert_minutes_to_base100, round_base100_hours
|
||||||
from payroll_excel_exporter import PayrollExcelExporter
|
from payroll_excel_exporter import PayrollExcelExporter
|
||||||
from enhanced_payroll_excel_exporter import EnhancedPayrollExcelExporter
|
from enhanced_payroll_excel_exporter import EnhancedPayrollExcelExporter
|
||||||
@@ -11193,18 +11193,27 @@ def create_tables():
|
|||||||
# Create default admin user if not exists
|
# Create default admin user if not exists
|
||||||
admin = User.query.filter_by(username='admin').first()
|
admin = User.query.filter_by(username='admin').first()
|
||||||
if not admin:
|
if not admin:
|
||||||
|
default_password = os.environ.get('DEFAULT_ADMIN_PASSWORD', 'admin123')
|
||||||
admin = User(
|
admin = User(
|
||||||
full_name='System Administrator',
|
full_name='System Administrator',
|
||||||
email='admin@example.com',
|
email='admin@example.com',
|
||||||
username='admin',
|
username='admin',
|
||||||
role='admin'
|
role='admin'
|
||||||
)
|
)
|
||||||
admin.set_password('admin123') # Change this in production
|
admin.set_password(default_password)
|
||||||
db.session.add(admin)
|
db.session.add(admin)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Log admin user creation
|
# Warn if the insecure default password is still in use
|
||||||
logger_handler.logger.info("Default admin user created during initialization")
|
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
|
# Initialize logging table
|
||||||
logger_handler._create_log_table()
|
logger_handler._create_log_table()
|
||||||
@@ -11401,7 +11410,7 @@ def log_response_info(response):
|
|||||||
|
|
||||||
# Log slow requests (over 5 seconds)
|
# Log slow requests (over 5 seconds)
|
||||||
if hasattr(request, 'start_time'):
|
if hasattr(request, 'start_time'):
|
||||||
duration = time.time() - request.start_time
|
duration = _time.time() - request.start_time
|
||||||
if duration > 5.0:
|
if duration > 5.0:
|
||||||
logger_handler.logger.warning(f"Slow request: {request.path} took {duration:.2f} seconds")
|
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"""
|
"""Monitor and log slow query performance"""
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def before_request():
|
def before_request():
|
||||||
g.start_time = time.time()
|
g.start_time = _time.time()
|
||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
def after_request(response):
|
def after_request(response):
|
||||||
if hasattr(g, 'start_time'):
|
if hasattr(g, 'start_time'):
|
||||||
duration = time.time() - g.start_time
|
duration = _time.time() - g.start_time
|
||||||
|
|
||||||
# Log slow requests (over 2 seconds)
|
# Log slow requests (over 2 seconds)
|
||||||
if duration > 2.0:
|
if duration > 2.0:
|
||||||
@@ -11504,6 +11513,9 @@ if __name__ == '__main__':
|
|||||||
# Initialize database and logging
|
# Initialize database and logging
|
||||||
create_tables()
|
create_tables()
|
||||||
|
|
||||||
|
# Register slow query performance monitoring hooks
|
||||||
|
log_slow_query_performance()
|
||||||
|
|
||||||
# Initialize performance optimizations
|
# Initialize performance optimizations
|
||||||
print("🚀 Initializing performance optimizations...")
|
print("🚀 Initializing performance optimizations...")
|
||||||
cached_query = initialize_performance_optimizations(app, db, logger_handler)
|
cached_query = initialize_performance_optimizations(app, db, logger_handler)
|
||||||
|
|||||||
+44
-57
@@ -856,6 +856,50 @@ class AppLogger:
|
|||||||
severity='INFO'
|
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
|
# DECORATOR FUNCTIONS FOR AUTOMATIC LOGGING
|
||||||
|
|
||||||
def log_user_activity(activity_type):
|
def log_user_activity(activity_type):
|
||||||
@@ -917,63 +961,6 @@ def log_database_operations(operation_name):
|
|||||||
return decorated_function
|
return decorated_function
|
||||||
return decorator
|
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
|
# INITIALIZATION FUNCTION
|
||||||
def init_logging(app, db):
|
def init_logging(app, db):
|
||||||
"""Initialize the logging system with the Flask app"""
|
"""Initialize the logging system with the Flask app"""
|
||||||
|
|||||||
@@ -811,23 +811,57 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function displayEmployeeAutocompleteResults(employees) {
|
function displayEmployeeAutocompleteResults(employees) {
|
||||||
|
// Clear previous results safely
|
||||||
|
autocompleteResultsFilter.innerHTML = '';
|
||||||
|
|
||||||
if (employees.length === 0) {
|
if (employees.length === 0) {
|
||||||
autocompleteResultsFilter.innerHTML = '<div class="autocomplete-item-filter" style="cursor: default; color: #999;">No employees found</div>';
|
const noResult = document.createElement('div');
|
||||||
positionDropdown();
|
noResult.className = 'autocomplete-item-filter';
|
||||||
autocompleteResultsFilter.classList.add('show');
|
noResult.style.cssText = 'cursor:default;color:#999;';
|
||||||
return;
|
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 => `
|
|
||||||
<div class="autocomplete-item-filter" onclick="selectEmployeeFilter(${emp.id}, '${emp.lastName}, ${emp.firstName}')">
|
|
||||||
<div class="employee-info-filter">
|
|
||||||
<span class="employee-name-filter">${emp.lastName}, ${emp.firstName}</span>
|
|
||||||
<span class="employee-id-filter">ID: ${emp.id}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
|
|
||||||
autocompleteResultsFilter.innerHTML = html;
|
|
||||||
positionDropdown();
|
positionDropdown();
|
||||||
autocompleteResultsFilter.classList.add('show');
|
autocompleteResultsFilter.classList.add('show');
|
||||||
}
|
}
|
||||||
@@ -853,19 +887,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Submit the form to refresh with cleared filter
|
// Submit the form to refresh with cleared filter
|
||||||
employeeSearchFilter.closest('form').submit();
|
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user