Dashboard: new Syncfusion card layout, global stats, filters, health bars, active event display, client details, bulk restart, 15s auto-refresh, manual refresh toasts API: standardized responses to camelCase; added serializers.py and updated events endpoints Time: ensured UTC storage; frontend appends 'Z' for parsing and displays local time Docs: updated copilot-instructions.md, README.md, TECH-CHANGELOG.md Program Info: bumped to 2025.1.0-alpha.12 with user-facing changelog BREAKING: external API consumers must migrate field names from PascalCase to camelCase.
Infoscreen 2025
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 │◄──────►│ Worker │
│ (React/Vite) │ │ (Flask) │ │ (Conversions) │
└───────────────┘ └──────────────────────────┘ └───────────────┘
▲ ▲
│ │
┌───────────────┐ │
│ MariaDB │ │
│ (Database) │ │
└───────────────┘ │
│ direct commands/results
Reads events ▲ Interacts with API ▲
│ ┌────┘
┌───────────────┐ │ │ ┌───────────────┐
│ Scheduler │──┘ └──│ Listener │
│ (Events) │ │ (MQTT Client) │
└───────────────┘ └───────────────┘
│ Publishes events ▲ Consumes discovery/heartbeats
▼ │ and forwards to API
┌─────────────────┐◄─────────────────────────────────────────────────────────────────┘
│ MQTT Broker │────────────────────────────────────────────────────────► Clients
│ (Mosquitto) │ Sends events to clients (send discovery/heartbeats)
└─────────────────┘
Data flow summary:
- Listener: consumes discovery and heartbeat messages from the MQTT Broker and updates the API Server (client registration/heartbeats).
- Scheduler: reads events from the API Server and publishes only currently active content to the MQTT Broker (retained topics per group). When a group has no active events, the scheduler clears its retained topic by publishing an empty list. All time comparisons are done in UTC; any naive timestamps are normalized.
- Clients: send discovery/heartbeat via the MQTT Broker (handled by the Listener) and receive content from the Scheduler via MQTT.
- Worker: receives conversion commands directly from the API Server and reports results/status back to the API (no MQTT involved).
- MariaDB: is accessed exclusively by the API Server. The Dashboard never talks to the database directly; it only communicates with the API.
🌟 Key Features
-
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
-
Visual indicators: TentTree icon next to the main event icon marks events that skip holidays (icon color: black)
-
Event Deletion: All event types (single, single-in-series, entire series) are handled with custom dialogs. The frontend intercepts Syncfusion's built-in RecurrenceAlert and DeleteAlert popups to provide a unified, user-friendly deletion flow:
- Single (non-recurring) event: deleted directly after confirmation.
- Single occurrence of a recurring series: user can delete just that instance.
- Entire recurring series: user can delete all occurrences after a final custom confirmation dialog.
- Detached occurrences (edited/broken out): treated as single events.
🎯 Event System
- Presentations: PowerPoint/LibreOffice → PDF conversion via Gotenberg
- Websites: URL-based content display
- Videos: Media file streaming with per-event playback settings (
autoplay,loop,volume,muted); system-wide defaults configurable under Settings → Events → Videos - Messages: Text announcements
- WebUntis: Educational schedule integration
- Uses the system-wide Vertretungsplan/Supplement-Table URL (
supplement_table_url) configured under Settings → Events. No separate per-event URL is required; WebUntis events display the same as Website events.
- Uses the system-wide Vertretungsplan/Supplement-Table URL (
- Recurrence & Holidays: Recurring events can be configured to skip holidays. The backend generates EXDATEs (RecurrenceException) for holiday occurrences using RFC 5545 timestamps (yyyyMMddTHHmmssZ), so the calendar never shows those instances. The scheduler queries a 7-day window to expand recurring events and applies event exceptions, but only publishes events that are active at the current time (UTC). The "Termine an Ferientagen erlauben" toggle does not affect these events.
- Single Occurrence Editing: Users can edit individual occurrences of recurring events without affecting the master series. The system provides a confirmation dialog to choose between editing a single occurrence or the entire series.
🏫 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
-
Clone the repository
git clone https://github.com/RobbStarkAustria/infoscreen_2025.git cd infoscreen_2025 -
Environment Configuration
cp .env.example .env # Edit .env with your configuration -
Start the development stack
make up # or: docker compose up -d --build
Before running the dashboard dev server you may need to install Syncfusion packages used by the UI. Example (install only the packages you use):
# from the repository root
cd dashboard
npm install --save @syncfusion/ej2-react-splitbuttons @syncfusion/ej2-splitbuttons \
@syncfusion/ej2-react-grids @syncfusion/ej2-react-schedule @syncfusion/ej2-react-filemanager
License note: Syncfusion distributes components under a commercial license with a free community license for qualifying users. Verify licensing for your organization before using Syncfusion in production and document any license keys or compliance steps in this repository.
-
Initialize the database (first run only)
# One-shot: runs all Alembic migrations, creates default admin/group, and seeds academic periods python server/initialize_database.py -
Access the services
- Dashboard: http://localhost:5173
- API: http://localhost:8000
- Database: localhost:3306
- MQTT: localhost:1883 (WebSocket: 9001)
Production Deployment
-
Build and push images
make build make push -
Deploy on server
make pull-prod make up-prod
For detailed deployment instructions, see:
🛠️ Services
🖥️ Dashboard (dashboard/)
- Technology: React 19 + TypeScript + Vite
- UI Framework: Syncfusion components (Material 3 theme)
- Styling: Centralized Syncfusion Material 3 CSS imports in
dashboard/src/main.tsx - Features: Responsive design, real-time updates, file management
- Port: 5173 (dev), served via Nginx (prod)
- Data access: No direct database connection; communicates with the API Server only via HTTP.
- Dev proxy tip: In development, use relative paths like
/api/...in the frontend to route through Vite's proxy to the API. Avoid absolute URLs with an extra/apisegment to prevent CORS or double-path issues.
🔧 API Server (server/)
- Technology: Flask + SQLAlchemy + Alembic
- Database: MariaDB with timezone-aware timestamps
- Features: RESTful API, file uploads, MQTT integration
- Recurrence/holidays: returns only master events with
RecurrenceRuleandRecurrenceException(EXDATEs) so clients render recurrences and skip holiday instances reliably. - Recurring events are only deactivated after their recurrence_end (UNTIL); non-recurring events deactivate after their end time. Event exceptions are respected and rendered in scheduler output.
- Single occurrence detach:
POST /api/events/<id>/occurrences/<date>/detachcreates standalone events from recurring series without modifying the master event.
- Recurrence/holidays: returns only master events with
- Port: 8000
- Health Check:
/health
👂 Listener (listener/)
⏰ Scheduler (scheduler/)
Technology: Python + SQLAlchemy Purpose: Event publishing, group-based content distribution Features:
- Queries a future window (default: 7 days) to expand recurring events
- Expands recurrences using RFC 5545 rules
- Applies event exceptions (skipped dates, detached occurrences)
- Only deactivates recurring events after their recurrence_end (UNTIL)
- Publishes only currently active events to MQTT (per group)
- Clears retained topics by publishing an empty list when a group has no active events
- Normalizes naive timestamps and compares times in UTC
- Logging is concise; conversion lookups are cached and logged only once per media
🔄 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 registrationinfoscreen/{uuid}/heartbeat- Client alive statusinfoscreen/events/{group_id}- Event distribution
🔗 Scheduler Event Payloads
- Presentations include a
presentationobject withfiles,slide_interval,page_progress, andauto_progress. - Website and WebUntis events share a unified payload:
website:{ "type": "browser", "url": "https://..." }
- The
event_typefield remains specific (e.g.,presentation,website,webuntis) so clients can dispatch appropriately; however,websiteandwebuntisshould be handled identically in clients. - Videos include a
videopayload with a stream URL and playback flags:video: includesurl(streaming endpoint) andautoplay,loop,volume,muted- Streaming endpoint supports byte-range requests (206) to enable seeking:
/api/eventmedia/stream/<media_id>/<filename>
Recent changes since last commit
- Video / Streaming support: Added end-to-end support for video events. The API and dashboard now allow creating
videoevents referencing uploaded media. The server exposes a range-capable streaming endpoint at/api/eventmedia/stream/<media_id>/<filename>so clients can seek during playback. - Scheduler metadata: Scheduler now performs a best-effort HEAD probe for video stream URLs and includes basic metadata in the retained MQTT payload:
mime_type,size(bytes) andaccept_ranges(bool). Placeholders for richer metadata (duration,resolution,bitrate,qualities,thumbnails,checksum) are emitted as null/empty until a background worker fills them. - Dashboard & uploads: The dashboard's FileManager upload limits were increased (to support Full-HD uploads) and client-side validation enforces a maximum video length (10 minutes). The event modal exposes playback flags (
autoplay,loop,volume,muted) and initializes them from system defaults for new events. - DB model & API:
Eventincludesmutedin addition toautoplay,loop, andvolume; endpoints accept, persist, and return these fields for video events. Events reference uploaded media viaevent_media_id. - Settings UI: Settings page refactored to nested tabs; added Events → Videos defaults (autoplay, loop, volume, mute) backed by system settings keys (
video_autoplay,video_loop,video_volume,video_muted). - Academic Calendar UI: Merged “School Holidays Import” and “List” into a single “📥 Import & Liste” tab; nested tab selection is persisted with controlled
selectedItemstate to avoid jumps.
These changes are designed to be safe if metadata extraction or probes fail — clients should still attempt playback using the provided url and fall back to requesting/resolving richer metadata when available.
See MQTT_EVENT_PAYLOAD_GUIDE.md for details.
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
│ ├── initialize_database.py # All-in-one DB initialization (dev)
│ └── 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
# 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
# One-shot initialization (schema + defaults + academic periods)
python server/initialize_database.py
# 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
# 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 clientsPUT /api/clients/{uuid}/group- Assign client to groupGET /api/groups- List client groups with alive statusGET /api/events- List events with filteringPOST /api/events- Create new eventPOST /api/events/{id}/occurrences/{date}/detach- Detach single occurrence from recurring seriesGET /api/academic_periods- List academic periodsPOST /api/academic_periods/active- Set active period
File Management
POST /api/files- Upload media filesGET /api/files/{path}- Download filesGET /api/files/converted/{path}- Download converted PDFsPOST /api/conversions/{media_id}/pdf- Request conversionGET /api/conversions/{media_id}/status- Check conversion statusGET /api/eventmedia/stream/<media_id>/<filename>- Stream media with byte-range support (206) for seeking
System Settings
GET /api/system-settings- List all system settings (admin+)GET /api/system-settings/{key}- Get a specific setting (admin+)POST /api/system-settings/{key}- Create or update a setting (admin+)DELETE /api/system-settings/{key}- Delete a setting (admin+)GET /api/system-settings/supplement-table- Get WebUntis/Vertretungsplan settings (enabled, url)POST /api/system-settings/supplement-table- Update WebUntis/Vertretungsplan settings- Presentation defaults stored as keys:
presentation_interval(seconds, default "10")presentation_page_progress("true"/"false", default "true")presentation_auto_progress("true"/"false", default "true")
- Video defaults stored as keys:
video_autoplay("true"/"false", default "true")video_loop("true"/"false", default "true")video_volume(0.0–1.0, default "0.8")video_muted("true"/"false", default "false")
- Presentation defaults stored as keys:
Health & Monitoring
GET /health- Service health checkGET /api/screenshots/{uuid}.jpg- Client screenshots
🎨 Frontend Features
API Response Format
- JSON Convention: All API endpoints return camelCase JSON (e.g.,
startTime,endTime,groupId). Frontend consumes camelCase directly. - UTC Time Parsing: API returns ISO strings without 'Z' suffix. Frontend appends 'Z' before parsing to ensure UTC interpretation:
const utcString = dateStr.endsWith('Z') ? dateStr : dateStr + 'Z'; new Date(utcString);. Display usestoLocaleTimeString('de-DE')for German format.
Recurrence & holidays
- Recurrence is handled natively by Syncfusion. The API returns master events with
RecurrenceRuleandRecurrenceException(EXDATE) in RFC 5545 format (yyyyMMddTHHmmssZ, UTC) so the Scheduler excludes holiday instances reliably. - Events with "skip holidays" display a TentTree icon next to the main event icon (icon color: black). The Scheduler’s native lower-right recurrence badge indicates series membership.
- Single occurrence editing: Users can edit either a single occurrence or the entire series. The UI persists changes using
onActionCompleted (requestType='eventChanged'):- Single occurrence →
POST /api/events/<id>/occurrences/<date>/detach(creates standalone event and adds EXDATE to master) - Series/single event →
PUT /api/events/<id>
- Single occurrence →
Syncfusion Components Used (Material 3)
- 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
- Pager: Used on Programinfo changelog for pagination
- Cards (layouts): Programinfo sections styled with Syncfusion card classes
- SplitButtons: Header user menu (top-right) using Syncfusion DropDownButton to show current user and role, with actions “Profil” and “Abmelden”.
Pages Overview
- Dashboard: Card-based overview of all Raumgruppen (room groups) with real-time status monitoring. Features include:
- Global statistics: total infoscreens, online/offline counts, warning groups
- Filter buttons: All / Online / Offline / Warnings with dynamic counts
- Per-group cards showing currently active event (title, type, date/time in local timezone)
- Health bar with online/offline ratio and color-coded status
- Expandable client list with last alive timestamps
- Bulk restart button for offline clients
- Auto-refresh every 15 seconds; manual refresh button available
- Clients: Device management and monitoring
- Groups: Client group organization
- Events: Schedule management
- Media: File upload and conversion
- Settings: Central configuration (tabbed)
- 📅 Academic Calendar (all users):
- 📥 Import & Liste: CSV/TXT import combined with holidays list
- 🗂️ Perioden: Academic Periods (set active period)
- 🖥️ Display & Clients (admin+): Defaults placeholders and quick links to Clients/Groups
- 🎬 Media & Files (admin+): Upload settings placeholders and Conversion status overview
- 🗓️ Events (admin+): WebUntis/Vertretungsplan URL enable/disable, save, preview. Presentations: general defaults for slideshow interval, page-progress, and auto-progress; persisted via
/api/system-settingskeys and applied on create in the event modal. Videos: system-wide defaults forautoplay,loop,volume, andmuted; persisted via/api/system-settingskeys and applied on create in the event modal. - ⚙️ System (superadmin): Organization info and Advanced configuration placeholders
- 📅 Academic Calendar (all users):
- Holidays: Academic calendar management
- Program info: Version, build info, tech stack and paginated changelog (reads
dashboard/public/program-info.json)
🔒 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
Scheduler: Logging is concise; conversion lookups are cached and logged only once per media.
- 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
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - 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
# Check service health
make health
# View specific service logs
make logs-server
make logs-db
Database connection errors
# Verify database is running
docker exec -it infoscreen-db mysqladmin ping
# Check credentials in .env file
# Restart dependent services
MQTT communication issues Vite import-analysis errors (Syncfusion splitbuttons)
# Symptom
[plugin:vite:import-analysis] Failed to resolve import "@syncfusion/ej2-react-splitbuttons"
# Fix
# 1) Ensure dependencies are added in dashboard/package.json:
# - @syncfusion/ej2-react-splitbuttons, @syncfusion/ej2-splitbuttons
# 2) In dashboard/vite.config.ts, add to optimizeDeps.include:
# '@syncfusion/ej2-react-splitbuttons', '@syncfusion/ej2-splitbuttons'
# 3) If dashboard uses a named node_modules volume, recreate it so npm ci runs inside the container:
docker compose rm -sf dashboard
docker volume rm <project>_dashboard-node-modules <project>_dashboard-vite-cache || true
docker compose up -d --build dashboard
# 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
# 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 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.
Notes:
- Tailwind CSS was removed. Styling is managed via Syncfusion Material 3 theme imports in
dashboard/src/main.tsx.
🧭 Changelog Style Guide
When adding entries to dashboard/public/program-info.json (displayed on the Program info page):
-
Structure per release
version(e.g.,2025.1.0-alpha.8)dateinYYYY-MM-DD(ISO format)changes: array of short bullet strings
-
Categories (Keep a Changelog inspired)
- Prefer starting bullets with an implicit category or an emoji, e.g.:
- Added (🆕/✨), Changed (🔧/🛠️), Fixed (🐛/✅), Removed (🗑️), Security (🔒), Deprecated (⚠️)
- Prefer starting bullets with an implicit category or an emoji, e.g.:
-
Writing rules
- Keep bullets concise (ideally one line) and user-facing; avoid internal IDs or jargon
- Put the affected area first when helpful (e.g., “UI: …”, “API: …”, “Scheduler: …”)
- Highlight breaking changes with “BREAKING:”
- Prefer German wording consistently; dates are localized at runtime for display
-
Ordering and size
- Newest release first in the array
- Aim for ≤ 8–10 bullets per release; group or summarize if longer
-
JSON hygiene
- Valid JSON only (no trailing commas); escape quotes as needed
- One release object per version; do not modify historical entries unless to correct typos
The Program info page paginates older entries (default page size 5). Keep highlights at the top of each release for scanability.