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:
RobbStarkAustria
2025-10-19 11:35:41 +00:00
parent c9cc535fc6
commit e6c19c189f
12 changed files with 788 additions and 11 deletions

View 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