feat(events): add webuntis event, unify website payload, bump UI to alpha.13
- Add `webuntis` event type; event creation resolves URL from system `supplement_table_url`
- Consolidate settings: remove separate webuntis-url endpoints; use GET/POST /api/system-settings/supplement-table
- Scheduler: emit top-level `event_type` and unified `website` payload (`{ "type":"browser","url":"..." }`) for website/webuntis
- Preserve presentation payloads (page_progress/auto_progress) — presentation messages remain backwards-compatible
- Update defaults (`init_defaults.py`) and remove duplicate webuntis setting
- Docs & metadata: bump program-info to 2025.1.0-alpha.13; update README, copilot-instructions, DEV- and TECH-CHANGELOGs; add MQTT_EVENT_PAYLOAD_GUIDE.md and WEBUNTIS_EVENT_IMPLEMENTATION.md
This commit is contained in:
324
WEBUNTIS_EVENT_IMPLEMENTATION.md
Normal file
324
WEBUNTIS_EVENT_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,324 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user