diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec3160f --- /dev/null +++ b/README.md @@ -0,0 +1,407 @@ +# Infoscreen 2025 + +[![Docker](https://img.shields.io/badge/Docker-Multi--Service-blue?logo=docker)](https://www.docker.com/) +[![React](https://img.shields.io/badge/React-19.1.0-61DAFB?logo=react)](https://reactjs.org/) +[![Flask](https://img.shields.io/badge/Flask-REST_API-green?logo=flask)](https://flask.palletsprojects.com/) +[![MariaDB](https://img.shields.io/badge/MariaDB-11.2-003545?logo=mariadb)](https://mariadb.org/) +[![MQTT](https://img.shields.io/badge/MQTT-Eclipse_Mosquitto-purple)](https://mosquitto.org/) + +A comprehensive multi-service digital signage solution for educational institutions, featuring client management, event scheduling, presentation conversion, and real-time MQTT communication. + +## 🏗️ Architecture Overview + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Dashboard │ │ API Server │ │ Listener │ +│ (React/Vite) │◄──►│ (Flask) │◄──►│ (MQTT Client) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ + │ ▼ │ + │ ┌─────────────────┐ │ + │ │ MariaDB │ │ + │ │ (Database) │ │ + │ └─────────────────┘ │ + │ │ + └────────────────────┬───────────────────────────┘ + ▼ + ┌─────────────────┐ + │ MQTT Broker │ + │ (Mosquitto) │ + └─────────────────┘ + │ + ┌────────────────────┼────────────────────┐ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Scheduler │ │ Worker │ │ Infoscreen │ +│ (Events) │ │ (Conversions) │ │ Clients │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +## 🌟 Key Features + +### 📊 **Dashboard Management** +- Modern React-based web interface with Syncfusion components +- Real-time client monitoring and group management +- Event scheduling with academic period support +- Media management with presentation conversion +- Holiday calendar integration + +### 🎯 **Event System** +- **Presentations**: PowerPoint/LibreOffice → PDF conversion via Gotenberg +- **Websites**: URL-based content display +- **Videos**: Media file streaming +- **Messages**: Text announcements +- **WebUntis**: Educational schedule integration + +### 🏫 **Academic Period Management** +- Support for school years, semesters, and trimesters +- Austrian school system integration +- Holiday calendar synchronization +- Period-based event organization + +### 📡 **Real-time Communication** +- MQTT-based client discovery and heartbeat monitoring +- Retained topics for reliable state synchronization +- WebSocket support for browser clients +- Automatic client group assignment + +### 🔄 **Background Processing** +- Redis-based job queues for presentation conversion +- Gotenberg integration for LibreOffice/PowerPoint processing +- Asynchronous file processing with status tracking +- RQ (Redis Queue) worker management + +## 🚀 Quick Start + +### Prerequisites +- Docker & Docker Compose +- Git +- SSL certificates (for production) + +### Development Setup + +1. **Clone the repository** + ```bash + git clone + cd infoscreen_2025 + ``` + +2. **Environment Configuration** + ```bash + cp .env.example .env + # Edit .env with your configuration + ``` + +3. **Start the development stack** + ```bash + make up + # or: docker compose up -d --build + ``` + +4. **Access the services** + - Dashboard: http://localhost:5173 + - API: http://localhost:8000 + - Database: localhost:3306 + - MQTT: localhost:1883 (WebSocket: 9001) + +### Production Deployment + +1. **Build and push images** + ```bash + make build + make push + ``` + +2. **Deploy on server** + ```bash + make pull-prod + make up-prod + ``` + +For detailed deployment instructions, see: +- [Debian Deployment Guide](deployment-debian.md) +- [Ubuntu Deployment Guide](deployment-ubuntu.md) + +## 🛠️ Services + +### 🖥️ **Dashboard** (`dashboard/`) +- **Technology**: React 19 + TypeScript + Vite +- **UI Framework**: Syncfusion components + Tailwind CSS +- **Features**: Responsive design, real-time updates, file management +- **Port**: 5173 (dev), served via Nginx (prod) + +### 🔧 **API Server** (`server/`) +- **Technology**: Flask + SQLAlchemy + Alembic +- **Database**: MariaDB with timezone-aware timestamps +- **Features**: RESTful API, file uploads, MQTT integration +- **Port**: 8000 +- **Health Check**: `/health` + +### 👂 **Listener** (`listener/`) +- **Technology**: Python + paho-mqtt +- **Purpose**: MQTT message processing, client discovery +- **Features**: Heartbeat monitoring, automatic client registration + +### ⏰ **Scheduler** (`scheduler/`) +- **Technology**: Python + SQLAlchemy +- **Purpose**: Event publishing, group-based content distribution +- **Features**: Time-based event activation, MQTT publishing + +### 🔄 **Worker** (Conversion Service) +- **Technology**: RQ (Redis Queue) + Gotenberg +- **Purpose**: Background presentation conversion +- **Features**: PPT/PPTX/ODP → PDF conversion, status tracking + +### 🗄️ **Database** (MariaDB 11.2) +- **Features**: Health checks, automatic initialization +- **Migrations**: Alembic-based schema management +- **Timezone**: UTC-aware timestamps + +### 📡 **MQTT Broker** (Eclipse Mosquitto 2.0.21) +- **Features**: WebSocket support, health monitoring +- **Topics**: + - `infoscreen/discovery` - Client registration + - `infoscreen/{uuid}/heartbeat` - Client alive status + - `infoscreen/events/{group_id}` - Event distribution + - `infoscreen/{uuid}/group_id` - Client group assignment + +## 📁 Project Structure + +``` +infoscreen_2025/ +├── dashboard/ # React frontend +│ ├── src/ # React components and logic +│ ├── public/ # Static assets +│ └── Dockerfile # Production build +├── server/ # Flask API backend +│ ├── routes/ # API endpoints +│ ├── alembic/ # Database migrations +│ ├── media/ # File storage +│ └── worker.py # Background jobs +├── listener/ # MQTT listener service +├── scheduler/ # Event scheduling service +├── models/ # Shared database models +├── mosquitto/ # MQTT broker configuration +├── certs/ # SSL certificates +├── docker-compose.yml # Development setup +├── docker-compose.prod.yml # Production setup +└── Makefile # Development shortcuts +``` + +## 🔧 Development + +### Available Commands + +```bash +# Development +make up # Start dev stack +make down # Stop dev stack +make logs # View all logs +make logs-server # View specific service logs + +# Building & Deployment +make build # Build all images +make push # Push to registry +make pull-prod # Pull production images +make up-prod # Start production stack + +# Maintenance +make health # Health checks +make fix-perms # Fix file permissions +``` + +### Database Management + +```bash +# Access database directly +docker exec -it infoscreen-db mysql -u${DB_USER} -p${DB_PASSWORD} ${DB_NAME} + +# Run migrations +docker exec -it infoscreen-api alembic upgrade head + +# Initialize academic periods (Austrian school system) +docker exec -it infoscreen-api python init_academic_periods.py +``` + +### MQTT Testing + +```bash +# Subscribe to all topics +mosquitto_sub -h localhost -t "infoscreen/#" -v + +# Publish test message +mosquitto_pub -h localhost -t "infoscreen/test" -m "Hello World" + +# Monitor client heartbeats +mosquitto_sub -h localhost -t "infoscreen/+/heartbeat" -v +``` + +## 🌐 API Endpoints + +### Core Resources +- `GET /api/clients` - List all registered clients +- `PUT /api/clients/{uuid}/group` - Assign client to group +- `GET /api/groups` - List client groups with alive status +- `GET /api/events` - List events with filtering +- `POST /api/events` - Create new event +- `GET /api/academic_periods` - List academic periods +- `POST /api/academic_periods/active` - Set active period + +### File Management +- `POST /api/files` - Upload media files +- `GET /api/files/{path}` - Download files +- `GET /api/files/converted/{path}` - Download converted PDFs +- `POST /api/conversions/{media_id}/pdf` - Request conversion +- `GET /api/conversions/{media_id}/status` - Check conversion status + +### Health & Monitoring +- `GET /health` - Service health check +- `GET /api/screenshots/{uuid}.jpg` - Client screenshots + +## 🎨 Frontend Features + +### Syncfusion Components Used +- **Schedule**: Event calendar with drag-drop support +- **Grid**: Data tables with filtering and sorting +- **DropDownList**: Group and period selectors +- **FileManager**: Media upload and organization +- **Kanban**: Task management views +- **Notifications**: Toast messages and alerts + +### Pages Overview +- **Dashboard**: System overview and statistics +- **Clients**: Device management and monitoring +- **Groups**: Client group organization +- **Events**: Schedule management +- **Media**: File upload and conversion +- **Settings**: System configuration +- **Holidays**: Academic calendar management + +## 🔒 Security & Authentication + +- **Environment Variables**: Sensitive data via `.env` +- **SSL/TLS**: HTTPS support with custom certificates +- **MQTT Security**: Username/password authentication +- **Database**: Parameterized queries, connection pooling +- **File Uploads**: Type validation, size limits +- **CORS**: Configured for production deployment + +## 📊 Monitoring & Logging + +### Health Checks +All services include Docker health checks: +- API: HTTP endpoint monitoring +- Database: Connection and initialization status +- MQTT: Pub/sub functionality test +- Dashboard: Nginx availability + +### Logging Strategy +- **Development**: Docker Compose logs with service prefixes +- **Production**: Centralized logging via Docker log drivers +- **MQTT**: Message-level debugging available +- **Database**: Query logging in development mode + +## 🌍 Deployment Options + +### Development +- **Hot Reload**: Vite dev server + Flask debug mode +- **Volume Mounts**: Live code editing +- **Debug Ports**: Python debugger support (port 5678) +- **Local Certificates**: Self-signed SSL for testing + +### Production +- **Optimized Builds**: Multi-stage Dockerfiles +- **Reverse Proxy**: Nginx with SSL termination +- **Health Monitoring**: Comprehensive healthchecks +- **Registry**: GitHub Container Registry integration +- **Scaling**: Docker Compose for single-node deployment + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/amazing-feature` +3. Commit your changes: `git commit -m 'Add amazing feature'` +4. Push to the branch: `git push origin feature/amazing-feature` +5. Open a Pull Request + +### Development Guidelines +- Follow existing code patterns and naming conventions +- Add appropriate tests for new features +- Update documentation for API changes +- Use TypeScript for frontend development +- Follow Python PEP 8 for backend code + +## 📋 Requirements + +### System Requirements +- **CPU**: 2+ cores recommended +- **RAM**: 4GB minimum, 8GB recommended +- **Storage**: 20GB+ for media files and database +- **Network**: Reliable internet for client communication + +### Software Dependencies +- Docker 24.0+ +- Docker Compose 2.0+ +- Git 2.30+ +- Modern web browser (Chrome, Firefox, Safari, Edge) + +## 🐛 Troubleshooting + +### Common Issues + +**Services won't start** +```bash +# Check service health +make health + +# View specific service logs +make logs-server +make logs-db +``` + +**Database connection errors** +```bash +# Verify database is running +docker exec -it infoscreen-db mysqladmin ping + +# Check credentials in .env file +# Restart dependent services +``` + +**MQTT communication issues** +```bash +# Test MQTT broker +mosquitto_pub -h localhost -t test -m "hello" + +# Check client certificates and credentials +# Verify firewall settings for ports 1883/9001 +``` + +**File conversion problems** +```bash +# Check Gotenberg service +curl http://localhost:3000/health + +# Monitor worker logs +make logs-worker + +# Check Redis queue status +docker exec -it infoscreen-redis redis-cli LLEN conversions +``` + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## 🙏 Acknowledgments + +- **Syncfusion**: UI components for React dashboard +- **Eclipse Mosquitto**: MQTT broker implementation +- **Gotenberg**: Document conversion service +- **MariaDB**: Reliable database engine +- **Flask**: Python web framework +- **React**: Frontend user interface library + +--- + +For detailed technical documentation, deployment guides, and API specifications, please refer to the additional documentation files in this repository. diff --git a/docker-compose.yml b/docker-compose.yml index e49c59c..565e7f4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -166,6 +166,9 @@ services: networks: - infoscreen-net + volumes: + - ./scheduler:/app/scheduler + redis: image: redis:7-alpine container_name: infoscreen-redis diff --git a/scheduler/db_utils.py b/scheduler/db_utils.py index 8d2f3d5..089428e 100644 --- a/scheduler/db_utils.py +++ b/scheduler/db_utils.py @@ -53,6 +53,7 @@ def format_event_with_media(event): } # Now you can directly access event.event_media + import logging if event.event_media: media = event.event_media @@ -64,7 +65,39 @@ def format_event_with_media(event): "auto_advance": True } - if media.file_path: + # Debug: log media_type + logging.debug( + f"[Scheduler] EventMedia id={media.id} media_type={getattr(media.media_type, 'value', str(media.media_type))}") + + # Check for PDF conversion for ppt/pptx/odp + from sqlalchemy.orm import scoped_session + from models.models import Conversion, ConversionStatus + session = scoped_session(Session) + pdf_url = None + if getattr(media.media_type, 'value', str(media.media_type)) in ("ppt", "pptx", "odp"): + conversion = session.query(Conversion).filter_by( + source_event_media_id=media.id, + target_format="pdf", + status=ConversionStatus.ready + ).order_by(Conversion.completed_at.desc()).first() + logging.debug( + f"[Scheduler] Conversion lookup for media_id={media.id}: found={bool(conversion)}, path={getattr(conversion, 'target_path', None) if conversion else None}") + if conversion and conversion.target_path: + # Serve via /api/files/converted/ + pdf_url = f"{API_BASE_URL}/api/files/converted/{conversion.target_path}" + session.remove() + + if pdf_url: + filename = os.path.basename(pdf_url) + event_dict["presentation"]["files"].append({ + "name": filename, + "url": pdf_url, + "checksum": None, + "size": None + }) + logging.info( + f"[Scheduler] Using converted PDF for event_media_id={media.id}: {pdf_url}") + elif media.file_path: filename = os.path.basename(media.file_path) event_dict["presentation"]["files"].append({ "name": filename, @@ -72,6 +105,8 @@ def format_event_with_media(event): "checksum": None, "size": None }) + logging.info( + f"[Scheduler] Using original file for event_media_id={media.id}: {filename}") # Add other event types...