Files
infoscreen/WEBUNTIS_EVENT_IMPLEMENTATION.md
RobbStarkAustria e6c19c189f 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
2025-10-19 11:35:41 +00:00

10 KiB

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:

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:

('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():

# 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:

# 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

{
  "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)

{
  "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:

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):

    POST /api/system-settings/supplement-table
    {
      "url": "https://webuntis.example.com/schedule",
      "enabled": true
    }
    
  2. Via Database (for initial setup):

    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:
    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

  • Database migration includes webuntis enum value
  • System setting supplement_table_url description updated to include WebUntis
  • Event creation validates supplement_table_url is configured
  • Event creation creates EventMedia with supplement table URL
  • Scheduler includes website payload for webuntis events
  • MQTT message structure documented
  • 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)

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