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:
9
.github/copilot-instructions.md
vendored
9
.github/copilot-instructions.md
vendored
@@ -43,6 +43,7 @@ Use this as your shared context when proposing changes. Keep edits minimal and m
|
|||||||
- `presentation_auto_progress` ("true"/"false", default "true")
|
- `presentation_auto_progress` ("true"/"false", default "true")
|
||||||
Seeded in `server/init_defaults.py` if missing.
|
Seeded in `server/init_defaults.py` if missing.
|
||||||
- Events: Added `page_progress` (Boolean) and `auto_progress` (Boolean) for presentation behavior per event.
|
- Events: Added `page_progress` (Boolean) and `auto_progress` (Boolean) for presentation behavior per event.
|
||||||
|
- WebUntis URL: WebUntis uses the existing Vertretungsplan/Supplement-Table URL (`supplement_table_url`). There is no separate `webuntis_url` setting; use `GET/POST /api/system-settings/supplement-table`.
|
||||||
|
|
||||||
- Conversions:
|
- Conversions:
|
||||||
- Enum `ConversionStatus`: `pending`, `processing`, `ready`, `failed`.
|
- Enum `ConversionStatus`: `pending`, `processing`, `ready`, `failed`.
|
||||||
@@ -83,6 +84,7 @@ Use this as your shared context when proposing changes. Keep edits minimal and m
|
|||||||
- Detached occurrences (edited/broken out): treated as single events.
|
- Detached occurrences (edited/broken out): treated as single events.
|
||||||
- Single occurrence editing: Users can detach individual occurrences from recurring series. The frontend hooks `actionComplete`/`onActionCompleted` with `requestType='eventChanged'` to persist changes: it calls `POST /api/events/<id>/occurrences/<date>/detach` for single-occurrence edits and `PUT /api/events/<id>` for series or single events as appropriate. The backend creates `EventException` and a standalone `Event` without modifying the master beyond EXDATEs.
|
- Single occurrence editing: Users can detach individual occurrences from recurring series. The frontend hooks `actionComplete`/`onActionCompleted` with `requestType='eventChanged'` to persist changes: it calls `POST /api/events/<id>/occurrences/<date>/detach` for single-occurrence edits and `PUT /api/events/<id>` for series or single events as appropriate. The backend creates `EventException` and a standalone `Event` without modifying the master beyond EXDATEs.
|
||||||
- UI: Events with `SkipHolidays` render a TentTree icon next to the main event icon. The custom recurrence icon in the header was removed; rely on Syncfusion’s native lower-right recurrence badge.
|
- UI: Events with `SkipHolidays` render a TentTree icon next to the main event icon. The custom recurrence icon in the header was removed; rely on Syncfusion’s native lower-right recurrence badge.
|
||||||
|
- Website & WebUntis: Both event types display a website. WebUntis reads its URL from the system `supplement_table_url` and does not provide a per-event URL field.
|
||||||
|
|
||||||
- Program info page (`dashboard/src/programminfo.tsx`):
|
- Program info page (`dashboard/src/programminfo.tsx`):
|
||||||
- Loads data from `dashboard/public/program-info.json` (app name, version, build info, tech stack, changelog).
|
- Loads data from `dashboard/public/program-info.json` (app name, version, build info, tech stack, changelog).
|
||||||
@@ -153,6 +155,7 @@ Note: Syncfusion usage in the dashboard is already documented above; if a UI for
|
|||||||
- Always compare datetimes in UTC; some DB values may be naive—normalize before comparing (see `routes/events.py`).
|
- Always compare datetimes in UTC; some DB values may be naive—normalize before comparing (see `routes/events.py`).
|
||||||
- Scheduler enforces UTC comparisons and normalizes naive timestamps. It publishes only currently active events and clears retained topics for groups with no active events. It also queries a future window (default: 7 days) and expands recurring events using RFC 5545 rules. Event exceptions are respected. Logging is concise and conversion lookups are cached.
|
- Scheduler enforces UTC comparisons and normalizes naive timestamps. It publishes only currently active events and clears retained topics for groups with no active events. It also queries a future window (default: 7 days) and expands recurring events using RFC 5545 rules. Event exceptions are respected. Logging is concise and conversion lookups are cached.
|
||||||
- Use retained MQTT messages for state that clients must recover after reconnect (events per group, client group_id).
|
- Use retained MQTT messages for state that clients must recover after reconnect (events per group, client group_id).
|
||||||
|
- Clients should parse `event_type` and then read the corresponding nested payload (`presentation`, `website`, etc.). `website` and `webuntis` use the same nested `website` payload with `type: browser` and a `url`.
|
||||||
- In-container DB host is `db`; do not use `localhost` inside services.
|
- In-container DB host is `db`; do not use `localhost` inside services.
|
||||||
- No separate dev vs prod secret conventions: use the same env var keys across environments (e.g., `DB_CONN`, `MQTT_USER`, `MQTT_PASSWORD`).
|
- No separate dev vs prod secret conventions: use the same env var keys across environments (e.g., `DB_CONN`, `MQTT_USER`, `MQTT_PASSWORD`).
|
||||||
- When adding a new route:
|
- When adding a new route:
|
||||||
@@ -177,6 +180,12 @@ Note: Syncfusion usage in the dashboard is already documented above; if a UI for
|
|||||||
|
|
||||||
## Scheduler payloads: presentation extras
|
## Scheduler payloads: presentation extras
|
||||||
- Presentation event payloads now include `page_progress` and `auto_progress` in addition to `slide_interval` and media files. These are sourced from per-event fields in the database (with system defaults applied on event creation).
|
- Presentation event payloads now include `page_progress` and `auto_progress` in addition to `slide_interval` and media files. These are sourced from per-event fields in the database (with system defaults applied on event creation).
|
||||||
|
|
||||||
|
## Scheduler payloads: website & webuntis
|
||||||
|
- For both `website` and `webuntis`, the scheduler emits a nested `website` object:
|
||||||
|
- `{ "type": "browser", "url": "https://..." }`
|
||||||
|
- The `event_type` remains `website` or `webuntis`. Clients should treat both identically for rendering.
|
||||||
|
- The WebUntis URL is set at event creation by reading the system `supplement_table_url`.
|
||||||
|
|
||||||
Questions or unclear areas? Tell us if you need: exact devcontainer debugging steps, stricter Alembic workflow, or a seed dataset beyond `init_defaults.py`.
|
Questions or unclear areas? Tell us if you need: exact devcontainer debugging steps, stricter Alembic workflow, or a seed dataset beyond `init_defaults.py`.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Your database has been successfully initialized! Here's what you need to know:
|
|||||||
|
|
||||||
### ✅ Current Status
|
### ✅ Current Status
|
||||||
- **Database**: MariaDB 11.2 running in Docker container `infoscreen-db`
|
- **Database**: MariaDB 11.2 running in Docker container `infoscreen-db`
|
||||||
- **Schema**: Up to date (Alembic revision: `b5a6c3d4e7f8`)
|
- **Schema**: Up to date (check with `alembic current` in `server/`)
|
||||||
- **Default Data**: Admin user and client group created
|
- **Default Data**: Admin user and client group created
|
||||||
- **Academic Periods**: Austrian school years 2024/25 (active), 2025/26, 2026/27
|
- **Academic Periods**: Austrian school years 2024/25 (active), 2025/26, 2026/27
|
||||||
|
|
||||||
@@ -82,8 +82,70 @@ session.close()
|
|||||||
- **`conversions`** - File conversion jobs (PPT → PDF)
|
- **`conversions`** - File conversion jobs (PPT → PDF)
|
||||||
- **`academic_periods`** - School year/semester management
|
- **`academic_periods`** - School year/semester management
|
||||||
- **`school_holidays`** - Holiday calendar
|
- **`school_holidays`** - Holiday calendar
|
||||||
|
- **`event_exceptions`** - Overrides and skips for recurring events (per occurrence)
|
||||||
|
- **`system_settings`** - Key–value store for global settings
|
||||||
- **`alembic_version`** - Migration tracking
|
- **`alembic_version`** - Migration tracking
|
||||||
|
|
||||||
|
### Key details and relationships
|
||||||
|
|
||||||
|
- Users (`users`)
|
||||||
|
- Fields: `username` (unique), `password_hash`, `role` (enum: user|editor|admin|superadmin), `is_active`
|
||||||
|
|
||||||
|
- Client groups (`client_groups`)
|
||||||
|
- Fields: `name` (unique), `description`, `is_active`
|
||||||
|
|
||||||
|
- Clients (`clients`)
|
||||||
|
- Fields: `uuid` (PK), network/device metadata, `group_id` (FK→client_groups, default 1), `last_alive` (updated on heartbeat), `is_active`
|
||||||
|
|
||||||
|
- Academic periods (`academic_periods`)
|
||||||
|
- Fields: `name` (unique), optional `display_name`, `start_date`, `end_date`, `period_type` (enum: schuljahr|semester|trimester), `is_active` (at most one should be active)
|
||||||
|
- Indexes: `is_active`, dates
|
||||||
|
|
||||||
|
- Event media (`event_media`)
|
||||||
|
- Fields: `media_type` (enum, see below), `url`, optional `file_path`, optional `message_content`, optional `academic_period_id`
|
||||||
|
- Used by events of types: presentation, video, website, message, other
|
||||||
|
|
||||||
|
- Events (`events`)
|
||||||
|
- Core: `group_id` (FK), optional `academic_period_id` (FK), `title`, optional `description`, `start`, `end`, `event_type` (enum), optional `event_media_id` (FK)
|
||||||
|
- Presentation/video extras: `autoplay`, `loop`, `volume`, `slideshow_interval`, `page_progress`, `auto_progress`
|
||||||
|
- Recurrence: `recurrence_rule` (RFC 5545 RRULE), `recurrence_end`, `skip_holidays` (bool)
|
||||||
|
- Audit/state: `created_by` (FK→users), `updated_by` (FK→users), `is_active`
|
||||||
|
- Indexes: `start`, `end`, `recurrence_rule`, `recurrence_end`
|
||||||
|
- Relationships: `event_media`, `academic_period`, `exceptions` (one-to-many to `event_exceptions` with cascade delete)
|
||||||
|
|
||||||
|
- Event exceptions (`event_exceptions`)
|
||||||
|
- Purpose: track per-occurrence skips or overrides for a recurring master event
|
||||||
|
- Fields: `event_id` (FK→events, ondelete CASCADE), `exception_date` (Date), `is_skipped`, optional overrides (`title`, `description`, `start`, `end`)
|
||||||
|
|
||||||
|
- School holidays (`school_holidays`)
|
||||||
|
- Unique: (`name`, `start_date`, `end_date`, `region`)
|
||||||
|
- Used in combination with `events.skip_holidays`
|
||||||
|
|
||||||
|
- Conversions (`conversions`)
|
||||||
|
- Purpose: track PPT/PPTX/ODP → PDF processing
|
||||||
|
- Fields: `source_event_media_id` (FK→event_media, ondelete CASCADE), `target_format`, `target_path`, `status` (enum), `file_hash`, timestamps, `error_message`
|
||||||
|
- Indexes: (`source_event_media_id`, `target_format`), (`status`, `target_format`)
|
||||||
|
- Unique: (`source_event_media_id`, `target_format`, `file_hash`) — idempotency per content
|
||||||
|
|
||||||
|
- System settings (`system_settings`)
|
||||||
|
- Key–value store: `key` (PK), `value`, optional `description`, `updated_at`
|
||||||
|
- Notable keys used by the app: `presentation_interval`, `presentation_page_progress`, `presentation_auto_progress`
|
||||||
|
|
||||||
|
### Enums (reference)
|
||||||
|
|
||||||
|
- UserRole: `user`, `editor`, `admin`, `superadmin`
|
||||||
|
- AcademicPeriodType: `schuljahr`, `semester`, `trimester`
|
||||||
|
- EventType: `presentation`, `website`, `video`, `message`, `other`, `webuntis`
|
||||||
|
- MediaType: `pdf`, `ppt`, `pptx`, `odp`, `mp4`, `avi`, `mkv`, `mov`, `wmv`, `flv`, `webm`, `mpg`, `mpeg`, `ogv`, `jpg`, `jpeg`, `png`, `gif`, `bmp`, `tiff`, `svg`, `html`, `website`
|
||||||
|
- ConversionStatus: `pending`, `processing`, `ready`, `failed`
|
||||||
|
|
||||||
|
### Timezones, recurrence, and holidays
|
||||||
|
|
||||||
|
- All timestamps are stored/compared as timezone-aware UTC. Any naive datetimes are normalized to UTC before comparisons.
|
||||||
|
- Recurrence is represented on events via `recurrence_rule` (RFC 5545 RRULE) and `recurrence_end`. Do not pre-expand series in the DB.
|
||||||
|
- Per-occurrence exclusions/overrides are stored in `event_exceptions`. The API also emits EXDATE tokens matching occurrence start times (UTC) so the frontend can exclude instances natively.
|
||||||
|
- When `skip_holidays` is true, occurrences that fall on school holidays are excluded via corresponding `event_exceptions`.
|
||||||
|
|
||||||
### Environment Variables:
|
### Environment Variables:
|
||||||
```bash
|
```bash
|
||||||
DB_CONN=mysql+pymysql://infoscreen_admin:KqtpM7wmNdM1DamFKs@db/infoscreen_by_taa
|
DB_CONN=mysql+pymysql://infoscreen_admin:KqtpM7wmNdM1DamFKs@db/infoscreen_by_taa
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ This changelog tracks all changes made in the development workspace, including i
|
|||||||
- Scheduler behavior: Now publishes only currently active events per group (at "now"); clears retained topics by publishing `[]` for groups with no active events; normalizes naive timestamps and compares times in UTC; presentation payloads include `page_progress` and `auto_progress`.
|
- Scheduler behavior: Now publishes only currently active events per group (at "now"); clears retained topics by publishing `[]` for groups with no active events; normalizes naive timestamps and compares times in UTC; presentation payloads include `page_progress` and `auto_progress`.
|
||||||
- Recurrence handling: Still queries a 7‑day window to expand recurring events and apply exceptions; recurring events only deactivate after `recurrence_end` (UNTIL).
|
- Recurrence handling: Still queries a 7‑day window to expand recurring events and apply exceptions; recurring events only deactivate after `recurrence_end` (UNTIL).
|
||||||
- Logging: Temporarily added filter diagnostics during debugging; removed verbose logs after verification.
|
- Logging: Temporarily added filter diagnostics during debugging; removed verbose logs after verification.
|
||||||
- Documentation: Updated `.github/copilot-instructions.md` and `README.md` to reflect presentation defaults, per‑event flags, scheduler active‑only publishing, retained‑topic cleanup, and UTC normalization.
|
- WebUntis event type: Implemented new `webuntis` type. Event creation resolves URL from system `supplement_table_url`; returns 400 if not configured. WebUntis behaves like Website on clients (shared website payload).
|
||||||
- Release metadata: Program info bumped to `2025.1.0-alpha.12`; changelog entries focus on UI‑facing changes, backend/internal notes removed from user-facing entries.
|
- Settings consolidation: Removed separate `webuntis_url` (if present during dev); WebUntis and Vertretungsplan share `supplement_table_url`. Removed `/api/system-settings/webuntis-url` endpoints; use `/api/system-settings/supplement-table`.
|
||||||
|
- Scheduler payloads: Added top-level `event_type` for all events; introduced unified nested `website` payload for both `website` and `webuntis` events: `{ "type": "browser", "url": "…" }`.
|
||||||
|
- Frontend: Program info bumped to `2025.1.0-alpha.13`; changelog includes WebUntis/Website unification and settings update. Event modal shows no per-event URL for WebUntis.
|
||||||
|
- Documentation: Added `MQTT_EVENT_PAYLOAD_GUIDE.md` and `WEBUNTIS_EVENT_IMPLEMENTATION.md`. Updated `.github/copilot-instructions.md` and `README.md` for unified Website/WebUntis handling and system settings usage.
|
||||||
|
|
||||||
Note: These changes are available in the development environment and may be included in future releases. For released changes, see TECH-CHANGELOG.md.
|
Note: These changes are available in the development environment and may be included in future releases. For released changes, see TECH-CHANGELOG.md.
|
||||||
|
|||||||
301
MQTT_EVENT_PAYLOAD_GUIDE.md
Normal file
301
MQTT_EVENT_PAYLOAD_GUIDE.md
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
# MQTT Event Payload Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes the MQTT message structure used by the Infoscreen system to deliver event information from the scheduler to display clients. It covers best practices, payload formats, and versioning strategies.
|
||||||
|
|
||||||
|
## MQTT Topics
|
||||||
|
|
||||||
|
### Event Distribution
|
||||||
|
- **Topic**: `infoscreen/events/{group_id}`
|
||||||
|
- **Retained**: Yes
|
||||||
|
- **Format**: JSON array of event objects
|
||||||
|
- **Purpose**: Delivers active events to client groups
|
||||||
|
|
||||||
|
### Per-Client Configuration
|
||||||
|
- **Topic**: `infoscreen/{uuid}/group_id`
|
||||||
|
- **Retained**: Yes
|
||||||
|
- **Format**: Integer (group ID)
|
||||||
|
- **Purpose**: Assigns clients to groups
|
||||||
|
|
||||||
|
## Message Structure
|
||||||
|
|
||||||
|
### General Principles
|
||||||
|
|
||||||
|
1. **Type Safety**: Always include `event_type` to allow clients to parse appropriately
|
||||||
|
2. **Backward Compatibility**: Add new fields without removing old ones
|
||||||
|
3. **Extensibility**: Use nested objects for event-type-specific data
|
||||||
|
4. **UTC Timestamps**: All times in ISO 8601 format with timezone info
|
||||||
|
|
||||||
|
### Base Event Structure
|
||||||
|
|
||||||
|
Every event includes these common fields:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"title": "Event Title",
|
||||||
|
"start": "2025-10-19T09:00:00+00:00",
|
||||||
|
"end": "2025-10-19T09:30:00+00:00",
|
||||||
|
"group_id": 1,
|
||||||
|
"event_type": "presentation|website|webuntis|video|message|other",
|
||||||
|
"recurrence_rule": "FREQ=WEEKLY;BYDAY=MO,WE,FR" or null,
|
||||||
|
"recurrence_end": "2025-12-31T23:59:59+00:00" or null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event Type-Specific Payloads
|
||||||
|
|
||||||
|
#### Presentation Events
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"event_type": "presentation",
|
||||||
|
"title": "Morning Announcements",
|
||||||
|
"start": "2025-10-19T09:00:00+00:00",
|
||||||
|
"end": "2025-10-19T09:30:00+00:00",
|
||||||
|
"group_id": 1,
|
||||||
|
"presentation": {
|
||||||
|
"type": "slideshow",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "slides.pdf",
|
||||||
|
"url": "http://server:8000/api/files/converted/abc123.pdf",
|
||||||
|
"checksum": null,
|
||||||
|
"size": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"slide_interval": 10000,
|
||||||
|
"auto_advance": true,
|
||||||
|
"page_progress": true,
|
||||||
|
"auto_progress": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
- `type`: Always "slideshow" for presentations
|
||||||
|
- `files`: Array of file objects with download URLs
|
||||||
|
- `slide_interval`: Milliseconds between slides (default: 5000)
|
||||||
|
- `auto_advance`: Whether to automatically advance slides
|
||||||
|
- `page_progress`: Show page number indicator
|
||||||
|
- `auto_progress`: Enable automatic progression
|
||||||
|
|
||||||
|
#### Website Events
|
||||||
|
|
||||||
|
```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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fields**:
|
||||||
|
- `type`: Always "browser" for website display
|
||||||
|
- `url`: Full URL to display in embedded browser
|
||||||
|
|
||||||
|
#### WebUntis Events
|
||||||
|
|
||||||
|
```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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: WebUntis events use the same payload structure as website events. The URL is fetched from system settings (`webuntis_url`) rather than being specified per-event. Clients treat `webuntis` and `website` event types identically—both display a website.
|
||||||
|
|
||||||
|
#### Video Events (Future)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 126,
|
||||||
|
"event_type": "video",
|
||||||
|
"title": "Video Playback",
|
||||||
|
"start": "2025-10-19T09:00:00+00:00",
|
||||||
|
"end": "2025-10-19T09:30:00+00:00",
|
||||||
|
"group_id": 1,
|
||||||
|
"video": {
|
||||||
|
"type": "media",
|
||||||
|
"url": "http://server:8000/api/files/videos/123/video.mp4",
|
||||||
|
"autoplay": true,
|
||||||
|
"loop": false,
|
||||||
|
"volume": 0.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Message Events (Future)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 127,
|
||||||
|
"event_type": "message",
|
||||||
|
"title": "Important Announcement",
|
||||||
|
"start": "2025-10-19T09:00:00+00:00",
|
||||||
|
"end": "2025-10-19T09:30:00+00:00",
|
||||||
|
"group_id": 1,
|
||||||
|
"message": {
|
||||||
|
"type": "html",
|
||||||
|
"content": "<h1>Important</h1><p>Message content</p>",
|
||||||
|
"style": "default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Type-Based Parsing
|
||||||
|
|
||||||
|
Clients should:
|
||||||
|
1. Read the `event_type` field first
|
||||||
|
2. Switch/dispatch based on type
|
||||||
|
3. Parse type-specific nested objects (`presentation`, `website`, etc.)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Example client parsing
|
||||||
|
function parseEvent(event) {
|
||||||
|
switch (event.event_type) {
|
||||||
|
case 'presentation':
|
||||||
|
return handlePresentation(event.presentation);
|
||||||
|
case 'website':
|
||||||
|
case 'webuntis':
|
||||||
|
return handleWebsite(event.website);
|
||||||
|
case 'video':
|
||||||
|
return handleVideo(event.video);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Graceful Degradation
|
||||||
|
|
||||||
|
- Always provide fallback values for optional fields
|
||||||
|
- Validate URLs before attempting to load
|
||||||
|
- Handle missing or malformed data gracefully
|
||||||
|
|
||||||
|
### 3. Performance Optimization
|
||||||
|
|
||||||
|
- Cache downloaded presentation files
|
||||||
|
- Use checksums to avoid re-downloading unchanged content
|
||||||
|
- Preload resources before event start time
|
||||||
|
|
||||||
|
### 4. Time Handling
|
||||||
|
|
||||||
|
- Always parse ISO 8601 timestamps with timezone awareness
|
||||||
|
- Compare event start/end times in UTC
|
||||||
|
- Account for clock drift on embedded devices
|
||||||
|
|
||||||
|
### 5. Error Recovery
|
||||||
|
|
||||||
|
- Retry failed downloads with exponential backoff
|
||||||
|
- Log errors but continue operation
|
||||||
|
- Display fallback content if event data is invalid
|
||||||
|
|
||||||
|
## Message Flow
|
||||||
|
|
||||||
|
1. **Scheduler** queries active events from database
|
||||||
|
2. **Scheduler** formats events with type-specific payloads
|
||||||
|
3. **Scheduler** publishes JSON array to `infoscreen/events/{group_id}` (retained)
|
||||||
|
4. **Client** receives retained message on connect
|
||||||
|
5. **Client** parses events and schedules display
|
||||||
|
6. **Client** downloads resources (presentations, etc.)
|
||||||
|
7. **Client** displays events at scheduled times
|
||||||
|
|
||||||
|
## Versioning Strategy
|
||||||
|
|
||||||
|
### Adding New Event Types
|
||||||
|
|
||||||
|
1. Add enum value to `EventType` in `models/models.py`
|
||||||
|
2. Update scheduler's `format_event_with_media()` in `scheduler/db_utils.py`
|
||||||
|
3. Update events API in `server/routes/events.py`
|
||||||
|
4. Add icon mapping in `get_icon_for_type()`
|
||||||
|
5. Document payload structure in this guide
|
||||||
|
|
||||||
|
### Adding Fields to Existing Types
|
||||||
|
|
||||||
|
- **Safe**: Add new optional fields to nested objects
|
||||||
|
- **Unsafe**: Remove or rename existing fields
|
||||||
|
- **Migration**: Provide both old and new field names during transition
|
||||||
|
|
||||||
|
### Example: Adding a New Field
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event_type": "presentation",
|
||||||
|
"presentation": {
|
||||||
|
"type": "slideshow",
|
||||||
|
"files": [...],
|
||||||
|
"slide_interval": 10000,
|
||||||
|
"transition_effect": "fade" // NEW FIELD (optional)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Old clients ignore unknown fields; new clients use enhanced features.
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
1. **Hardcoding Event Types**: Use `event_type` field, not assumptions
|
||||||
|
2. **Timezone Confusion**: Always use UTC internally
|
||||||
|
3. **Missing Error Handling**: Network failures, malformed URLs, etc.
|
||||||
|
4. **Resource Leaks**: Clean up downloaded files periodically
|
||||||
|
5. **Not Handling Recurrence**: Events may repeat; check `recurrence_rule`
|
||||||
|
|
||||||
|
## System Settings Integration
|
||||||
|
|
||||||
|
Some event types rely on system-wide settings rather than per-event configuration:
|
||||||
|
|
||||||
|
### WebUntis / Supplement Table URL
|
||||||
|
- **Setting Key**: `supplement_table_url`
|
||||||
|
- **API Endpoint**: `GET/POST /api/system-settings/supplement-table`
|
||||||
|
- **Usage**: Automatically applied when creating `webuntis` events
|
||||||
|
- **Default**: Empty string (must be configured by admin)
|
||||||
|
- **Description**: This URL is shared for both Vertretungsplan (supplement table) and WebUntis displays
|
||||||
|
|
||||||
|
### Presentation Defaults
|
||||||
|
- `presentation_interval`: Default slide interval (seconds)
|
||||||
|
- `presentation_page_progress`: Show page indicators by default
|
||||||
|
- `presentation_auto_progress`: Auto-advance by default
|
||||||
|
|
||||||
|
These are applied when creating new events but can be overridden per-event.
|
||||||
|
|
||||||
|
## Testing Recommendations
|
||||||
|
|
||||||
|
1. **Unit Tests**: Validate payload serialization/deserialization
|
||||||
|
2. **Integration Tests**: Full scheduler → MQTT → client flow
|
||||||
|
3. **Edge Cases**: Empty event lists, missing URLs, malformed data
|
||||||
|
4. **Performance Tests**: Large file downloads, many events
|
||||||
|
5. **Time Tests**: Events across midnight, timezone boundaries, DST
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- `AUTH_SYSTEM.md` - Authentication and authorization
|
||||||
|
- `DATABASE_GUIDE.md` - Database schema and models
|
||||||
|
- `.github/copilot-instructions.md` - System architecture overview
|
||||||
|
- `scheduler/scheduler.py` - Event publishing implementation
|
||||||
|
- `scheduler/db_utils.py` - Event formatting logic
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
- **2025-10-19**: Initial documentation
|
||||||
|
- Documented base event structure
|
||||||
|
- Added presentation and website/webuntis payload formats
|
||||||
|
- Established best practices and versioning strategy
|
||||||
@@ -66,6 +66,7 @@ Data flow summary:
|
|||||||
- **Videos**: Media file streaming
|
- **Videos**: Media file streaming
|
||||||
- **Messages**: Text announcements
|
- **Messages**: Text announcements
|
||||||
- **WebUntis**: Educational schedule integration
|
- **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.
|
||||||
- **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.
|
- **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.
|
- **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.
|
||||||
|
|
||||||
@@ -196,6 +197,14 @@ For detailed deployment instructions, see:
|
|||||||
- `infoscreen/discovery` - Client registration
|
- `infoscreen/discovery` - Client registration
|
||||||
- `infoscreen/{uuid}/heartbeat` - Client alive status
|
- `infoscreen/{uuid}/heartbeat` - Client alive status
|
||||||
- `infoscreen/events/{group_id}` - Event distribution
|
- `infoscreen/events/{group_id}` - Event distribution
|
||||||
|
## 🔗 Scheduler Event Payloads
|
||||||
|
|
||||||
|
- Presentations include a `presentation` object with `files`, `slide_interval`, `page_progress`, and `auto_progress`.
|
||||||
|
- Website and WebUntis events share a unified payload:
|
||||||
|
- `website`: `{ "type": "browser", "url": "https://..." }`
|
||||||
|
- The `event_type` field remains specific (e.g., `presentation`, `website`, `webuntis`) so clients can dispatch appropriately; however, `website` and `webuntis` should be handled identically in clients.
|
||||||
|
|
||||||
|
See `MQTT_EVENT_PAYLOAD_GUIDE.md` for details.
|
||||||
- `infoscreen/{uuid}/group_id` - Client group assignment
|
- `infoscreen/{uuid}/group_id` - Client group assignment
|
||||||
|
|
||||||
## 📁 Project Structure
|
## 📁 Project Structure
|
||||||
|
|||||||
@@ -7,7 +7,34 @@ This changelog documents technical and developer-relevant changes included in pu
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2025.1.0-alpha.11 (2025-10-16)
|
## 2025.1.0-alpha.13 (2025-10-19)
|
||||||
|
- 🗓️ Events/API:
|
||||||
|
- Implemented new `webuntis` event type. Event creation now resolves the URL from the system setting `supplement_table_url`; returns 400 if unset.
|
||||||
|
- Removed obsolete `webuntis-url` settings endpoints. Use `GET/POST /api/system-settings/supplement-table` for URL and enabled state (shared for WebUntis/Vertretungsplan).
|
||||||
|
- Initialization defaults: dropped `webuntis_url`; updated `supplement_table_url` description to “Vertretungsplan / WebUntis”.
|
||||||
|
- 🚦 Scheduler payloads:
|
||||||
|
- Unified Website/WebUntis payload: both emit a nested `website` object `{ "type": "browser", "url": "…" }`; `event_type` remains either `website` or `webuntis` for dispatch.
|
||||||
|
- Payloads now include a top-level `event_type` string for all events to aid client dispatch.
|
||||||
|
- 🖥️ Frontend/Dashboard:
|
||||||
|
- Program info updated to `2025.1.0-alpha.13` with release notes.
|
||||||
|
- Settings → Events: WebUntis now uses the existing Supplement-Table URL; no separate WebUntis URL field.
|
||||||
|
- Event modal: WebUntis type behaves like Website (no per-event URL input).
|
||||||
|
- 📖 Documentation:
|
||||||
|
- Added `MQTT_EVENT_PAYLOAD_GUIDE.md` (message structure, client best practices, versioning).
|
||||||
|
- Added `WEBUNTIS_EVENT_IMPLEMENTATION.md` (design notes, admin setup, testing checklist).
|
||||||
|
- Updated `.github/copilot-instructions.md` and `README.md` for the unified Website/WebUntis handling and settings usage.
|
||||||
|
|
||||||
|
Notes for integrators:
|
||||||
|
- If you previously integrated against `/api/system-settings/webuntis-url`, migrate to `/api/system-settings/supplement-table`.
|
||||||
|
- Clients should now parse `event_type` and use the corresponding nested payload (`presentation`, `website`, …). `webuntis` and `website` should be handled identically (nested `website` payload).
|
||||||
|
|
||||||
|
|
||||||
|
## 2025.1.0-alpha.12 (2025-10-19)
|
||||||
|
- 🛠️ Backend: Seeded presentation defaults (`presentation_interval`, `presentation_page_progress`, `presentation_auto_progress`) in system settings; applied on event creation.
|
||||||
|
- 🗃️ Data model: Added `page_progress` and `auto_progress` fields to `Event` (with Alembic migration).
|
||||||
|
- 🗓️ Scheduler: Now publishes only currently active events per group (at "now"); clears retained topics by publishing `[]` for groups with no active events; normalizes naive timestamps and compares times in UTC; presentation payloads include `page_progress` and `auto_progress`.
|
||||||
|
- 🖥️ Dashboard: Settings → Events tab now includes Presentations defaults (interval, page-progress, auto-progress) with load/save via API; event modal applies defaults on create and persists per-event values on edit.
|
||||||
|
- 📖 Docs: Updated README and Copilot instructions for new scheduler behavior, UTC handling, presentation defaults, and per-event flags.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"appName": "Infoscreen-Management",
|
"appName": "Infoscreen-Management",
|
||||||
"version": "2025.1.0-alpha.12",
|
"version": "2025.1.0-alpha.13",
|
||||||
"copyright": "© 2025 Third-Age-Applications",
|
"copyright": "© 2025 Third-Age-Applications",
|
||||||
"supportContact": "support@third-age-applications.com",
|
"supportContact": "support@third-age-applications.com",
|
||||||
"description": "Eine zentrale Verwaltungsoberfläche für digitale Informationsbildschirme.",
|
"description": "Eine zentrale Verwaltungsoberfläche für digitale Informationsbildschirme.",
|
||||||
@@ -26,10 +26,20 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"buildInfo": {
|
"buildInfo": {
|
||||||
"buildDate": "2025-10-18T14:00:00Z",
|
"buildDate": "2025-10-19T12:00:00Z",
|
||||||
"commitId": "9f2ae8b44c3a"
|
"commitId": "9f2ae8b44c3a"
|
||||||
},
|
},
|
||||||
"changelog": [
|
"changelog": [
|
||||||
|
{
|
||||||
|
"version": "2025.1.0-alpha.13",
|
||||||
|
"date": "2025-10-19",
|
||||||
|
"changes": [
|
||||||
|
"🆕 Events: Neuer Termin-Typ ‘WebUntis’ – nutzt die systemweite Vertretungsplan-URL; Darstellung ident mit ‘Website’.",
|
||||||
|
"🛠️ Scheduler/Clients: Einheitliches Website-Payload für ‘Website’ und ‘WebUntis’ (type: browser, url).",
|
||||||
|
"🛠️ Einstellungen › Events: WebUntis verwendet jetzt die bestehende Vertretungsplan-URL (Supplement-Table); kein separates WebUntis-URL-Feld mehr.",
|
||||||
|
"📖 Doku: MQTT-Event-Payload-Leitfaden und Implementierungsnotizen zu WebUntis/Website ergänzt."
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "2025.1.0-alpha.12",
|
"version": "2025.1.0-alpha.12",
|
||||||
"date": "2025-10-18",
|
"date": "2025-10-18",
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ def format_event_with_media(event):
|
|||||||
"start": str(event.start),
|
"start": str(event.start),
|
||||||
"end": str(event.end),
|
"end": str(event.end),
|
||||||
"group_id": event.group_id,
|
"group_id": event.group_id,
|
||||||
|
"event_type": event.event_type.value if event.event_type else None,
|
||||||
# Carry recurrence metadata for consumers if needed
|
# Carry recurrence metadata for consumers if needed
|
||||||
"recurrence_rule": getattr(event, "recurrence_rule", None),
|
"recurrence_rule": getattr(event, "recurrence_rule", None),
|
||||||
"recurrence_end": (event.recurrence_end.isoformat() if getattr(event, "recurrence_end", None) else None),
|
"recurrence_end": (event.recurrence_end.isoformat() if getattr(event, "recurrence_end", None) else None),
|
||||||
@@ -244,6 +245,17 @@ def format_event_with_media(event):
|
|||||||
f"[Scheduler] Using original file for event_media_id={media.id}: {filename}")
|
f"[Scheduler] Using original file for event_media_id={media.id}: {filename}")
|
||||||
_media_decision_logged.add(media.id)
|
_media_decision_logged.add(media.id)
|
||||||
|
|
||||||
# Add other event types...
|
# 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)
|
||||||
|
|
||||||
|
# Add other event types (video, message, etc.) here as needed...
|
||||||
|
|
||||||
return event_dict
|
return event_dict
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ with engine.connect() as conn:
|
|||||||
|
|
||||||
# Default System Settings anlegen
|
# Default System Settings anlegen
|
||||||
default_settings = [
|
default_settings = [
|
||||||
('supplement_table_url', '', 'URL für Vertretungsplan (Stundenplan-Änderungstabelle)'),
|
('supplement_table_url', '', 'URL für Vertretungsplan / WebUntis (Stundenplan-Änderungstabelle)'),
|
||||||
('supplement_table_enabled', 'false', 'Ob Vertretungsplan aktiviert ist'),
|
('supplement_table_enabled', 'false', 'Ob Vertretungsplan aktiviert ist'),
|
||||||
('presentation_interval', '10', 'Standard Intervall für Präsentationen (Sekunden)'),
|
('presentation_interval', '10', 'Standard Intervall für Präsentationen (Sekunden)'),
|
||||||
('presentation_page_progress', 'true', 'Seitenfortschritt anzeigen (Page-Progress) für Präsentationen'),
|
('presentation_page_progress', 'true', 'Seitenfortschrift anzeigen (Page-Progress) für Präsentationen'),
|
||||||
('presentation_auto_progress', 'true', 'Automatischer Fortschritt (Auto-Progress) für Präsentationen'),
|
('presentation_auto_progress', 'true', 'Automatischer Fortschritt (Auto-Progress) für Präsentationen'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from server.permissions import editor_or_higher
|
from server.permissions import editor_or_higher
|
||||||
from server.database import Session
|
from server.database import Session
|
||||||
from models.models import Event, EventMedia, MediaType, EventException
|
from models.models import Event, EventMedia, MediaType, EventException, SystemSetting
|
||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timezone, timedelta
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from dateutil.rrule import rrulestr
|
from dateutil.rrule import rrulestr
|
||||||
@@ -375,6 +375,25 @@ def create_event():
|
|||||||
session.commit()
|
session.commit()
|
||||||
event_media_id = media.id
|
event_media_id = media.id
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
# created_by aus den Daten holen, Default: None
|
# created_by aus den Daten holen, Default: None
|
||||||
created_by = data.get("created_by")
|
created_by = data.get("created_by")
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ def update_supplement_table_settings():
|
|||||||
url_setting = SystemSetting(
|
url_setting = SystemSetting(
|
||||||
key='supplement_table_url',
|
key='supplement_table_url',
|
||||||
value=url,
|
value=url,
|
||||||
description='URL für Vertretungsplan (Stundenplan-Änderungstabelle)'
|
description='URL für Vertretungsplan / WebUntis (Stundenplan-Änderungstabelle)'
|
||||||
)
|
)
|
||||||
session.add(url_setting)
|
session.add(url_setting)
|
||||||
|
|
||||||
@@ -201,3 +201,4 @@ def update_supplement_table_settings():
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user