# WebUntis Event Type Implementation **Date**: 2025-10-19 **Status**: Completed ## Summary Implemented support for a new `webuntis` event type that displays a centrally-configured WebUntis website on infoscreen clients. This event type follows the same client-side behavior as `website` events but sources its URL from system settings rather than per-event configuration. ## Changes Made ### 1. Database & Models The `webuntis` event type was already defined in the `EventType` enum in `models/models.py`: ```python class EventType(enum.Enum): presentation = "presentation" website = "website" video = "video" message = "message" other = "other" webuntis = "webuntis" # Already present ``` ### 2. System Settings #### Default Initialization (`server/init_defaults.py`) Updated `supplement_table_url` description to indicate it's used for both Vertretungsplan and WebUntis: ```python ('supplement_table_url', '', 'URL für Vertretungsplan / WebUntis (Stundenplan-Änderungstabelle)') ``` This setting is automatically seeded during database initialization. **Note**: The same URL (`supplement_table_url`) is used for both: - Vertretungsplan (supplement table) displays - WebUntis event displays #### API Endpoints (`server/routes/system_settings.py`) WebUntis events use the existing supplement table endpoints: - **`GET /api/system-settings/supplement-table`** (Admin+) - Returns: `{"url": "https://...", "enabled": true/false}` - **`POST /api/system-settings/supplement-table`** (Admin+) - Body: `{"url": "https://...", "enabled": true/false}` - Updates the URL used for both supplement table and WebUntis events No separate WebUntis URL endpoint is needed—the supplement table URL serves both purposes. ### 3. Event Creation (`server/routes/events.py`) Added handling for `webuntis` event type in `create_event()`: ```python # WebUntis: URL aus System-Einstellungen holen und EventMedia anlegen if event_type == "webuntis": # Hole WebUntis-URL aus Systemeinstellungen (verwendet supplement_table_url) webuntis_setting = session.query(SystemSetting).filter_by(key='supplement_table_url').first() webuntis_url = webuntis_setting.value if webuntis_setting else '' if not webuntis_url: return jsonify({"error": "WebUntis / Supplement table URL not configured in system settings"}), 400 # EventMedia für WebUntis anlegen media = EventMedia( media_type=MediaType.website, url=webuntis_url, file_path=webuntis_url ) session.add(media) session.commit() event_media_id = media.id ``` **Workflow**: 1. Check if `supplement_table_url` is configured in system settings 2. Return error if not configured 3. Create `EventMedia` with `MediaType.website` using the supplement table URL 4. Associate the media with the event ### 4. Scheduler Payload (`scheduler/db_utils.py`) Modified `format_event_with_media()` to handle both `website` and `webuntis` events: ```python # Handle website and webuntis events (both display a website) elif event.event_type.value in ("website", "webuntis"): event_dict["website"] = { "type": "browser", "url": media.url if media.url else None } if media.id not in _media_decision_logged: logging.debug( f"[Scheduler] Using website URL for event_media_id={media.id} (type={event.event_type.value}): {media.url}") _media_decision_logged.add(media.id) ``` **Key Points**: - Both event types use the same `website` payload structure - Clients interpret `event_type` but handle display identically - URL is already resolved from system settings during event creation ### 5. Documentation Created comprehensive documentation in `MQTT_EVENT_PAYLOAD_GUIDE.md` covering: - MQTT message structure - Event type-specific payloads - Best practices for client implementation - Versioning strategy - System settings integration ## MQTT Message Format ### WebUntis Event Payload ```json { "id": 125, "event_type": "webuntis", "title": "Schedule Display", "start": "2025-10-19T09:00:00+00:00", "end": "2025-10-19T09:30:00+00:00", "group_id": 1, "website": { "type": "browser", "url": "https://webuntis.example.com/schedule" } } ``` ### Website Event Payload (for comparison) ```json { "id": 124, "event_type": "website", "title": "School Website", "start": "2025-10-19T09:00:00+00:00", "end": "2025-10-19T09:30:00+00:00", "group_id": 1, "website": { "type": "browser", "url": "https://example.com/page" } } ``` ## Client Implementation Guide Clients should handle both `website` and `webuntis` event types identically: ```javascript function parseEvent(event) { switch (event.event_type) { case 'presentation': return handlePresentation(event.presentation); case 'website': case 'webuntis': // Both types use the same display logic return handleWebsite(event.website); case 'video': return handleVideo(event.video); default: console.warn(`Unknown event type: ${event.event_type}`); } } function handleWebsite(websiteData) { // websiteData = { type: "browser", url: "https://..." } if (!websiteData.url) { console.error('Website event missing URL'); return; } // Display URL in embedded browser/webview displayInBrowser(websiteData.url); } ``` ## Best Practices ### 1. Type-Based Dispatch Always check `event_type` first and dispatch to appropriate handlers. The nested payload structure (`presentation`, `website`, etc.) provides type-specific details. ### 2. Graceful Error Handling - Validate URLs before displaying - Handle missing or empty URLs gracefully - Provide user-friendly error messages ### 3. Unified Website Display Both `website` and `webuntis` events trigger the same browser/webview component. The only difference is in event creation (per-event URL vs. system-wide URL). ### 4. Extensibility The message structure supports adding new event types without breaking existing clients: - Old clients ignore unknown `event_type` values - New fields in existing payloads are optional - Nested objects isolate type-specific changes ## Administrative Setup ### Setting the WebUntis / Supplement Table URL The same URL is used for both Vertretungsplan (supplement table) and WebUntis displays. 1. **Via API** (recommended for UI integration): ```bash POST /api/system-settings/supplement-table { "url": "https://webuntis.example.com/schedule", "enabled": true } ``` 2. **Via Database** (for initial setup): ```sql INSERT INTO system_settings (`key`, value, description) VALUES ('supplement_table_url', 'https://webuntis.example.com/schedule', 'URL für Vertretungsplan / WebUntis (Stundenplan-Änderungstabelle)'); ``` 3. **Via Dashboard**: Settings → Events → WebUntis / Vertretungsplan ### Creating a WebUntis Event Once the URL is configured, events can be created through: 1. **Dashboard UI**: Select "WebUntis" as event type 2. **API**: ```json POST /api/events { "group_id": 1, "title": "Daily Schedule", "description": "Current class schedule", "start": "2025-10-19T08:00:00Z", "end": "2025-10-19T16:00:00Z", "event_type": "webuntis", "created_by": 1 } ``` No `website_url` is required—it's automatically fetched from the `supplement_table_url` system setting. ## Migration Notes ### From Presentation-Only System This implementation extends the existing event system without breaking presentation events: - **Presentation events**: Still use `presentation` payload with `files` array - **Website/WebUntis events**: Use new `website` payload with `url` field - **Message structure**: Includes `event_type` for client-side dispatch ### Future Event Types The pattern established here can be extended to other event types: - **Video**: `event_dict["video"] = { "type": "media", "url": "...", "autoplay": true }` - **Message**: `event_dict["message"] = { "type": "html", "content": "..." }` - **Custom**: Any new type with its own nested payload ## Testing Checklist - [x] Database migration includes `webuntis` enum value - [x] System setting `supplement_table_url` description updated to include WebUntis - [x] Event creation validates supplement_table_url is configured - [x] Event creation creates `EventMedia` with supplement table URL - [x] Scheduler includes `website` payload for `webuntis` events - [x] MQTT message structure documented - [x] No duplicate webuntis_url setting (uses supplement_table_url) - [ ] Dashboard UI shows supplement table URL is used for WebUntis (documentation) - [ ] Client implementation tested with WebUntis events (client-side) ## Related Files ### Modified - `scheduler/db_utils.py` - Event formatting logic - `server/routes/events.py` - Event creation handling - `server/routes/system_settings.py` - WebUntis URL endpoints - `server/init_defaults.py` - System setting defaults ### Created - `MQTT_EVENT_PAYLOAD_GUIDE.md` - Comprehensive message format documentation - `WEBUNTIS_EVENT_IMPLEMENTATION.md` - This file ### Existing (Not Modified) - `models/models.py` - Already had `webuntis` enum value - `dashboard/src/components/CustomEventModal.tsx` - Already supports webuntis type ## Further Enhancements ### Short-term 1. Add WebUntis URL configuration to dashboard Settings page 2. Update event creation UI to explain WebUntis URL comes from settings 3. Add validation/preview for WebUntis URL in settings ### Long-term 1. Support multiple WebUntis instances (per-school in multi-tenant setup) 2. Add WebUntis-specific metadata (class filter, room filter, etc.) 3. Implement iframe sandboxing options for security 4. Add refresh intervals for dynamic WebUntis content ## Conclusion The `webuntis` event type is now fully integrated into the infoscreen system. It uses the existing `supplement_table_url` system setting, which serves dual purposes: 1. **Vertretungsplan (supplement table)** displays in the existing settings UI 2. **WebUntis schedule** displays via the webuntis event type This provides a clean separation between system-wide URL configuration and per-event scheduling, while maintaining backward compatibility and following established patterns for event payload structure. The implementation demonstrates best practices: - **Reuse existing infrastructure**: Uses supplement_table_url instead of creating duplicate settings - **Consistency**: Follows same patterns as existing event types - **Extensibility**: Easy to add new event types following this model - **Documentation**: Comprehensive guides for both developers and clients