- Add GET /api/clients/crashed endpoint (process_status=crashed or stale heartbeat) - Add restart_app command action with same lifecycle + lockout as reboot_host - Scheduler: crash auto-recovery loop (CRASH_RECOVERY_ENABLED flag, lockout, MQTT publish) - Scheduler: unconditional command expiry sweep per poll cycle (sweep_expired_commands) - Listener: subscribe to infoscreen/+/service_failed; persist service_failed_at + unit - Listener: extract broker_connection block from health payload; persist reconnect_count + last_disconnect_at - DB migration b1c2d3e4f5a6: service_failed_at, service_failed_unit, mqtt_reconnect_count, mqtt_last_disconnect_at on clients - Add GET /api/clients/service_failed and POST /api/clients/<uuid>/clear_service_failed - Monitoring overview API: include mqtt_reconnect_count + mqtt_last_disconnect_at per client - Frontend: orange service-failed alert panel (hidden when empty, auto-refresh, quittieren action) - Frontend: MQTT reconnect count + last disconnect in client detail panel - MQTT auth hardening: listener/scheduler/server use env credentials; broker enforces allow_anonymous false - Client command lifecycle foundation: ClientCommand model, reboot_host/shutdown_host, full ACK lifecycle - Docs: TECH-CHANGELOG, DEV-CHANGELOG, MQTT_EVENT_PAYLOAD_GUIDE, copilot-instructions updated - Add implementation-plans/, RESTART_VALIDATION_CHECKLIST.md, TODO.md
39 KiB
TECH-CHANGELOG
This changelog documents technical and developer-relevant changes included in public releases. For development workspace changes, see DEV-CHANGELOG.md. Not all changes here are reflected in the user-facing changelog (program-info.json), and not all UI/feature changes are repeated here. Some changes (e.g., backend refactoring, API adjustments, infrastructure, developer tooling, or internal logic) may only appear in TECH-CHANGELOG.md. For UI/feature changes, see dashboard/public/program-info.json.
Unreleased
- <EFBFBD> Crash detection, auto-recovery, and service_failed monitoring (2026-04-05):
- Added
GET /api/clients/crashedendpoint: returns active clients withprocess_status=crashedor stale heartbeat beyond grace period, withcrash_reasonfield. - Added
restart_appcommand action alongside existingreboot_host/shutdown_host; registered inserver/routes/clients.pywith same safety lockout. - Scheduler: Added crash auto-recovery loop (feature-flagged via
CRASH_RECOVERY_ENABLED): scans candidates viaget_crash_recovery_candidates(), issuesreboot_hostcommand per client, publishes to primary + compat MQTT topics, updates command lifecycle. - Scheduler: Added unconditional command expiry sweep each poll cycle via
sweep_expired_commands()inscheduler/db_utils.py: marks non-terminalClientCommandrows withexpires_at < nowasexpired. - Added
service_failedtopic ingestion inlistener/listener.py: subscribe toinfoscreen/+/service_failedon every connect; persistservice_failed_atandservice_failed_uniton Client; empty payload (retain clear) ignored. - Added
broker_connectionblock extraction in health payload handler: persistsmqtt_reconnect_countandmqtt_last_disconnect_atfrominfoscreen/{uuid}/health. - Added four new DB columns to
clientstable via migrationb1c2d3e4f5a6:service_failed_at,service_failed_unit,mqtt_reconnect_count,mqtt_last_disconnect_at. - Added
GET /api/clients/service_failedendpoint: lists clients withservice_failed_atset, ordered by event time desc. - Added
POST /api/clients/<uuid>/clear_service_failedendpoint: clears DB flag and publishes empty retained MQTT message to clearinfoscreen/{uuid}/service_failed. - Monitoring overview API (
GET /api/client-logs/monitoring-overview) now includesmqtt_reconnect_countandmqtt_last_disconnect_atper client. - Frontend: Added orange service-failed alert panel to monitoring page (hidden when empty, auto-refresh 15s, per-row Quittieren button with loading/success/error states).
- Frontend: Client detail panel in monitoring now shows MQTT reconnect count and last disconnect timestamp.
- Frontend: Added
ServiceFailedClient,ServiceFailedClientsResponsetypes;fetchServiceFailedClients()andclearServiceFailed()API helpers indashboard/src/apiClients.ts. - Added
service_failedtopic contract toMQTT_EVENT_PAYLOAD_GUIDE.md.
- Added
- <EFBFBD>🔐 MQTT auth hardening for server-side services (2026-04-03):
listener/listener.pynow uses env-based broker connectivity for host/port and credentials (MQTT_BROKER_HOST,MQTT_BROKER_PORT,MQTT_USER,MQTT_PASSWORD) instead of anonymous fixedmqtt:1883.scheduler/scheduler.pynow uses the same env-based MQTT auth path and optional TLS toggles (MQTT_TLS_ENABLED,MQTT_TLS_CA_CERT,MQTT_TLS_CERTFILE,MQTT_TLS_KEYFILE,MQTT_TLS_INSECURE).docker-compose.ymlanddocker-compose.override.ymlnow pass MQTT credentials into listener and scheduler containers for consistent authenticated connections.- Mosquitto is now configured for authenticated access (
allow_anonymous false,password_file /mosquitto/config/passwd) and bootstraps credentials from env at container startup. - MQTT healthcheck publish now authenticates with configured broker credentials.
- 🔁 Client command lifecycle foundation (restart/shutdown) (2026-04-03):
- Added persistent command tracking model
ClientCommandinmodels/models.pyand Alembic migrationaa12bb34cc56_add_client_commands_table.py. - Upgraded
POST /api/clients/<uuid>/restartfrom fire-and-forget publish to lifecycle-aware command issuance with command metadata (command_id,issued_at,expires_at,reason,requested_by). - Added
POST /api/clients/<uuid>/shutdownendpoint with the same lifecycle contract. - Added
GET /api/clients/commands/<command_id>status endpoint for command-state polling. - Added restart safety lockout in API path: max 3 restart commands per client in rolling 15 minutes, returning
blocked_safetywhen threshold is exceeded. - Added command MQTT publish to
infoscreen/{uuid}/commands(QoS1, non-retained) and temporary legacy restart compatibility publish toclients/{uuid}/restart. - Added temporary topic compatibility publish to
infoscreen/{uuid}/commandand listener acceptance ofinfoscreen/{uuid}/command/ackto bridge singular/plural naming assumptions. - Canonicalized command payload action values to host-level semantics:
reboot_hostandshutdown_host(API routes remain/restartand/shutdownfor operator UX compatibility). - Added frozen payload validation snippets for integration/client tooling in
implementation-plans/reboot-command-payload-schemas.mdandimplementation-plans/reboot-command-payload-schemas.json. - Listener now subscribes to
infoscreen/{uuid}/commands/ackand maps client acknowledgements into command lifecycle states (ack_received,execution_started,completed,failed). - Initial lifecycle statuses implemented server-side:
queued,publish_in_progress,published,failed, andblocked_safety. - Frontend API helper extended in
dashboard/src/apiClients.tswithClientCommandtyping plus command APIs for shutdown and status polling preparation.
- Added persistent command tracking model
2026.1.0-alpha.16 (2026-04-02)
- 🐛 Dashboard holiday banner refactoring and state fix (
dashboard/src/dashboard.tsx):- Motivation — unstable fetch function:
loadHolidayStatushadlocation.pathnamein itsuseCallbackdependency array, causing a new function reference to be created on every navigation event. TheuseEffectdepending on that reference then re-fired, producing overlapping API calls at mount that cancelled each other via the request-sequence guard, leaving the banner unresolved. - Refactoring: Removed
location.pathnamefromuseCallbackdeps (it was unused inside the function body). The callback now has an empty dependency array, making its reference stable across the component lifetime. TheuseEffectis keyed only to the stable callback reference — no spurious re-fires. - Motivation — Syncfusion stale render: Syncfusion's
MessageComponentcaches its rendered DOM internally and does not reactively update when React passes new children or props. Even after React state changed, the component displayed whatever text was rendered on first mount. - Fix: Added a
keyprop derived from${severity}:${text}toMessageComponent. React unmounts and remounts the component whenever the key changes, bypassing Syncfusion's internal caching and ensuring the correct message is always visible. - Result: Active-period name and holiday overlap details now render correctly on hard refresh, initial load, and route transitions without additional API calls.
- Motivation — unstable fetch function:
- 🗓️ Academic period bootstrap hardening (
server/init_academic_periods.py):- Refactored initialization into idempotent flow:
- seed default periods only when table is empty,
- on every run, activate exactly the non-archived period covering
date.today().
- Enforces single-active behavior by deactivating all previously active periods before setting the period for today.
- Emits explicit warning if no period covers current date (all remain inactive), improving operational diagnostics.
- Refactored initialization into idempotent flow:
- 🚀 Production startup alignment (
docker-compose.prod.yml):- Server startup command now runs
python /app/server/init_academic_periods.pyafter migrations and default settings bootstrap. - Removes manual post-deploy step to set an active academic period.
- Server startup command now runs
- 🌐 Dashboard UX/text refinement (
dashboard/src/dashboard.tsx):- Converted user-facing transliterated German strings to proper Umlauts in the dashboard (for example: "für", "prüfen", "Ferienüberschneidungen", "Vorfälle", "Ausfälle").
Notes for integrators:
- On production boot, the active period is now derived from current date coverage automatically.
- If customer calendars do not include today, startup logs a warning and dashboard banner will still guide admins to configure periods.
2026.1.0-alpha.15 (2026-03-31)
- 🔌 TV Power Intent Phase 1 (server-side):
- Scheduler now publishes retained QoS1 group-level intents to
infoscreen/groups/{group_id}/power/intent. - Implemented deterministic intent computation helpers in
scheduler/db_utils.py(compute_group_power_intent_basis,build_group_power_intent_body,compute_group_power_intent_fingerprint). - Implemented transition + heartbeat semantics in
scheduler/scheduler.py: stableintent_idon heartbeat; newintent_idonly on semantic transition. - Added startup publish and MQTT reconnect republish behavior for retained intent continuity.
- Added poll-based expiry rule:
expires_at = issued_at + max(3 × poll_interval_sec, 90s). - Added scheduler tests and canary validation artifacts:
scheduler/test_power_intent_utils.py,scheduler/test_power_intent_scheduler.py,test_power_intent_canary.py, andTV_POWER_CANARY_VALIDATION_CHECKLIST.md.
- Scheduler now publishes retained QoS1 group-level intents to
- 🗃️ Holiday data model scoping to academic periods:
- Added period scoping for holidays via
SchoolHoliday.academic_period_id(FK to academic periods) inmodels/models.py. - Added Alembic migration
f3c4d5e6a7b8_scope_school_holidays_to_academic_.pyto introduce FK/index/constraint updates for period-aware holiday storage. - Updated uniqueness semantics and indexing so holiday identity is evaluated in the selected academic period context.
- Added period scoping for holidays via
- 🔌 Holiday API hardening (
server/routes/holidays.py):- Extended to period-scoped workflows for list/import/manual CRUD.
- Added manual CRUD endpoints and behavior:
POST /api/holidaysPUT /api/holidays/<id>DELETE /api/holidays/<id>
- Enforced date-range validation against selected academic period for both import and manual writes.
- Added duplicate prevention (normalized name/region matching with null-safe handling).
- Implemented overlap policy:
- Same normalized
name+regionoverlaps (including adjacent ranges) are merged. - Different-identity overlaps are treated as conflicts (manual blocked, import skipped with details).
- Same normalized
- Import responses now include richer counters/details (inserted/updated/merged/skipped/conflicts).
- 🔁 Recurrence integration updates:
- Event holiday-skip exception regeneration now resolves holidays by
academic_period_idinstead of global holiday sets. - Updated event-side recurrence handling (
server/routes/events.py) to keep EXDATE behavior in sync with period-scoped holidays.
- Event holiday-skip exception regeneration now resolves holidays by
- 🖥️ Frontend integration (technical):
- Updated holiday API client (
dashboard/src/apiHolidays.ts) for period-aware list/upload and manual CRUD operations. - Settings holiday management (
dashboard/src/settings.tsx) now binds import/list/manual CRUD to selected academic period and surfaces conflict/merge outcomes. - Dashboard and appointments holiday data loading updated to active-period context.
- Updated holiday API client (
- 📖 Documentation & release alignment:
- Updated
.github/copilot-instructions.mdwith period-scoped holiday conventions, overlap policy, and settings behavior. - Refactored root
README.mdto index-style documentation and archived historical implementation docs underdocs/archive/. - Synchronized release line with user-facing version
2026.1.0-alpha.15indashboard/public/program-info.json.
- Updated
Notes for integrators:
- Holiday operations now require a clear academic period context; archived periods should be treated as read-only for holiday mutation flows.
- Existing recurrence flows depend on period-scoped holiday sets; verify period assignment for recurring master events when validating skip-holidays behavior.
2026.1.0-alpha.14 (2026-01-28)
- 🗓️ Ressourcen Page (Timeline View):
- New frontend page:
dashboard/src/ressourcen.tsx(357 lines) – Parallel timeline view showing active events for all room groups - Uses Syncfusion ScheduleComponent with TimelineViews module for resource-based scheduling
- Compact visualization: 65px row height per group, dynamically calculated total container height
- Real-time event loading: Fetches events per group for current date range on mount and view/date changes
- Timeline modes: Day (default) and Week views with date range calculation
- Color-coded event bars: Uses
getGroupColor()fromgroupColors.tsfor group theme matching - Displays first active event per group with type, title, and time window
- Filters out "Nicht zugeordnet" group from timeline display
- Resource mapping: Each group becomes a timeline resource row, events mapped via
ResourceId - Syncfusion modules: TimelineViews, Resize, DragAndDrop injected for rich interaction
- New frontend page:
- 🎨 Ressourcen Styling:
- New CSS file:
dashboard/src/ressourcen.css(178 lines) with modern Material 3 design - Fixed CSS lint errors: Converted
rgba()to modernrgb()notation with percentage alpha values (rgb(0 0 0 / 10%)) - Removed unnecessary quotes from font-family names (Roboto, Oxygen, Ubuntu, Cantarell)
- Fixed CSS selector specificity ordering (
.e-schedulebefore.ressourcen-timeline-wrapper .e-schedule) - Card-based controls layout with shadow and rounded corners
- Group ordering panel with scrollable list and action buttons
- Responsive timeline wrapper with flex layout
- New CSS file:
- 🔌 Group Order API:
- New backend endpoints in
server/routes/groups.py:GET /api/groups/order– Retrieve saved group display order (returns JSON withorderarray of group IDs)POST /api/groups/order– Persist group display order (accepts JSON withorderarray)
- Order persistence: Stored in
system_settingstable with keygroup_display_order(JSON array of integers) - Automatic synchronization: Missing group IDs added to order, removed IDs filtered out
- Frontend integration: Group order panel with drag up/down buttons, real-time reordering with backend sync
- New backend endpoints in
- 🖥️ Frontend Technical:
- State management: React hooks with unused setters removed (setTimelineView, setViewDate) to resolve lint warnings
- TypeScript: Changed
lettoconstfor immutable end date calculation - UTC date parsing: Uses parseUTCDate callback to append 'Z' and ensure UTC interpretation
- Event formatting: Capitalizes first letter of event type for display (e.g., "Website - Title")
- Loading state: Shows loading indicator while fetching group/event data
- Schedule height: Dynamic calculation based on
groups.length * 65px + 100pxfor header
- 📖 Documentation:
- Updated
.github/copilot-instructions.md:- Added Ressourcen page to "Recent changes" section (January 2026)
- Added
ressourcen.tsxandressourcen.cssto "Important files" list - Added Groups API order endpoints documentation
- Added comprehensive Ressourcen page section to "Frontend patterns"
- Updated
README.md:- Added Ressourcen page to "Pages Overview" section with feature details
- Added
GET/POST /api/groups/orderto Core Resources API section
- Bumped version in
dashboard/public/program-info.jsonto2026.1.0-alpha.14with user-facing changelog
- Updated
Notes for integrators:
- Group order API returns JSON with
{ "order": [1, 2, 3, ...] }structure (array of group IDs) - Timeline view automatically filters "Nicht zugeordnet" group for cleaner display
- CSS follows modern Material 3 color-function notation (
rgb(r g b / alpha%)) - Syncfusion ScheduleComponent requires TimelineViews, Resize, and DragAndDrop modules injected
Backend technical work (post-release notes; no version bump):
- 📊 Client Monitoring Infrastructure (Server-Side) (2026-03-10):
- Database schema: New Alembic migration
c1d2e3f4g5h6_add_client_monitoring.py(idempotent) adds:client_logstable: Stores centralized logs with columns (id, client_uuid, timestamp, level, message, context, created_at)- Foreign key:
client_logs.client_uuid→clients.uuid(ON DELETE CASCADE) - Health monitoring columns added to
clientstable:current_event_id,current_process,process_status,process_pid,last_screenshot_analyzed,screen_health_status,last_screenshot_hash - Indexes for performance: (client_uuid, timestamp DESC), (level, timestamp DESC), (created_at DESC)
- Data models (
models/models.py):- New enums:
LogLevel(ERROR, WARN, INFO, DEBUG),ProcessStatus(running, crashed, starting, stopped),ScreenHealthStatus(OK, BLACK, FROZEN, UNKNOWN) - New model:
ClientLogwith foreign key toClient(CASCADE on delete) - Extended
Clientmodel with 7 health monitoring fields
- New enums:
- MQTT listener extensions (
listener/listener.py):- New topic subscriptions:
infoscreen/+/logs/error,infoscreen/+/logs/warn,infoscreen/+/logs/info,infoscreen/+/health - Log handler: Parses JSON payloads, creates
ClientLogentries, validates client UUID exists (FK constraint) - Health handler: Updates client state from MQTT health messages
- Enhanced heartbeat handler: Captures
process_status,current_process,process_pid,current_event_idfrom payload
- New topic subscriptions:
- API endpoints (
server/routes/client_logs.py):GET /api/client-logs/<uuid>/logs– Retrieve client logs with filters (level, limit, since); authenticated (admin_or_higher)GET /api/client-logs/summary– Get log counts by level per client for last 24h; authenticated (admin_or_higher)GET /api/client-logs/monitoring-overview– Aggregated monitoring overview for dashboard clients/statuses; authenticated (admin_or_higher)GET /api/client-logs/recent-errors– System-wide error monitoring; authenticated (admin_or_higher)GET /api/client-logs/test– Infrastructure validation endpoint (no auth required)- Blueprint registered in
server/wsgi.pyasclient_logs_bp
- Dev environment fix: Updated
docker-compose.override.ymllistener service to useworking_dir: /workspaceand direct command path for live code reload
- Database schema: New Alembic migration
- 🖥️ Monitoring Dashboard Integration (2026-03-24):
- Frontend monitoring dashboard (
dashboard/src/monitoring.tsx) is active and wired to monitoring APIs - Superadmin-only route/menu integration completed in
dashboard/src/App.tsx - Added dashboard monitoring API client (
dashboard/src/apiClientMonitoring.ts) for overview and recent errors
- Frontend monitoring dashboard (
- 🐛 Presentation Flags Persistence Fix (2026-03-24):
- Fixed persistence for presentation flags
page_progressandauto_progressacross create/update and detached-occurrence flows - API serialization now reliably returns stored values for presentation behavior fields
- Fixed persistence for presentation flags
- 📡 MQTT Protocol Extensions:
- New log topics:
infoscreen/{uuid}/logs/{error|warn|info}with JSON payload (timestamp, message, context) - New health topic:
infoscreen/{uuid}/healthwith metrics (expected_state, actual_state, health_metrics) - Enhanced heartbeat:
infoscreen/{uuid}/heartbeatnow includescurrent_process,process_pid,process_status,current_event_id - QoS levels: ERROR/WARN logs use QoS 1 (at least once), INFO/health use QoS 0 (fire and forget)
- New log topics:
- 📖 Documentation:
- New file:
CLIENT_MONITORING_SPECIFICATION.md– Comprehensive 20-section technical spec for client-side implementation (MQTT protocol, process monitoring, auto-recovery, payload formats, testing guide) - New file:
CLIENT_MONITORING_IMPLEMENTATION_GUIDE.md– 5-phase implementation guide (database, backend, client watchdog, dashboard UI, testing) - Updated
.github/copilot-instructions.md: Added MQTT topics section, client monitoring integration notes
- New file:
- ✅ Validation:
- End-to-end testing completed: MQTT message → listener → database → API confirmed working
- Test flow: Published message to
infoscreen/{real-uuid}/logs/error→ listener logs showed receipt → database stored entry → test API returned log data - Known client UUIDs validated: 9b8d1856-ff34-4864-a726-12de072d0f77, 7f65c615-5827-4ada-9ac8-4727c2e8ee55, bdbfff95-0b2b-4265-8cc7-b0284509540a
Notes for integrators:
- Tiered logging strategy: ERROR/WARN always centralized (QoS 1), INFO dev-only (QoS 0), DEBUG local-only
- Monitoring dashboard is implemented and consumes
/api/client-logs/monitoring-overview,/api/client-logs/recent-errors, and/api/client-logs/<uuid>/logs - Foreign key constraint prevents logging for non-existent clients (data integrity enforced)
- Migration is idempotent and can be safely rerun after interruption
- Use
GET /api/client-logs/testfor quick infrastructure validation without authentication
2025.1.0-beta.1 (TBD)
- 🔐 User Management & Role-Based Access Control:
- Backend: Implemented comprehensive user management API (
server/routes/users.py) with 6 endpoints (GET, POST, PUT, DELETE users + password reset). - Data model: Extended
Userwith 7 audit/security fields via Alembic migration (4f0b8a3e5c20_add_user_audit_fields.py):last_login_at,last_password_change_at: TIMESTAMP (UTC) for auth event trackingfailed_login_attempts,last_failed_login_at: Security monitoring for brute-force detectionlocked_until: TIMESTAMP placeholder for account lockout (infrastructure in place, not yet enforced)deactivated_at,deactivated_by: Soft-delete audit trail (FK self-reference)
- Role hierarchy: 4-tier privilege escalation (user → editor → admin → superadmin) enforced at API and UI levels:
- Admin cannot see, create, or manage superadmin accounts
- Admin can manage user/editor/admin roles only
- Superadmin can manage all roles including other superadmins
- Auth routes enhanced (
server/routes/auth.py):- Login: Sets
last_login_at, resetsfailed_login_attemptson success; incrementsfailed_login_attemptsandlast_failed_login_aton failure - Password change: Sets
last_password_change_aton both self-service and admin reset - New endpoint:
PUT /api/auth/change-passwordfor self-service password change (all authenticated users; requires current password verification)
- Login: Sets
- User API security:
- Admin cannot reset superadmin passwords
- Self-account protections: cannot change own role/status, cannot delete self
- Admin cannot use password reset endpoint for their own account (backend check enforces self-service requirement)
- All user responses include audit fields in camelCase (lastLoginAt, lastPasswordChangeAt, failedLoginAttempts, deactivatedAt, deactivatedBy)
- Soft-delete pattern: Deactivation by default (sets
deactivated_atanddeactivated_by); hard-delete superadmin-only
- Backend: Implemented comprehensive user management API (
- 🖥️ Frontend User Management:
- New page:
dashboard/src/users.tsx– Full CRUD interface (820 lines) with Syncfusion components - GridComponent: 20 per page (configurable), sortable columns (ID, username, role), custom action button template with role-based visibility
- Statistics cards: Total users, active (non-deactivated), inactive (deactivated) counts
- Dialogs: Create (username/password/role/status), Edit (with self-edit protections), Password Reset (admin only, no current password required), Delete (superadmin only, self-check), Details (read-only audit info with formatted timestamps)
- Role badges: Color-coded display (user: gray, editor: blue, admin: green, superadmin: red)
- Audit information display: last login, password change, last failed login, deactivation timestamps and deactivating user
- Self-protection: Delete button hidden for current user (prevents accidental self-deletion)
- Menu visibility: "Benutzer" sidebar item only visible to admin+ (role-gated in App.tsx)
- New page:
- 💬 Header User Menu:
- Enhanced top-right dropdown with "Passwort ändern" (lock icon), "Profil", and "Abmelden"
- Self-service password change dialog: Available to all authenticated users; requires current password verification, new password min 6 chars, must match confirm field
- Implemented with Syncfusion DropDownButton (
@syncfusion/ej2-react-splitbuttons)
- 🔌 API Client:
- New file:
dashboard/src/apiUsers.ts– Type-safe TypeScript client (143 lines) for user operations - Functions: listUsers(), getUser(), createUser(), updateUser(), resetUserPassword(), deleteUser()
- All functions include proper error handling and camelCase JSON mapping
- New file:
- 📖 Documentation:
- Updated
.github/copilot-instructions.md: Added comprehensive sections on user model audit fields, user management API routes, auth routes, header menu, and user management page implementation - Updated
README.md: Added user management to Key Features, API endpoints (User Management + Authentication sections), Pages Overview, and Security & Authentication sections with RBAC details - Updated
TECH-CHANGELOG.md: Documented all technical changes and integration notes
- Updated
Notes for integrators:
- User CRUD endpoints accept/return all audit fields in camelCase
- Admin password reset (
PUT /api/users/<id>/password) cannot be used for admin's own account; users must use self-service endpoint - Frontend enforces role-gated menu visibility; backend validates all role transitions to prevent privilege escalation
- Soft-delete is default; hard-delete (superadmin-only) requires explicit confirmation
- Audit fields populated automatically on login/logout/password-change/deactivation events
Backend rework (post-release notes; no version bump):
- 🧩 Dev Container hygiene: Remote Containers runs on UI (
remote.extensionKind), removed in-container install to prevent reappearance loops; switchedpostCreateCommandtonpm cifor reproducible dashboard installs;postStartCommandaliases made idempotent. - 🔄 Serialization: Consolidated snake_case→camelCase via
server/serializers.pyfor all JSON outputs; ensured enums/UTC datetimes serialize consistently across routes. - 🕒 Time handling: Normalized naive timestamps to UTC in all back-end comparisons (events, scheduler, groups) and kept ISO strings without
Zin API responses; frontend appendsZ. - 📡 Streaming: Stabilized range-capable endpoint (
/api/eventmedia/stream/<media_id>/<filename>), clarified client handling; scheduler emits basic HEAD-probe metadata (mime_type,size,accept_ranges). - 📅 Recurrence/exceptions: Ensured EXDATE tokens (RFC 5545 UTC) align with occurrence start; detached-occurrence flow confirmed via
POST /api/events/<id>/occurrences/<date>/detach. - 🧰 Routes cleanup: Applied
dict_to_camel_case()beforejsonify()uniformly; verified Session lifecycle consistency (open/commit/close) across blueprints. - 🔄 API Naming Convention Standardization:
- Created
server/serializers.pywithdict_to_camel_case()anddict_to_snake_case()utilities for consistent JSON serialization - Events API refactored:
GET /api/eventsandGET /api/events/<id>now return camelCase JSON (id,subject,startTime,endTime,type,groupId, etc.) instead of PascalCase - Internal event dictionaries use snake_case keys, then converted to camelCase via
dict_to_camel_case()beforejsonify() - Breaking: External API consumers must update field names from PascalCase to camelCase
- Created
- ⏰ UTC Time Handling:
- Standardized datetime handling: Database stores timestamps in UTC (naive timestamps normalized by backend)
- API returns ISO strings without 'Z' suffix:
"2025-11-27T20:03:00" - Frontend appends 'Z' to parse as UTC and displays in user's local timezone via
toLocaleTimeString('de-DE') - All time comparisons use UTC;
date.toISOString()sends UTC back to API
- 🖥️ Dashboard Major Redesign:
- Completely redesigned dashboard with card-based layout for Raumgruppen (room groups)
- Global statistics summary card: total infoscreens, online/offline counts, warning groups
- Filter buttons with dynamic counts: All, Online, Offline, Warnings
- Active event display per group: shows currently playing content with type icon, title, date ("Heute"/"Morgen"/date), and time range
- Health visualization: color-coded progress bars showing online/offline ratio per group
- Expandable client details: shows last alive timestamps with human-readable format ("vor X Min.", "vor X Std.", "vor X Tagen")
- Bulk restart functionality: restart all offline clients in a group
- Manual refresh button with toast notifications
- 15-second auto-refresh interval
- "Nicht zugeordnet" group always appears last in sorted list
- 🎨 Frontend Technical:
- Dashboard (
dashboard/src/dashboard.tsx): Uses Syncfusion ButtonComponent, ToastComponent, and card CSS classes - Appointments page updated to map camelCase API responses to internal PascalCase for Syncfusion compatibility
- Time formatting functions (
formatEventTime,formatEventDate) handle UTC string parsing with 'Z' appending - TypeScript lint errors resolved: unused error variables removed, null safety checks added with optional chaining
- Dashboard (
- 📖 Documentation:
- Updated
.github/copilot-instructions.mdwith comprehensive sections on:- API patterns: JSON serialization, datetime handling conventions
- Frontend patterns: API response format, UTC time parsing
- Dashboard page overview with features
- Conventions & gotchas: datetime and JSON naming guidelines
- Updated
README.mdwith recent changes, API response format section, and dashboard page details
- Updated
Notes for integrators:
- Breaking change: All Events API endpoints now return camelCase field names. Update client code accordingly.
- Frontend must append 'Z' to API datetime strings before parsing:
const utcStr = dateStr.endsWith('Z') ? dateStr : dateStr + 'Z'; new Date(utcStr); - Use
dict_to_camel_case()fromserver/serializers.pyfor any new API endpoints returning JSON - Dev container: prefer
npm ciand UI-only Remote Containers to avoid extension drift in-container.
Component build metadata template (for traceability)
Record component builds under the unified app version when releasing:
Component builds for this release
- API: image tag `ghcr.io/robbstarkaustria/api:<short-sha>` (commit `<sha>`)
- Dashboard: image tag `ghcr.io/robbstarkaustria/dashboard:<short-sha>` (commit `<sha>`)
- Scheduler: image tag `ghcr.io/robbstarkaustria/scheduler:<short-sha>` (commit `<sha>`)
- Listener: image tag `ghcr.io/robbstarkaustria/listener:<short-sha>` (commit `<sha>`)
- Worker: image tag `ghcr.io/robbstarkaustria/worker:<short-sha>` (commit `<sha>`)
This is informational (build metadata) and does not change the user-facing version number.
2025.1.0-alpha.11 (2025-11-05)
- 🗃️ Data model & API:
- Added
muted(Boolean) toEventwith Alembic migration; create/update and GET endpoints now accept, persist, and returnmutedalongsideautoplay,loop, andvolumefor video events. - Video event fields consolidated:
event_media_id,autoplay,loop,volume,muted.
- Added
- 🔗 Streaming:
- Added range-capable streaming endpoint:
GET /api/eventmedia/stream/<media_id>/<filename>(supports byte-range requests 206 for seeking). - Scheduler: Performs a best-effort HEAD probe for video stream URLs and includes basic metadata in the emitted payload (
mime_type,size,accept_ranges). Placeholders added forduration,resolution,bitrate,qualities,thumbnails,checksum.
- Added range-capable streaming endpoint:
- 🖥️ Frontend/Dashboard:
- Settings page refactored to nested tabs with controlled tab selection (
selectedItem) to prevent sub-tab jumps. - Settings → Events → Videos: Added system-wide defaults with load/save via system settings keys:
video_autoplay,video_loop,video_volume,video_muted. - Event modal (CustomEventModal): Exposes per-event video options including “Ton aus” (
muted) and initializes all video fields from system defaults when creating new events. - Academic Calendar (Settings): Merged “Schulferien Import” and “Liste” into a single sub-tab “📥 Import & Liste”.
- Settings page refactored to nested tabs with controlled tab selection (
- 📖 Documentation:
- Updated
README.mdand.github/copilot-instructions.mdfor video payload (incl.muted), streaming endpoint (206), nested Settings tabs, and video defaults keys; clarified client handling ofvideopayloads. - Updated
dashboard/public/program-info.json(user-facing changelog) and bumped version to2025.1.0-alpha.11with corresponding UI/UX notes.
- Updated
Notes for integrators:
- Clients should parse
event_typeand handle the nestedvideopayload, honoringautoplay,loop,volume, andmuted. Use the streaming endpoint with HTTP Range for seeking. - System settings keys for video defaults:
video_autoplay,video_loop,video_volume,video_muted.
2025.1.0-alpha.10 (2025-10-25)
- No new developer-facing changes in this release.
- UI/UX updates are documented in
dashboard/public/program-info.json:- Event modal: Surfaced video options (Autoplay, Loop, Volume).
- FileManager: Increased upload limits (Full-HD); client-side duration validation (max 10 minutes).
2025.1.0-alpha.9 (2025-10-19)
- 🗓️ Events/API:
- Implemented new
webuntisevent type. Event creation now resolves the URL from the system settingsupplement_table_url; returns 400 if unset. - Removed obsolete
webuntis-urlsettings endpoints. UseGET/POST /api/system-settings/supplement-tablefor URL and enabled state (shared for WebUntis/Vertretungsplan). - Initialization defaults: dropped
webuntis_url; updatedsupplement_table_urldescription to “Vertretungsplan / WebUntis”.
- Implemented new
- 🚦 Scheduler payloads:
- Unified Website/WebUntis payload: both emit a nested
websiteobject{ "type": "browser", "url": "…" };event_typeremains eitherwebsiteorwebuntisfor dispatch. - Payloads now include a top-level
event_typestring for all events to aid client dispatch.
- Unified Website/WebUntis payload: both emit a nested
- 🖥️ Frontend/Dashboard:
- Program info updated to
2025.1.0-alpha.13with 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).
- Program info updated to
- 📖 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.mdandREADME.mdfor the unified Website/WebUntis handling and settings usage.
- Added
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_typeand use the corresponding nested payload (presentation,website, …).webuntisandwebsiteshould be handled identically (nestedwebsitepayload).
2025.1.0-alpha.8 (2025-10-18)
- 🛠️ Backend: Seeded presentation defaults (
presentation_interval,presentation_page_progress,presentation_auto_progress) in system settings; applied on event creation. - 🗃️ Data model: Added
page_progressandauto_progressfields toEvent(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 includepage_progressandauto_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.
2025.1.0-alpha.11 (2025-10-16)
- ✨ Settings page: New tab layout (Syncfusion) with role-based visibility – Tabs: 📅 Academic Calendar, 🖥️ Display & Clients, 🎬 Media & Files, 🗓️ Events, ⚙️ System.
- 🛠️ Settings (Technical): API calls now use relative /api paths via the Vite proxy (prevents CORS and double /api).
- 📖 Docs: README updated for settings page (tabs) and system settings API.
2025.1.0-alpha.10 (2025-10-15)
- 🔐 Auth: Login and user management implemented (role-based, persistent sessions).
- 🧩 Frontend: Syncfusion SplitButtons integrated (react-splitbuttons) and Vite config updated for pre-bundling.
- 🐛 Fix: Import error ‘@syncfusion/ej2-react-splitbuttons’ – instructions added to README (optimizeDeps + volume reset).
2025.1.0-alpha.9 (2025-10-14)
- ✨ UI: Unified deletion workflow for appointments – all types (single, single instance, entire series) handled with custom dialogs.
- 🔧 Frontend: Syncfusion RecurrenceAlert and DeleteAlert intercepted and replaced with custom dialogs (including final confirmation for series deletion).
- 📖 Docs: README and Copilot instructions expanded for deletion workflow and dialog handling.
2025.1.0-alpha.8 (2025-10-11)
- 🎨 Theme: Migrated to Syncfusion Material 3; centralized CSS imports in main.tsx
- 🧹 Cleanup: Tailwind CSS completely removed (packages, PostCSS, Stylelint, config files)
- 🧩 Group management: "infoscreen_groups" migrated to Syncfusion components (Buttons, Dialogs, DropDownList, TextBox); improved spacing
- 🔔 Notifications: Unified toast/dialog wording; last alert usage replaced
- 📖 Docs: README and Copilot instructions updated (Material 3, centralized styles, no Tailwind)
2025.1.0-alpha.7 (2025-09-21)
- 🧭 UI: Period selection (Syncfusion) next to group selection; compact layout
- ✅ Display: Badge for existing holiday plan + counter ‘Holidays in view’
- 🛠️ API: Endpoints for academic periods (list, active GET/POST, for_date)
- 📅 Scheduler: By default, no scheduling during holidays; block display like all-day event; black text color
- 📤 Holidays: Upload from TXT/CSV (headless TXT uses columns 2–4)
- 🔧 UX: Switches in a row; dropdown widths optimized
2025.1.0-alpha.6 (2025-09-20)
- 🗓️ NEW: Academic periods system – support for school years, semesters, trimesters
- 🏗️ DATABASE: New 'academic_periods' table for time-based organization
- 🔗 EXTENDED: Events and media can now optionally be linked to an academic period
- 📊 ARCHITECTURE: Fully backward-compatible implementation for gradual rollout
- ⚙️ TOOLS: Automatic creation of standard school years for Austrian schools
2025.1.0-alpha.5 (2025-09-14)
- Backend: Complete redesign of backend handling for group assignments of new clients and steps for changing group assignment.
2025.1.0-alpha.4 (2025-09-01)
- Deployment: Base structure for deployment tested and optimized.
- FIX: Program error when switching view on media page fixed.
2025.1.0-alpha.3 (2025-08-30)
- NEW: Program info page with dynamic data, build info, and changelog.
- NEW: Logout functionality implemented.
- FIX: Sidebar width corrected in collapsed state.
2025.1.0-alpha.2 (2025-08-29)
- INFO: Analysis and display of used open-source libraries.
2025.1.0-alpha.1 (2025-08-28)
- Initial project setup and base structure.