Feature: Convert from Node.js/Express to Python/Flask
BREAKING CHANGE: Replaced Node.js backend with Python Flask Reason: npm not available on production server, Python/pip is available. Changes: - Created app.py (Flask) to replace server.js (Node.js) - Created requirements.txt with only 2 dependencies (Flask, mysql-connector-python) - Updated README.md with Flask installation and deployment instructions - Maintained all existing functionality: * Same API endpoints (/api/notifications, /health) * Same database queries (isshopfloor filter, 72-hour window) * Same priority sorting (incidents first) * Serves static files from public/ directory * Same environment variable configuration Dependencies: - Flask==3.0.0 - mysql-connector-python==8.2.0 The public/index.html frontend remains unchanged - only the backend was converted. Tested and verified: - API endpoint returns correct data - Health check responds - Dashboard displays properly - Database connectivity working - PM2 process manager compatible 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
95
README.md
95
README.md
@@ -9,15 +9,18 @@ Real-time display dashboard for showing current and upcoming events on the shopf
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Live Data Updates**: Auto-refreshes every 10 seconds via AJAX (no page reload)
|
- **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
|
- **Real-Time Clock**: Always-on clock display
|
||||||
- **Connection Monitoring**: Visual indicator shows live connection status
|
- **Connection Monitoring**: Visual indicator shows live connection status
|
||||||
- **GE Aerospace Branding**: Official colors, fonts, and logo
|
- **GE Aerospace Branding**: Official colors, fonts, and logo
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
- **Backend**: Node.js with Express
|
- **Backend**: Python 3 with Flask
|
||||||
- **Database**: MySQL 5.6
|
- **Database**: MySQL 5.6+
|
||||||
- **Frontend**: Vanilla JavaScript (no frameworks)
|
- **Frontend**: Vanilla JavaScript (no frameworks)
|
||||||
- **Styling**: Custom CSS with GE Aerospace brand colors
|
- **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
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js 16+ and npm
|
- Python 3.8+ with pip
|
||||||
- MySQL 5.6+ database
|
- MySQL 5.6+ database
|
||||||
- Access to ShopDB database
|
- Access to ShopDB database
|
||||||
|
|
||||||
@@ -33,7 +36,7 @@ Real-time display dashboard for showing current and upcoming events on the shopf
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
npm install
|
pip install -r requirements.txt
|
||||||
|
|
||||||
# Set environment variables (optional)
|
# Set environment variables (optional)
|
||||||
export DB_HOST=localhost
|
export DB_HOST=localhost
|
||||||
@@ -44,10 +47,7 @@ export DB_NAME=shopdb
|
|||||||
export PORT=3001
|
export PORT=3001
|
||||||
|
|
||||||
# Start the server
|
# Start the server
|
||||||
npm start
|
python3 app.py
|
||||||
|
|
||||||
# Or for development with auto-restart
|
|
||||||
npm run dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -99,8 +99,8 @@ Returns server status.
|
|||||||
|
|
||||||
```
|
```
|
||||||
shopfloor-dashboard/
|
shopfloor-dashboard/
|
||||||
├── server.js # Express server
|
├── app.py # Flask application
|
||||||
├── package.json # Dependencies
|
├── requirements.txt # Python dependencies
|
||||||
├── public/
|
├── public/
|
||||||
│ ├── index.html # Dashboard UI
|
│ ├── index.html # Dashboard UI
|
||||||
│ └── ge-aerospace-logo.svg
|
│ └── ge-aerospace-logo.svg
|
||||||
@@ -110,17 +110,24 @@ shopfloor-dashboard/
|
|||||||
|
|
||||||
## Database Schema
|
## Database Schema
|
||||||
|
|
||||||
Queries the `notifications` table in ShopDB:
|
Queries the `notifications` table in ShopDB with shopfloor filtering:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT notificationid, notification, starttime, endtime,
|
SELECT n.notificationid, n.notification, n.starttime, n.endtime,
|
||||||
ticketnumber, link, isactive
|
n.ticketnumber, n.link, n.isactive, n.isshopfloor,
|
||||||
FROM notifications
|
nt.typename, nt.typecolor
|
||||||
WHERE isactive = 1
|
FROM notifications n
|
||||||
AND (conditions for current/upcoming)
|
LEFT JOIN notificationtypes nt ON n.notificationtypeid = nt.notificationtypeid
|
||||||
ORDER BY starttime ASC
|
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
|
## Design
|
||||||
|
|
||||||
### Colors (GE Aerospace Brand)
|
### Colors (GE Aerospace Brand)
|
||||||
@@ -173,50 +180,72 @@ git push
|
|||||||
## Development
|
## Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dev dependencies
|
# Install dependencies
|
||||||
npm install
|
pip install -r requirements.txt
|
||||||
|
|
||||||
# Run with auto-restart
|
# Run the Flask development server
|
||||||
npm run dev
|
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
|
## Deployment
|
||||||
|
|
||||||
For production deployment:
|
For production deployment (no pip/npm required on production server):
|
||||||
|
|
||||||
1. Use a process manager (PM2, systemd)
|
### Option 1: Package with Dependencies (Recommended)
|
||||||
2. Set environment variables
|
|
||||||
3. Configure reverse proxy (nginx/Apache) if needed
|
|
||||||
4. Enable SSL/TLS for secure connections
|
|
||||||
|
|
||||||
### 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
|
```bash
|
||||||
npm install -g pm2
|
# Install dependencies on production
|
||||||
pm2 start server.js --name shopfloor-dashboard
|
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 save
|
||||||
pm2 startup
|
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
|
## Troubleshooting
|
||||||
|
|
||||||
**Port already in use:**
|
**Port already in use:**
|
||||||
```bash
|
```bash
|
||||||
# Use a different port
|
# Use a different port
|
||||||
PORT=3001 npm start
|
PORT=3001 python3 app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
**Can't connect to database:**
|
**Can't connect to database:**
|
||||||
- Verify MySQL is running
|
- Verify MySQL is running
|
||||||
- Check credentials in environment variables
|
- Check credentials in environment variables or app.py
|
||||||
- Ensure database exists and user has permissions
|
- Ensure database exists and user has permissions
|
||||||
|
- Verify `mysql-connector-python` is installed
|
||||||
|
|
||||||
**Dashboard not updating:**
|
**Dashboard not updating:**
|
||||||
- Check browser console for errors
|
- Check browser console for errors
|
||||||
- Verify `/api/notifications` endpoint returns data
|
- Verify `/api/notifications` endpoint returns data
|
||||||
- Check network connectivity
|
- 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
|
## License
|
||||||
|
|
||||||
|
|||||||
131
app.py
Normal file
131
app.py
Normal file
@@ -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('/<path:path>')
|
||||||
|
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)
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Flask==3.0.0
|
||||||
|
mysql-connector-python==8.2.0
|
||||||
Reference in New Issue
Block a user