|
|
from flask import Flask, render_template, jsonify, request |
|
|
from flask_cors import CORS |
|
|
import json |
|
|
import threading |
|
|
import time |
|
|
from datetime import datetime, date, timedelta |
|
|
from attendance_tracker import AttendanceTracker |
|
|
from database import AttendanceDatabase |
|
|
|
|
|
app = Flask(__name__) |
|
|
CORS(app) |
|
|
|
|
|
|
|
|
tracker = None |
|
|
db = None |
|
|
latest_events = [] |
|
|
is_monitoring = False |
|
|
|
|
|
def initialize_system(): |
|
|
"""Initialize the attendance tracking system.""" |
|
|
global tracker, db |
|
|
tracker = AttendanceTracker() |
|
|
db = AttendanceDatabase() |
|
|
|
|
|
|
|
|
db.sync_employees_from_config() |
|
|
|
|
|
def monitoring_loop(): |
|
|
"""Background monitoring loop.""" |
|
|
global latest_events, is_monitoring |
|
|
|
|
|
while is_monitoring: |
|
|
try: |
|
|
events = tracker.scan_once() |
|
|
|
|
|
|
|
|
latest_events.extend(events) |
|
|
latest_events = latest_events[-50:] |
|
|
|
|
|
time.sleep(tracker.scan_interval) |
|
|
except Exception as e: |
|
|
print(f"Error in monitoring loop: {e}") |
|
|
time.sleep(5) |
|
|
|
|
|
@app.route('/') |
|
|
def index(): |
|
|
"""Serve the main dashboard page.""" |
|
|
return render_template('index.html') |
|
|
|
|
|
@app.route('/api/status') |
|
|
def get_status(): |
|
|
"""Get current system status.""" |
|
|
return jsonify({ |
|
|
'is_monitoring': is_monitoring, |
|
|
'employee_count': len(tracker.employees) if tracker else 0, |
|
|
'scan_interval': tracker.scan_interval if tracker else 60, |
|
|
'current_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), |
|
|
'office_timeout': f"{tracker.office_timeout_hour:02d}:{tracker.office_timeout_minute:02d}" if tracker else "17:00" |
|
|
}) |
|
|
|
|
|
@app.route('/api/employees') |
|
|
def get_employees(): |
|
|
"""Get all employees and their current status.""" |
|
|
if not tracker: |
|
|
return jsonify([]) |
|
|
|
|
|
status = tracker.get_current_status() |
|
|
employees = [] |
|
|
|
|
|
for mac, info in status.items(): |
|
|
employees.append({ |
|
|
'name': info['name'], |
|
|
'mac': info['mac'], |
|
|
'is_present': info['is_present'], |
|
|
'status': info['status'], |
|
|
'last_seen': info['last_seen'], |
|
|
'time_in': info['time_in'] |
|
|
}) |
|
|
|
|
|
return jsonify(employees) |
|
|
|
|
|
@app.route('/api/events') |
|
|
def get_recent_events(): |
|
|
"""Get recent attendance events.""" |
|
|
global latest_events |
|
|
|
|
|
|
|
|
events_data = [] |
|
|
for mac, event_type, timestamp in latest_events: |
|
|
employee_name = tracker.get_employee_name(mac) if tracker else f"Unknown ({mac})" |
|
|
events_data.append({ |
|
|
'name': employee_name, |
|
|
'mac': mac, |
|
|
'event_type': event_type, |
|
|
'timestamp': timestamp.strftime('%Y-%m-%d %H:%M:%S'), |
|
|
'time_ago': get_time_ago(timestamp) |
|
|
}) |
|
|
|
|
|
|
|
|
events_data.sort(key=lambda x: x['timestamp'], reverse=True) |
|
|
|
|
|
return jsonify(events_data) |
|
|
|
|
|
@app.route('/api/attendance_events') |
|
|
def get_attendance_events(): |
|
|
"""Get attendance events from database.""" |
|
|
if not db: |
|
|
return jsonify([]) |
|
|
|
|
|
date_filter = request.args.get('date') |
|
|
limit = int(request.args.get('limit', 50)) |
|
|
|
|
|
events = db.get_attendance_events(date=date_filter, limit=limit) |
|
|
|
|
|
|
|
|
for event in events: |
|
|
timestamp = datetime.fromisoformat(event['timestamp']) |
|
|
event['time_ago'] = get_time_ago(timestamp) |
|
|
|
|
|
return jsonify(events) |
|
|
|
|
|
@app.route('/api/daily_summary') |
|
|
def get_daily_summary(): |
|
|
"""Get daily attendance summary.""" |
|
|
if not db: |
|
|
return jsonify([]) |
|
|
|
|
|
date_str = request.args.get('date', date.today().strftime('%Y-%m-%d')) |
|
|
summary = db.get_daily_summary(date_str) |
|
|
|
|
|
|
|
|
for employee in summary: |
|
|
employee['total_break_formatted'] = format_duration(employee['total_break_duration']) |
|
|
employee['total_work_formatted'] = format_duration(employee['total_work_duration']) |
|
|
|
|
|
return jsonify(summary) |
|
|
|
|
|
@app.route('/api/summary_stats') |
|
|
def get_summary_stats(): |
|
|
"""Get summary statistics for the dashboard.""" |
|
|
if not db: |
|
|
return jsonify({}) |
|
|
|
|
|
date_str = request.args.get('date', date.today().strftime('%Y-%m-%d')) |
|
|
summary = db.get_daily_summary(date_str) |
|
|
|
|
|
stats = { |
|
|
'total_employees': len(summary), |
|
|
'present_count': len([emp for emp in summary if emp['status'] == 'Present']), |
|
|
'absent_count': len([emp for emp in summary if emp['status'] == 'Absent']), |
|
|
'on_break_count': len([emp for emp in summary if emp['status'] == 'On Break']), |
|
|
'timed_out_count': len([emp for emp in summary if emp['status'] == 'Timed Out']), |
|
|
'total_events': len(db.get_attendance_events(date=date_str, limit=1000)) |
|
|
} |
|
|
|
|
|
return jsonify(stats) |
|
|
|
|
|
@app.route('/api/start_monitoring', methods=['POST']) |
|
|
def start_monitoring(): |
|
|
"""Start the attendance monitoring.""" |
|
|
global is_monitoring |
|
|
|
|
|
if not is_monitoring: |
|
|
is_monitoring = True |
|
|
monitoring_thread = threading.Thread(target=monitoring_loop, daemon=True) |
|
|
monitoring_thread.start() |
|
|
return jsonify({'success': True, 'message': 'Monitoring started'}) |
|
|
else: |
|
|
return jsonify({'success': False, 'message': 'Monitoring already running'}) |
|
|
|
|
|
@app.route('/api/stop_monitoring', methods=['POST']) |
|
|
def stop_monitoring(): |
|
|
"""Stop the attendance monitoring.""" |
|
|
global is_monitoring |
|
|
is_monitoring = False |
|
|
return jsonify({'success': True, 'message': 'Monitoring stopped'}) |
|
|
|
|
|
@app.route('/api/export_csv') |
|
|
def export_csv(): |
|
|
"""Export daily summary to CSV.""" |
|
|
if not db: |
|
|
return jsonify({'success': False, 'message': 'Database not available'}) |
|
|
|
|
|
date_str = request.args.get('date', date.today().strftime('%Y-%m-%d')) |
|
|
|
|
|
try: |
|
|
db.export_daily_summary_to_csv(date_str) |
|
|
return jsonify({'success': True, 'message': f'CSV exported for {date_str}'}) |
|
|
except Exception as e: |
|
|
return jsonify({'success': False, 'message': f'Export failed: {str(e)}'}) |
|
|
|
|
|
def get_time_ago(timestamp): |
|
|
"""Get human-readable time difference.""" |
|
|
now = datetime.now() |
|
|
diff = now - timestamp |
|
|
|
|
|
if diff.days > 0: |
|
|
return f"{diff.days} day{'s' if diff.days > 1 else ''} ago" |
|
|
elif diff.seconds > 3600: |
|
|
hours = diff.seconds // 3600 |
|
|
return f"{hours} hour{'s' if hours > 1 else ''} ago" |
|
|
elif diff.seconds > 60: |
|
|
minutes = diff.seconds // 60 |
|
|
return f"{minutes} minute{'s' if minutes > 1 else ''} ago" |
|
|
else: |
|
|
return "Just now" |
|
|
|
|
|
def format_duration(seconds): |
|
|
"""Format duration in seconds to HH:MM:SS.""" |
|
|
if seconds is None: |
|
|
return "00:00:00" |
|
|
|
|
|
hours = seconds // 3600 |
|
|
minutes = (seconds % 3600) // 60 |
|
|
seconds = seconds % 60 |
|
|
|
|
|
return f"{hours:02d}:{minutes:02d}:{seconds:02d}" |
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
|
|
initialize_system() |
|
|
|
|
|
|
|
|
port = tracker.config.get('web_port', 5000) if tracker else 5000 |
|
|
|
|
|
print(f"Starting WiFi Attendance Tracker Web Interface on port {port}") |
|
|
print(f"Open your browser and go to: http://localhost:{port}") |
|
|
|
|
|
|
|
|
is_monitoring = True |
|
|
monitoring_thread = threading.Thread(target=monitoring_loop, daemon=True) |
|
|
monitoring_thread.start() |
|
|
|
|
|
|
|
|
app.run(host='0.0.0.0', port=port, debug=False) |
|
|
|
|
|
|