README: Add System Settings API endpoints; describe new tabbed Settings layout with role gating; add Vite dev proxy tip to use relative /api paths. Copilot instructions: Note SystemSetting key–value store in data model; document system_settings.py (CRUD + supplement-table convenience endpoint); reference apiSystemSettings.ts; note defaults seeding via init_defaults.py. Program Info: Bump version to 2025.1.0-alpha.11; changelog explicitly tied to the Settings page (Events tab: supplement-table URL moved; Academic Calendar: set active period; proxy note); README docs mention. No functional changes to API or UI code in this commit; documentation and program info only.
17 KiB
Copilot instructions for infoscreen_2025
Purpose
These instructions tell Copilot Chat how to reason about this codebase. Prefer explanations and refactors that align with these structures.
Use this as your shared context when proposing changes. Keep edits minimal and match existing patterns referenced below.
Big picture
- Multi-service app orchestrated by Docker Compose.
- API: Flask + SQLAlchemy (MariaDB), in
server/exposed on :8000 (health:/health). - Dashboard: React + Vite in
dashboard/, dev on :5173, served via Nginx in prod. - MQTT broker: Eclipse Mosquitto, config in
mosquitto/config/mosquitto.conf. - Listener: MQTT consumer handling discovery + heartbeats in
listener/listener.py. - Scheduler: Publishes active events (per group) to MQTT retained topics in
scheduler/scheduler.py. - Nginx: Reverse proxy routes
/api/*and/screenshots/*to API; everything else to dashboard (nginx.conf).
- API: Flask + SQLAlchemy (MariaDB), in
Service boundaries & data flow
-
Database connection string is passed as
DB_CONN(mysql+pymysql) to Python services.- API builds its engine in
server/database.py(loads.envonly in development). - Scheduler loads
DB_CONNinscheduler/db_utils.py. - Listener also creates its own engine for writes to
clients.
- API builds its engine in
-
MQTT topics (paho-mqtt v2, use Callback API v2):
- Discovery:
infoscreen/discovery(JSON includesuuid, hw/ip data). ACK toinfoscreen/{uuid}/discovery_ack. Seelistener/listener.py. - Heartbeat:
infoscreen/{uuid}/heartbeatupdatesClient.last_alive(UTC). - Event lists (retained):
infoscreen/events/{group_id}fromscheduler/scheduler.py. - Per-client group assignment (retained):
infoscreen/{uuid}/group_idviaserver/mqtt_helper.py.
- Discovery:
-
Screenshots: server-side folders
server/received_screenshots/andserver/screenshots/; Nginx exposes/screenshots/{uuid}.jpgviaserver/wsgi.pyroute. -
Presentation conversion (PPT/PPTX/ODP → PDF):
- Trigger: on upload in
server/routes/eventmedia.pyfor media typesppt|pptx|odp(compute sha256, upsertConversion, enqueue job). - Worker: RQ worker runs
server.worker.convert_event_media_to_pdf, calls Gotenberg LibreOffice endpoint, writes toserver/media/converted/. - Services: Redis (queue) and Gotenberg added in compose; worker service consumes the
conversionsqueue. - Env:
REDIS_URL(defaultredis://redis:6379/0),GOTENBERG_URL(defaulthttp://gotenberg:3000). - Endpoints:
POST /api/conversions/<media_id>/pdf(ensure/enqueue),GET /api/conversions/<media_id>/status,GET /api/files/converted/<path>(serve PDFs). - Storage: originals under
server/media/…, outputs underserver/media/converted/(prod compose mounts a shared volume for this path).
- Trigger: on upload in
Data model highlights (see models/models.py)
-
Enums:
EventType(presentation, website, video, message, webuntis),MediaType(file/website types), andAcademicPeriodType(schuljahr, semester, trimester). -
Tables:
clients,client_groups,events,event_media,users,academic_periods,school_holidays. -
System settings:
system_settingskey–value store viaSystemSettingfor global configuration (e.g., WebUntis/Vertretungsplan supplement-table). Managed through routes inserver/routes/system_settings.py. -
Academic periods:
academic_periodstable supports educational institution cycles (school years, semesters). Events and media can be optionally linked viaacademic_period_id(nullable for backward compatibility). -
Times are stored as timezone-aware; treat comparisons in UTC (see scheduler and routes/events).
-
Conversions:
- Enum
ConversionStatus:pending,processing,ready,failed. - Table
conversions:id,source_event_media_id(FK→event_media.idondelete CASCADE),target_format,target_path,status,file_hash(sha256),started_at,completed_at,error_message. - Indexes:
(source_event_media_id, target_format),(status, target_format); Unique:(source_event_media_id, target_format, file_hash).
- Enum
API patterns
- Blueprints live in
server/routes/*and are registered inserver/wsgi.pywith/api/...prefixes. - Session usage: instantiate
Session()per request, commit when mutating, and alwayssession.close()before returning. - Examples:
- Clients:
server/routes/clients.pyincludes bulk group updates and MQTT sync (publish_multiple_client_groups). - Groups:
server/routes/groups.pycomputes “alive” using a grace period that varies byENV. - Events:
server/routes/events.pyserializes enum values to strings and normalizes times to UTC. - Media:
server/routes/eventmedia.pyimplements a simple file manager API rooted atserver/media/. - System settings:
server/routes/system_settings.pyexposes key–value CRUD (/api/system-settings) and a convenience endpoint for WebUntis/Vertretungsplan supplement-table:GET/POST /api/system-settings/supplement-table(admin+). - Academic periods:
server/routes/academic_periods.pyexposes:GET /api/academic_periods— list all periodsGET /api/academic_periods/active— currently active periodPOST /api/academic_periods/active— set active period (deactivates others)GET /api/academic_periods/for_date?date=YYYY-MM-DD— period covering given date
- Clients:
Frontend patterns (dashboard)
-
Vite React app; proxies
/apiand/screenshotsto API in dev (vite.config.ts). -
Uses Syncfusion components; Vite config pre-bundles specific packages to avoid alias issues.
-
Environment:
VITE_API_URLprovided at build/run; in dev compose, proxy handles/apiso local fetches can use relative/api/...paths. -
Theming: Syncfusion Material 3 theme is used. All component CSS is imported centrally in
dashboard/src/main.tsx(base, navigations, buttons, inputs, dropdowns, popups, kanban, grids, schedule, filemanager, notifications, layouts, lists, calendars, splitbuttons, icons). Tailwind CSS has been removed. -
Scheduler (appointments page): top bar includes Group and Academic Period selectors (Syncfusion DropDownList). Selecting a period calls
POST /api/academic_periods/active, moves the calendar to today’s month/day within the period year, and refreshes a right-aligned indicator row showing:- Holidays present in the current view (count)
- Period label (display_name or name) with a badge indicating whether any holidays exist in that period (overlap check)
-
Recurrence & holidays (latest):
-
Backend stores holiday skips in
EventExceptionand emitsRecurrenceException(EXDATE) for master events inGET /api/events. EXDATE tokens are formatted in RFC 5545 compact form (yyyyMMddTHHmmssZ) and correspond to each occurrence start time (UTC). Syncfusion uses these to exclude holiday instances reliably. -
Frontend lets Syncfusion handle all recurrence patterns natively (no client-side expansion). Scheduler field mappings include
recurrenceID,recurrenceRule, andrecurrenceExceptionso series and edited occurrences are recognized correctly. -
Event deletion: All event types (single, single-in-series, entire series) are handled with custom dialogs. The frontend intercepts Syncfusion's built-in RecurrenceAlert and DeleteAlert popups to provide a unified, user-friendly deletion flow:
- Single (non-recurring) event: deleted directly after confirmation.
- Single occurrence of a recurring series: user can delete just that instance.
- Entire recurring series: user can delete all occurrences after a final custom confirmation dialog.
- Detached occurrences (edited/broken out): treated as single events.
-
Single occurrence editing: Users can detach individual occurrences from recurring series. The frontend hooks
actionComplete/onActionCompletedwithrequestType='eventChanged'to persist changes: it callsPOST /api/events/<id>/occurrences/<date>/detachfor single-occurrence edits andPUT /api/events/<id>for series or single events as appropriate. The backend createsEventExceptionand a standaloneEventwithout modifying the master beyond EXDATEs. -
UI: Events with
SkipHolidaysrender 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. -
Program info page (
dashboard/src/programminfo.tsx):- Loads data from
dashboard/public/program-info.json(app name, version, build info, tech stack, changelog). - Uses Syncfusion card classes (
e-card,e-card-header,e-card-title,e-card-content) for consistent styling. - Changelog is paginated with
PagerComponent(from@syncfusion/ej2-react-grids), default page size 5; adjustpageSizeor add a selector as needed.
- Loads data from
-
Groups page (
dashboard/src/infoscreen_groups.tsx):- Migrated to Syncfusion inputs and popups: Buttons, TextBox, DropDownList, Dialog; Kanban remains for drag/drop.
- Unified toast/dialog wording; replaced legacy alerts with toasts; spacing handled via inline styles to avoid Tailwind dependency.
-
Header user menu (top-right):
- Shows current username and role; click opens a menu with “Profil” and “Abmelden”.
- Implemented with Syncfusion DropDownButton (
@syncfusion/ej2-react-splitbuttons). - “Abmelden” navigates to
/logout; the page invokes backend logout and redirects to/login.
-
Settings page (
dashboard/src/settings.tsx):- Structure: Syncfusion TabComponent with role-gated tabs
- 📅 Academic Calendar (all users)
- School Holidays: CSV/TXT import and list
- Academic Periods: select and set active period (uses
/api/academic_periodsroutes)
- 🖥️ Display & Clients (admin+)
- Default Settings: placeholders for heartbeat, screenshots, defaults
- Client Configuration: quick links to Clients and Groups pages
- 🎬 Media & Files (admin+)
- Upload Settings: placeholders for limits and types
- Conversion Status: placeholder for conversions overview
- 🗓️ Events (admin+)
- WebUntis / Vertretungsplan: system-wide supplement table URL with enable/disable, save, and preview; persists via
/api/system-settings/supplement-table - Other event types (presentation, website, video, message, other): placeholders for defaults
- WebUntis / Vertretungsplan: system-wide supplement table URL with enable/disable, save, and preview; persists via
- ⚙️ System (superadmin)
- Organization Info and Advanced Configuration placeholders
- 📅 Academic Calendar (all users)
- Role gating: Admin/Superadmin tabs are hidden if the user lacks permission; System is superadmin-only
- API clients use relative
/api/...URLs so Vite dev proxy handles requests without CORS issues. The settings UI calls are centralized indashboard/src/apiSystemSettings.ts.
- Structure: Syncfusion TabComponent with role-gated tabs
-
User dropdown technical notes:
- Dependencies:
@syncfusion/ej2-react-splitbuttonsand@syncfusion/ej2-splitbuttonsmust be installed. - Vite: add both to
optimizeDeps.includeinvite.config.tsto avoid import-analysis errors. - Dev containers: when
node_modulesis a named volume, recreate the dashboard node_modules volume after adding dependencies sonpm ciruns inside the container.
- Dependencies:
Note: Syncfusion usage in the dashboard is already documented above; if a UI for conversion status/downloads is added later, link its routes and components here.
Local development
- Compose: development is
docker-compose.yml+docker-compose.override.yml.- API (dev):
server/Dockerfile.devwith debugpy on 5678, Flask appwsgi:appon :8000. - Dashboard (dev):
dashboard/Dockerfile.devexposes :5173 and waits for API viadashboard/wait-for-backend.sh. - Mosquitto: allows anonymous in dev; WebSocket on :9001.
- API (dev):
- Common env vars:
DB_CONN,DB_USER,DB_PASSWORD,DB_HOST=db,DB_NAME,ENV,MQTT_USER,MQTT_PASSWORD.- Alembic: prod compose runs
alembic ... upgrade headandserver/init_defaults.pybefore gunicorn. - Local dev: prefer
python server/initialize_database.pyfor one-shot setup (migrations + defaults + academic periods). - Defaults:
server/init_defaults.pyseeds initial system settings likesupplement_table_urlandsupplement_table_enabledif missing. server/init_academic_periods.pyremains available to (re)seed school years.
- Alembic: prod compose runs
Production
docker-compose.prod.ymluses prebuilt images (ghcr.io/robbstarkaustria/*).- Nginx serves dashboard and proxies API; TLS certs expected in
certs/and mounted to/etc/nginx/certs.
Environment variables (reference)
- DB_CONN — Preferred DB URL for services. Example:
mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME} - DB_USER, DB_PASSWORD, DB_NAME, DB_HOST — Used to assemble DB_CONN in dev if missing; inside containers
DB_HOST=db. - ENV —
developmentorproduction; in development,server/database.pyloads.env. - MQTT_BROKER_HOST, MQTT_BROKER_PORT — Defaults
mqttand1883; MQTT_USER/MQTT_PASSWORD optional (dev often anonymous per Mosquitto config). - VITE_API_URL — Dashboard build-time base URL (prod); in dev the Vite proxy serves
/apitoserver:8000. - HEARTBEAT_GRACE_PERIOD_DEV / HEARTBEAT_GRACE_PERIOD_PROD — Groups “alive” window (defaults ~15s dev / 180s prod).
- REFRESH_SECONDS — Optional scheduler republish interval;
0disables periodic refresh.
Conventions & gotchas
- Always compare datetimes in UTC; some DB values may be naive—normalize before comparing (see
routes/events.py). - Use retained MQTT messages for state that clients must recover after reconnect (events per group, client group_id).
- In-container DB host is
db; do not uselocalhostinside services. - 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:
- Create a Blueprint in
server/routes/..., - Register it in
server/wsgi.py, - Manage
Session()lifecycle, and - Return JSON-safe values (serialize enums and datetimes).
- Create a Blueprint in
- When extending media types, update
MediaTypeand any logic ineventmediaand dashboard that depends on it. - Academic periods: Events/media can be optionally associated with periods for educational organization. Only one period should be active at a time (
is_active=True). - Initialization scripts: legacy DB init scripts were removed; use Alembic and
initialize_database.pygoing forward.
Recurrence & holidays: conventions
- Do not pre-expand recurrences on the backend. Always send master events with
RecurrenceRule+RecurrenceException. - Ensure EXDATE tokens are RFC 5545 timestamps (
yyyyMMddTHHmmssZ) matching the occurrence start time (UTC) so Syncfusion can exclude them natively. - When
skip_holidaysor recurrence changes, regenerateEventExceptionrows soRecurrenceExceptionstays in sync. - Single occurrence detach: Use
POST /api/events/<id>/occurrences/<date>/detachto create standalone events and add EXDATE entries without modifying master events. The frontend persists edits viaactionComplete(requestType='eventChanged').
Quick examples
- Add client description persists to DB and publishes group via MQTT: see
PUT /api/clients/<uuid>/descriptioninroutes/clients.py. - Bulk group assignment emits retained messages for each client:
PUT /api/clients/group. - Listener heartbeat path:
infoscreen/<uuid>/heartbeat→ setsclients.last_alive.
Questions or unclear areas? Tell us if you need: exact devcontainer debugging steps, stricter Alembic workflow, or a seed dataset beyond init_defaults.py.
Academic Periods System
- Purpose: Organize events and media by educational cycles (school years, semesters, trimesters).
- Design: Fully backward compatible - existing events/media continue to work without period assignment.
- Usage: New events/media can optionally reference
academic_period_idfor better organization and filtering. - Constraints: Only one period can be active at a time; use
init_academic_periods.pyfor Austrian school year setup. - UI Integration: The dashboard highlights the currently selected period and whether a holiday plan exists within that date range. Holiday linkage currently uses date overlap with
school_holidays; an explicitacademic_period_idonschool_holidayscan be added later if tighter association is required.
Changelog Style Guide (Program info)
- Source:
dashboard/public/program-info.json; newest entry first - Fields per release:
version,date(YYYY-MM-DD),changes(array of short bullets) - Tone: concise, user-facing; German wording; area prefixes allowed (e.g., “UI: …”, “API: …”)
- Categories via emoji or words: Added (🆕/✨), Changed (🛠️), Fixed (✅/🐛), Removed (🗑️), Security (🔒), Deprecated (⚠️)
- Breaking changes must be prefixed with
BREAKING: - Keep ≤ 8–10 bullets; summarize or group micro-changes
- JSON hygiene: valid JSON, no trailing commas, don’t edit historical entries except typos