diff --git a/README.md b/README.md index ee9f775..0d40590 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,18 @@ Real-time display dashboard for showing current and upcoming events on the shopf ## Features - **Live Data Updates**: Auto-refreshes every 10 seconds via AJAX (no page reload) -- **48-Hour Window**: Shows current events and upcoming events within next 48 hours +- **72-Hour Window**: Shows current events and upcoming events within next 72 hours +- **Shopfloor Filtering**: Only displays notifications marked for shopfloor display +- **Type-Based Color Coding**: Visual severity indicators (Red=Incident, Yellow=Change, Green=Awareness) +- **Priority Sorting**: Critical incidents always shown first - **Real-Time Clock**: Always-on clock display - **Connection Monitoring**: Visual indicator shows live connection status - **GE Aerospace Branding**: Official colors, fonts, and logo ## Technology Stack -- **Backend**: Node.js with Express -- **Database**: MySQL 5.6 +- **Backend**: Python 3 with Flask +- **Database**: MySQL 5.6+ - **Frontend**: Vanilla JavaScript (no frameworks) - **Styling**: Custom CSS with GE Aerospace brand colors @@ -25,7 +28,7 @@ Real-time display dashboard for showing current and upcoming events on the shopf ### Prerequisites -- Node.js 16+ and npm +- Python 3.8+ with pip - MySQL 5.6+ database - Access to ShopDB database @@ -33,7 +36,7 @@ Real-time display dashboard for showing current and upcoming events on the shopf ```bash # Install dependencies -npm install +pip install -r requirements.txt # Set environment variables (optional) export DB_HOST=localhost @@ -44,10 +47,7 @@ export DB_NAME=shopdb export PORT=3001 # Start the server -npm start - -# Or for development with auto-restart -npm run dev +python3 app.py ``` ## Configuration @@ -99,8 +99,8 @@ Returns server status. ``` shopfloor-dashboard/ -├── server.js # Express server -├── package.json # Dependencies +├── app.py # Flask application +├── requirements.txt # Python dependencies ├── public/ │ ├── index.html # Dashboard UI │ └── ge-aerospace-logo.svg @@ -110,17 +110,24 @@ shopfloor-dashboard/ ## Database Schema -Queries the `notifications` table in ShopDB: +Queries the `notifications` table in ShopDB with shopfloor filtering: ```sql -SELECT notificationid, notification, starttime, endtime, - ticketnumber, link, isactive -FROM notifications -WHERE isactive = 1 -AND (conditions for current/upcoming) -ORDER BY starttime ASC +SELECT n.notificationid, n.notification, n.starttime, n.endtime, + n.ticketnumber, n.link, n.isactive, n.isshopfloor, + nt.typename, nt.typecolor +FROM notifications n +LEFT JOIN notificationtypes nt ON n.notificationtypeid = nt.notificationtypeid +WHERE n.isactive = 1 +AND n.isshopfloor = 1 +AND (conditions for 72-hour window) +ORDER BY n.starttime ASC ``` +**Key Fields:** +- `isshopfloor`: Boolean flag (0/1) - only events with `1` appear on dashboard +- `typecolor`: Used for visual severity indicators (danger/warning/success) + ## Design ### Colors (GE Aerospace Brand) @@ -173,50 +180,72 @@ git push ## Development ```bash -# Install dev dependencies -npm install +# Install dependencies +pip install -r requirements.txt -# Run with auto-restart -npm run dev +# Run the Flask development server +python3 app.py -# The server will restart automatically when you edit files +# The built-in Flask server auto-reloads on code changes when debug=True ``` ## Deployment -For production deployment: +For production deployment (no pip/npm required on production server): -1. Use a process manager (PM2, systemd) -2. Set environment variables -3. Configure reverse proxy (nginx/Apache) if needed -4. Enable SSL/TLS for secure connections +### Option 1: Package with Dependencies (Recommended) -### Example with PM2: +1. Package entire directory including installed Python packages +2. Copy to production server +3. Run directly with Python 3 +4. Use a process manager (systemd, supervisor, or PM2) + +### Option 2: Install on Production ```bash -npm install -g pm2 -pm2 start server.js --name shopfloor-dashboard +# Install dependencies on production +pip install -r requirements.txt + +# Run with production WSGI server (recommended) +pip install gunicorn +gunicorn -w 4 -b 0.0.0.0:3001 app:app + +# Or use PM2 with Python +pm2 start app.py --name shopfloor-dashboard --interpreter python3 pm2 save pm2 startup ``` +**Note**: Flask's built-in server (`python3 app.py`) works for production if using a process manager, but gunicorn is recommended for higher traffic. + ## Troubleshooting **Port already in use:** ```bash # Use a different port -PORT=3001 npm start +PORT=3001 python3 app.py ``` **Can't connect to database:** - Verify MySQL is running -- Check credentials in environment variables +- Check credentials in environment variables or app.py - Ensure database exists and user has permissions +- Verify `mysql-connector-python` is installed **Dashboard not updating:** - Check browser console for errors - Verify `/api/notifications` endpoint returns data - Check network connectivity +- Ensure Flask server is running + +**Python module errors:** +```bash +# Reinstall dependencies +pip install -r requirements.txt + +# Or install individually +pip install Flask mysql-connector-python +``` ## License diff --git a/app.py b/app.py new file mode 100644 index 0000000..fe90a50 --- /dev/null +++ b/app.py @@ -0,0 +1,131 @@ +from flask import Flask, jsonify, send_from_directory +import mysql.connector +from datetime import datetime, timedelta +import os + +app = Flask(__name__, static_folder='public') + +# Database configuration +DB_CONFIG = { + 'host': os.environ.get('DB_HOST', 'localhost'), + 'port': int(os.environ.get('DB_PORT', 3306)), + 'user': os.environ.get('DB_USER', '570005354'), + 'password': os.environ.get('DB_PASS', '570005354'), + 'database': os.environ.get('DB_NAME', 'shopdb') +} + +# Get database connection +def get_db_connection(): + return mysql.connector.connect(**DB_CONFIG) + +# Serve static files (index.html, logo, etc) +@app.route('/') +def index(): + return send_from_directory('public', 'index.html') + +@app.route('/') +def static_files(path): + return send_from_directory('public', path) + +# API endpoint to get notifications +@app.route('/api/notifications') +def get_notifications(): + try: + now = datetime.now() + future = now + timedelta(hours=72) # 72 hours from now + + # Connect to database + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + + # Query with isshopfloor filter and 72-hour window + query = """ + SELECT n.notificationid, n.notification, n.starttime, n.endtime, n.ticketnumber, n.link, + n.isactive, n.isshopfloor, nt.typename, nt.typecolor + FROM notifications n + LEFT JOIN notificationtypes nt ON n.notificationtypeid = nt.notificationtypeid + WHERE n.isactive = 1 + AND n.isshopfloor = 1 + AND ( + (n.starttime <= %s AND (n.endtime IS NULL OR n.endtime >= %s)) + OR (n.starttime BETWEEN %s AND %s) + ) + ORDER BY n.starttime ASC + """ + + cursor.execute(query, (future, now, now, future)) + rows = cursor.fetchall() + + cursor.close() + conn.close() + + # Convert bit fields to boolean and datetime to ISO strings + for row in rows: + row['isactive'] = bool(row['isactive']) + row['isshopfloor'] = bool(row['isshopfloor']) + if row['starttime']: + row['starttime'] = row['starttime'].isoformat() + if row['endtime']: + row['endtime'] = row['endtime'].isoformat() + + # Categorize notifications + current_events = [] + upcoming_events = [] + + for notification in rows: + start = datetime.fromisoformat(notification['starttime']) + end = datetime.fromisoformat(notification['endtime']) if notification['endtime'] else None + + if start <= now and (end is None or end >= now): + current_events.append(notification) + else: + upcoming_events.append(notification) + + # Sort current events by severity priority, then by starttime + # Priority: Incident (danger) > Change (warning) > Awareness/TBD (success) + severity_priority = { + 'danger': 1, # Incident - highest priority + 'warning': 2, # Change + 'success': 3, # Awareness/TBD - lowest priority + 'secondary': 4 # Fallback + } + + current_events.sort(key=lambda x: ( + severity_priority.get(x['typecolor'], 4), + datetime.fromisoformat(x['starttime']) + )) + + # Upcoming events stay sorted by starttime only + upcoming_events.sort(key=lambda x: datetime.fromisoformat(x['starttime'])) + + return jsonify({ + 'success': True, + 'timestamp': datetime.now().isoformat(), + 'current': current_events, + 'upcoming': upcoming_events + }) + + except mysql.connector.Error as err: + return jsonify({ + 'success': False, + 'error': f'Database error: {str(err)}' + }), 500 + except Exception as e: + return jsonify({ + 'success': False, + 'error': f'Server error: {str(e)}' + }), 500 + +# Health check endpoint +@app.route('/health') +def health(): + return jsonify({ + 'status': 'ok', + 'timestamp': datetime.now().isoformat() + }) + +if __name__ == '__main__': + port = int(os.environ.get('PORT', 3001)) + print(f'Shopfloor Dashboard running on port {port}') + print(f'Access at: http://localhost:{port}') + app.run(host='0.0.0.0', port=port, debug=False) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6f385a3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Flask==3.0.0 +mysql-connector-python==8.2.0