- 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
437 lines
39 KiB
Markdown
437 lines
39 KiB
Markdown
|
||
# 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
|
||
- <20> **Crash detection, auto-recovery, and service_failed monitoring (2026-04-05)**:
|
||
- Added `GET /api/clients/crashed` endpoint: returns active clients with `process_status=crashed` or stale heartbeat beyond grace period, with `crash_reason` field.
|
||
- Added `restart_app` command action alongside existing `reboot_host`/`shutdown_host`; registered in `server/routes/clients.py` with same safety lockout.
|
||
- Scheduler: Added crash auto-recovery loop (feature-flagged via `CRASH_RECOVERY_ENABLED`): scans candidates via `get_crash_recovery_candidates()`, issues `reboot_host` command per client, publishes to primary + compat MQTT topics, updates command lifecycle.
|
||
- Scheduler: Added unconditional command expiry sweep each poll cycle via `sweep_expired_commands()` in `scheduler/db_utils.py`: marks non-terminal `ClientCommand` rows with `expires_at < now` as `expired`.
|
||
- Added `service_failed` topic ingestion in `listener/listener.py`: subscribe to `infoscreen/+/service_failed` on every connect; persist `service_failed_at` and `service_failed_unit` on Client; empty payload (retain clear) ignored.
|
||
- Added `broker_connection` block extraction in health payload handler: persists `mqtt_reconnect_count` and `mqtt_last_disconnect_at` from `infoscreen/{uuid}/health`.
|
||
- Added four new DB columns to `clients` table via migration `b1c2d3e4f5a6`: `service_failed_at`, `service_failed_unit`, `mqtt_reconnect_count`, `mqtt_last_disconnect_at`.
|
||
- Added `GET /api/clients/service_failed` endpoint: lists clients with `service_failed_at` set, ordered by event time desc.
|
||
- Added `POST /api/clients/<uuid>/clear_service_failed` endpoint: clears DB flag and publishes empty retained MQTT message to clear `infoscreen/{uuid}/service_failed`.
|
||
- Monitoring overview API (`GET /api/client-logs/monitoring-overview`) now includes `mqtt_reconnect_count` and `mqtt_last_disconnect_at` per 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`, `ServiceFailedClientsResponse` types; `fetchServiceFailedClients()` and `clearServiceFailed()` API helpers in `dashboard/src/apiClients.ts`.
|
||
- Added `service_failed` topic contract to `MQTT_EVENT_PAYLOAD_GUIDE.md`.
|
||
- <20>🔐 **MQTT auth hardening for server-side services (2026-04-03)**:
|
||
- `listener/listener.py` now uses env-based broker connectivity for host/port and credentials (`MQTT_BROKER_HOST`, `MQTT_BROKER_PORT`, `MQTT_USER`, `MQTT_PASSWORD`) instead of anonymous fixed `mqtt:1883`.
|
||
- `scheduler/scheduler.py` now 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.yml` and `docker-compose.override.yml` now 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 `ClientCommand` in `models/models.py` and Alembic migration `aa12bb34cc56_add_client_commands_table.py`.
|
||
- Upgraded `POST /api/clients/<uuid>/restart` from 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>/shutdown` endpoint 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_safety` when threshold is exceeded.
|
||
- Added command MQTT publish to `infoscreen/{uuid}/commands` (QoS1, non-retained) and temporary legacy restart compatibility publish to `clients/{uuid}/restart`.
|
||
- Added temporary topic compatibility publish to `infoscreen/{uuid}/command` and listener acceptance of `infoscreen/{uuid}/command/ack` to bridge singular/plural naming assumptions.
|
||
- Canonicalized command payload action values to host-level semantics: `reboot_host` and `shutdown_host` (API routes remain `/restart` and `/shutdown` for operator UX compatibility).
|
||
- Added frozen payload validation snippets for integration/client tooling in `implementation-plans/reboot-command-payload-schemas.md` and `implementation-plans/reboot-command-payload-schemas.json`.
|
||
- Listener now subscribes to `infoscreen/{uuid}/commands/ack` and 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`, and `blocked_safety`.
|
||
- Frontend API helper extended in `dashboard/src/apiClients.ts` with `ClientCommand` typing plus command APIs for shutdown and status polling preparation.
|
||
|
||
## 2026.1.0-alpha.16 (2026-04-02)
|
||
- 🐛 **Dashboard holiday banner refactoring and state fix (`dashboard/src/dashboard.tsx`)**:
|
||
- **Motivation — unstable fetch function:** `loadHolidayStatus` had `location.pathname` in its `useCallback` dependency array, causing a new function reference to be created on every navigation event. The `useEffect` depending 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.pathname` from `useCallback` deps (it was unused inside the function body). The callback now has an empty dependency array, making its reference stable across the component lifetime. The `useEffect` is keyed only to the stable callback reference — no spurious re-fires.
|
||
- **Motivation — Syncfusion stale render:** Syncfusion's `MessageComponent` caches 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 `key` prop derived from `${severity}:${text}` to `MessageComponent`. 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.
|
||
- 🗓️ **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.
|
||
- 🚀 **Production startup alignment (`docker-compose.prod.yml`)**:
|
||
- Server startup command now runs `python /app/server/init_academic_periods.py` after migrations and default settings bootstrap.
|
||
- Removes manual post-deploy step to set an active academic period.
|
||
- 🌐 **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`: stable `intent_id` on heartbeat; new `intent_id` only 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`, and `TV_POWER_CANARY_VALIDATION_CHECKLIST.md`.
|
||
- 🗃️ **Holiday data model scoping to academic periods**:
|
||
- Added period scoping for holidays via `SchoolHoliday.academic_period_id` (FK to academic periods) in `models/models.py`.
|
||
- Added Alembic migration `f3c4d5e6a7b8_scope_school_holidays_to_academic_.py` to 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.
|
||
- 🔌 **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/holidays`
|
||
- `PUT /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+region` overlaps (including adjacent ranges) are merged.
|
||
- Different-identity overlaps are treated as conflicts (manual blocked, import skipped with details).
|
||
- 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_id` instead of global holiday sets.
|
||
- Updated event-side recurrence handling (`server/routes/events.py`) to keep EXDATE behavior in sync with period-scoped holidays.
|
||
- 🖥️ **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.
|
||
- 📖 **Documentation & release alignment**:
|
||
- Updated `.github/copilot-instructions.md` with period-scoped holiday conventions, overlap policy, and settings behavior.
|
||
- Refactored root `README.md` to index-style documentation and archived historical implementation docs under `docs/archive/`.
|
||
- Synchronized release line with user-facing version `2026.1.0-alpha.15` in `dashboard/public/program-info.json`.
|
||
|
||
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()` from `groupColors.ts` for 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
|
||
- 🎨 **Ressourcen Styling**:
|
||
- New CSS file: `dashboard/src/ressourcen.css` (178 lines) with modern Material 3 design
|
||
- Fixed CSS lint errors: Converted `rgba()` to modern `rgb()` 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-schedule` before `.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
|
||
- 🔌 **Group Order API**:
|
||
- New backend endpoints in `server/routes/groups.py`:
|
||
- `GET /api/groups/order` – Retrieve saved group display order (returns JSON with `order` array of group IDs)
|
||
- `POST /api/groups/order` – Persist group display order (accepts JSON with `order` array)
|
||
- Order persistence: Stored in `system_settings` table with key `group_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
|
||
- 🖥️ **Frontend Technical**:
|
||
- State management: React hooks with unused setters removed (setTimelineView, setViewDate) to resolve lint warnings
|
||
- TypeScript: Changed `let` to `const` for 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 + 100px` for header
|
||
- 📖 **Documentation**:
|
||
- Updated `.github/copilot-instructions.md`:
|
||
- Added Ressourcen page to "Recent changes" section (January 2026)
|
||
- Added `ressourcen.tsx` and `ressourcen.css` to "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/order` to Core Resources API section
|
||
- Bumped version in `dashboard/public/program-info.json` to `2026.1.0-alpha.14` with user-facing changelog
|
||
|
||
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_logs` table: 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 `clients` table: `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: `ClientLog` with foreign key to `Client` (CASCADE on delete)
|
||
- Extended `Client` model with 7 health monitoring fields
|
||
- 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 `ClientLog` entries, 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_id` from payload
|
||
- 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.py` as `client_logs_bp`
|
||
- Dev environment fix: Updated `docker-compose.override.yml` listener service to use `working_dir: /workspace` and direct command path for live code reload
|
||
- 🖥️ **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
|
||
- 🐛 **Presentation Flags Persistence Fix (2026-03-24)**:
|
||
- Fixed persistence for presentation flags `page_progress` and `auto_progress` across create/update and detached-occurrence flows
|
||
- API serialization now reliably returns stored values for presentation behavior fields
|
||
- 📡 **MQTT Protocol Extensions**:
|
||
- New log topics: `infoscreen/{uuid}/logs/{error|warn|info}` with JSON payload (timestamp, message, context)
|
||
- New health topic: `infoscreen/{uuid}/health` with metrics (expected_state, actual_state, health_metrics)
|
||
- Enhanced heartbeat: `infoscreen/{uuid}/heartbeat` now includes `current_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)
|
||
- 📖 **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
|
||
- ✅ **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/test` for 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 `User` with 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 tracking
|
||
- `failed_login_attempts`, `last_failed_login_at`: Security monitoring for brute-force detection
|
||
- `locked_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`, resets `failed_login_attempts` on success; increments `failed_login_attempts` and `last_failed_login_at` on failure
|
||
- Password change: Sets `last_password_change_at` on both self-service and admin reset
|
||
- New endpoint: `PUT /api/auth/change-password` for self-service password change (all authenticated users; requires current password verification)
|
||
- 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_at` and `deactivated_by`); hard-delete superadmin-only
|
||
- 🖥️ **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)
|
||
- 💬 **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
|
||
- 📖 **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
|
||
|
||
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; switched `postCreateCommand` to `npm ci` for reproducible dashboard installs; `postStartCommand` aliases made idempotent.
|
||
- 🔄 Serialization: Consolidated snake_case→camelCase via `server/serializers.py` for 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 `Z` in API responses; frontend appends `Z`.
|
||
- 📡 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()` before `jsonify()` uniformly; verified Session lifecycle consistency (open/commit/close) across blueprints.
|
||
- 🔄 **API Naming Convention Standardization**:
|
||
- Created `server/serializers.py` with `dict_to_camel_case()` and `dict_to_snake_case()` utilities for consistent JSON serialization
|
||
- Events API refactored: `GET /api/events` and `GET /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()` before `jsonify()`
|
||
- **Breaking**: External API consumers must update field names from PascalCase to camelCase
|
||
- ⏰ **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
|
||
- 📖 **Documentation**:
|
||
- Updated `.github/copilot-instructions.md` with 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.md` with recent changes, API response format section, and dashboard page details
|
||
|
||
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()` from `server/serializers.py` for any new API endpoints returning JSON
|
||
- Dev container: prefer `npm ci` and 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) to `Event` with Alembic migration; create/update and GET endpoints now accept, persist, and return `muted` alongside `autoplay`, `loop`, and `volume` for video events.
|
||
- Video event fields consolidated: `event_media_id`, `autoplay`, `loop`, `volume`, `muted`.
|
||
- 🔗 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 for `duration`, `resolution`, `bitrate`, `qualities`, `thumbnails`, `checksum`.
|
||
- 🖥️ 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”.
|
||
- 📖 Documentation:
|
||
- Updated `README.md` and `.github/copilot-instructions.md` for video payload (incl. `muted`), streaming endpoint (206), nested Settings tabs, and video defaults keys; clarified client handling of `video` payloads.
|
||
- Updated `dashboard/public/program-info.json` (user-facing changelog) and bumped version to `2025.1.0-alpha.11` with corresponding UI/UX notes.
|
||
|
||
Notes for integrators:
|
||
- Clients should parse `event_type` and handle the nested `video` payload, honoring `autoplay`, `loop`, `volume`, and `muted`. 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 `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.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_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.
|
||
|
||
---
|
||
|
||
## 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.
|