# Homeschool Dashboard A self-hosted web app for managing homeschool schedules, tracking daily learning sessions, and logging activities. Features a full-screen TV dashboard with live timers and real-time updates via WebSockets. --- ## Features - **TV Dashboard** — Full-screen display for the living room TV. Shows the current subject, countdown timer, day progress bar, activity options, and the schedule block list. Updates live without page refresh via WebSocket. - **Morning Routine** — Define a list of morning routine items in Admin. They appear in the TV dashboard Activities panel during the "Good Morning" greeting before the first block starts, then switch to subject-specific activities once a block begins. - **Break Time** — Each schedule block can optionally include a break at the end. Enable the checkbox and set a duration (in minutes) when building a block in Admin. Once the block's main timer is done, a **Break Time** section appears on the Dashboard with its own **Start / Pause / Resume / Reset** controls — the break does not start automatically. While break is active the TV left column switches to an amber break badge and countdown timer, and the center column shows the configurable **Break Activities** list instead of subject options. - **Break Activities** — A global list of break-time activities (e.g. "Get a snack", "Go outside") managed in Admin → Break Activities, using the same add/edit/delete interface as Morning Routine. These items are shown on the TV during any active break. - **Day Progress Bar** — Both the TV dashboard and the parent dashboard display a progress bar showing how far through the day the child is. Progress is calculated from total scheduled block time vs. remaining block time — not wall-clock time — so it advances only as blocks are actively worked. On the TV the bar is labeled **🟢 Start** and **Finish 🏁**. On the parent dashboard the left label shows the scheduled start time of the first block and the right label shows a live-updating **estimated finish time** computed as the current time plus all remaining block time and break time for incomplete blocks. - **Schedule Builder** — Create named schedule templates with time blocks assigned to subjects. Each block supports an optional custom duration override, label, and break time setting. Managed inside the Admin page. - **Daily Sessions** — Start a school day against a schedule template. Click any block in the list to select it as the current block. Use the **Start** button to begin timing, **Pause** to stop, **Resume** to continue from where you left off, and **Reset** to clear the elapsed time and restart the timer from zero. Elapsed time per block is remembered across switches, so returning to a block picks up where it left off. - **Block Timer Remaining** — Each block in the schedule list shows time remaining (allocated duration minus elapsed), counting down live on both the parent dashboard and the TV sidebar. Shows "< 1 min" when under a minute, and "Done!" when the full duration is elapsed. - **Activity Log** — Automatically records every timer event (day started, block start/pause/resume/complete/skip/reset, break start/pause/resume/reset) and every strike change as a timestamped timeline. Includes which schedule template was used. Supports manual notes with free text. Browse and filter history by child and date. - **Behavior Tracking (Strikes)** — Issue up to 3 strikes per child from the Dashboard. Strike additions and removals are logged in the activity log with a timestamp. Strike count is shown on the TV dashboard and resets automatically when a new school day begins. - **Timezone Support** — Set your local timezone in Admin → Settings. All activity log timestamps display in your timezone, including the TV dashboard clock. - **Multi-Child Support** — Manage multiple students under one parent account, each with their own color, schedule, and history. - **JWT Authentication** — Secure parent login with access tokens and httpOnly refresh cookies. TV dashboard is public (no login required). - **Super Admin Panel** — A separate admin interface (at `/super-admin`) for site-wide management. Log in with a dedicated admin username and password (set in `.env`). Lists all registered parent accounts and allows impersonating any user — switching into their session to view and manage their data. An impersonation banner is shown at the top of the screen with a one-click "Exit to Admin Panel" button. --- ## Tech Stack | Layer | Technology | |-------|-----------| | Frontend | Vue 3 + Vite + Pinia + Vue Router | | Frontend server | nginx (Docker) | | Backend API | FastAPI (Python 3.12) | | Real-time | WebSockets via FastAPI | | Database | MySQL 8 | | ORM | SQLAlchemy 2.0 (async) | | Auth | JWT — python-jose + passlib/bcrypt | | Orchestration | Docker Compose | --- ## Project Structure ``` homeschool/ ├── docker-compose.yml # Stack definition (3 services: db, backend, frontend) ├── .env.example # Environment variable template │ ├── backend/ │ ├── Dockerfile │ ├── requirements.txt │ └── app/ │ ├── main.py # FastAPI app entry point + table auto-creation │ ├── config.py # Settings (reads from .env) │ ├── database.py # Async SQLAlchemy engine │ ├── dependencies.py # Auth dependencies (get_current_user) │ ├── auth/jwt.py # Token creation, password hashing │ ├── models/ # SQLAlchemy ORM models │ │ ├── child.py │ │ ├── subject.py # Subject + SubjectOption │ │ ├── schedule.py # ScheduleTemplate + ScheduleBlock (incl. break fields) │ │ ├── session.py # DailySession + TimerEvent │ │ ├── activity.py # ActivityLog (manual notes) │ │ ├── morning_routine.py# MorningRoutineItem │ │ ├── break_activity.py # BreakActivityItem │ │ └── strike.py # StrikeEvent (strike history) │ ├── schemas/ # Pydantic request/response schemas │ ├── routers/ # API route handlers │ │ ├── auth.py │ │ ├── children.py │ │ ├── subjects.py │ │ ├── schedules.py │ │ ├── sessions.py # Timer actions + break timer events │ │ ├── logs.py # Timeline + strike events │ │ ├── morning_routine.py │ │ ├── break_activity.py # Break activities CRUD │ │ ├── dashboard.py # Public snapshot endpoint (TV) │ │ ├── admin.py # Super admin: login, user list, impersonation │ │ └── users.py │ ├── utils/ │ │ └── timer.py # Elapsed-time computation for block and break timers │ └── websocket/manager.py # WebSocket connection manager │ └── frontend/ ├── Dockerfile # Multi-stage: Node build → nginx serve ├── nginx.conf # Proxy /api/ and /ws/ to backend └── src/ ├── composables/ │ ├── useApi.js # Axios with auto token-refresh │ └── useWebSocket.js # Auto-reconnecting WebSocket ├── stores/ # Pinia: auth, children, schedule, superAdmin ├── views/ # LoginView, TVView, DashboardView, LogView, AdminView, │ # SuperAdminLoginView, SuperAdminView └── components/ # TimerDisplay, ScheduleBlock, NavBar, etc. ``` --- ## Getting Started ### Prerequisites - [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed - Port `8054` available on the host (or change it in `docker-compose.yml`) ### 1. Clone the repo ```bash git clone https://git.chns.tech/CooperandGoodman/homeschool.git cd homeschool ``` ### 2. Create your `.env` file ```bash cp .env.example .env ``` Open `.env` and fill in the values: ```env MYSQL_ROOT_PASSWORD=your_secure_root_password MYSQL_DATABASE=homeschool MYSQL_USER=homeschool MYSQL_PASSWORD=your_secure_db_password # Generate with: openssl rand -hex 32 SECRET_KEY=your_generated_secret_key ALGORITHM=HS256 ACCESS_TOKEN_EXPIRE_MINUTES=30 REFRESH_TOKEN_EXPIRE_DAYS=30 # Your host IP or domain (no trailing slash) CORS_ORIGINS=http://localhost:8054 # Super admin credentials (for /super-admin) ADMIN_USERNAME=admin ADMIN_PASSWORD=change_me_admin_password ``` ### 3. Build and start ```bash docker compose up --build ``` The first build takes a few minutes (npm install + pip install). On subsequent starts it's fast. ### 4. Register your parent account Open **http://localhost:8054/login** and register. This creates your admin account. ### 5. Set up your data (in order) 1. **Admin** (`/admin`) → Add each child, pick a color 2. **Admin** → Add subjects (Math, Reading, Science, etc.) with emoji icons and colors. Add activity options to each subject — they appear on the TV dashboard during that block. 3. **Admin** → Add **Morning Routine** items — these show on the TV during the greeting before the first block starts. 4. **Admin** → Add **Break Activities** items — these show on the TV center panel whenever a break is active. 5. **Admin** → Scroll to **Settings** and select your local timezone 6. **Admin** → Scroll to **Schedules** → Create a schedule template, set school day hours, add time blocks assigned to subjects. For any block that should include a break, check **Break** and enter the break duration in minutes. 6. **Dashboard** (`/dashboard`) → Click "Start Day", choose a template 7. **TV** → Open `http://your-lan-ip:8054/tv/1` on the living room TV (replace `1` with the child's ID) --- ## Usage ### Parent Views (require login) | URL | Description | |-----|-------------| | `/dashboard` | Overview, start/stop sessions, select and time blocks, issue behavior strikes | | `/logs` | Browse timer and strike event history and manual notes; filter by child and date | | `/admin` | Manage children, subjects (with activity options), morning routine, break activities, schedule templates, and account settings | ### Super Admin Views | URL | Description | |-----|-------------| | `/super-admin/login` | Log in with the `ADMIN_USERNAME` / `ADMIN_PASSWORD` from `.env` | | `/super-admin` | List all registered parent accounts and impersonate any user | While impersonating, a yellow banner appears at the top of every page showing who you're viewing as, with an **Exit to Admin Panel** button to return. ### Dashboard Controls While a session is active, clicking a block in the schedule list **selects** it as the current block without starting the timer. The action buttons then provide explicit control: **Main block timer:** | Button | Condition | Action | |--------|-----------|--------| | **Start** | Block selected, never timed | Begin counting from zero | | **Resume** | Block was previously paused | Continue from saved elapsed time | | **Pause** | Timer is running | Stop counting, save elapsed time | | **Reset** | Any current block | Clear elapsed to zero and restart timer immediately | | **End Day** | Session active | Mark the session complete | **Break timer** (shown when the current block has Break Time enabled): | Button | Condition | Action | |--------|-----------|--------| | **Start Break** | Break not yet started | Begin break countdown from zero | | **Pause** | Break timer running | Stop break countdown | | **Resume** | Break timer paused | Continue break countdown | | **Reset** | Break in progress | Clear break elapsed to zero and restart immediately | ### TV Dashboard (no login) | URL | Description | |-----|-------------| | `/tv/:childId` | Full-screen display — greeting + morning routine, current block timer with subject activities, break timer with break activities, day progress bar (🟢 Start → Finish 🏁), schedule sidebar | Point a browser on the living room TV at `http://your-lan-ip:8054/tv/1`. The page connects via WebSocket and updates automatically when a parent starts/stops/advances the timer from the Dashboard. ### API Documentation FastAPI auto-generates interactive API docs: - **Swagger UI** — `http://localhost:8054/api/docs` - **ReDoc** — `http://localhost:8054/api/redoc` --- ## Schema Migrations The app automatically creates all database tables on startup via SQLAlchemy's `create_all`. Additive column changes are applied with idempotent `ALTER TABLE` statements in the startup lifespan, so upgrading to a new version is safe — just rebuild and restart: ```bash docker compose build docker compose up -d ``` No separate migration tool or manual steps are required. --- ## WebSocket Events The TV dashboard connects to `ws://host/ws/{child_id}` and receives JSON events: | Event | Triggered by | Key payload fields | |-------|-------------|---------| | `session_update` | Session start | Full session snapshot including blocks, morning routine, break activities, and day times | | `start` | Block timer started | `block_id`, `current_block_id`, `block_elapsed_seconds`, `prev_block_id`, `prev_block_elapsed_seconds` | | `pause` | Block timer paused | `block_id`, `current_block_id` | | `resume` | Block timer resumed | `block_id`, `current_block_id` | | `select` | Block selected (not started) | `block_id`, `current_block_id`, `block_elapsed_seconds`, `prev_block_id`, `prev_block_elapsed_seconds` | | `reset` | Block timer reset to zero | `block_id`, `current_block_id`, `block_elapsed_seconds` (always 0) | | `break_start` | Break timer started | `block_id`, `current_block_id`, `break_elapsed_seconds` | | `break_pause` | Break timer paused | `block_id`, `current_block_id` | | `break_resume` | Break timer resumed | `block_id`, `current_block_id` | | `break_reset` | Break timer reset to zero | `block_id`, `current_block_id`, `break_elapsed_seconds` (always 0) | | `complete` | Session ended | `is_active: false` | | `strikes_update` | Strike issued/cleared | `strikes` | `block_elapsed_seconds` on `start` and `select` events carries the authoritative accumulated elapsed time for that block (all previous intervals, respecting any prior resets), so every client — including the TV — can restore the correct timer offset without a local cache. `prev_block_id` and `prev_block_elapsed_seconds` on `start` and `select` events carry the saved elapsed for the block being left, so the TV sidebar immediately shows the correct remaining time for that block. Break timer events (`break_*`) are stored as `TimerEvent` records alongside regular timer events but are computed and broadcast independently — they do not affect block selection, implicit pauses, or elapsed time for the main block timer. --- ## Environment Variables Reference | Variable | Required | Description | |----------|----------|-------------| | `MYSQL_ROOT_PASSWORD` | Yes | MySQL root password | | `MYSQL_DATABASE` | Yes | Database name (default: `homeschool`) | | `MYSQL_USER` | Yes | App database user | | `MYSQL_PASSWORD` | Yes | App database password | | `SECRET_KEY` | Yes | JWT signing key — generate with `openssl rand -hex 32` | | `ALGORITHM` | No | JWT algorithm (default: `HS256`) | | `ACCESS_TOKEN_EXPIRE_MINUTES` | No | Access token lifetime (default: `30`) | | `REFRESH_TOKEN_EXPIRE_DAYS` | No | Refresh token lifetime (default: `30`) | | `CORS_ORIGINS` | No | Comma-separated allowed origins (default: `http://localhost:8054`) | | `ADMIN_USERNAME` | No | Super admin login username (default: `admin`) | | `ADMIN_PASSWORD` | No | Super admin login password (default: `change_me_admin_password`) | > **Note:** `ADMIN_USERNAME` and `ADMIN_PASSWORD` must be set in `.env` **and** listed in the `backend` service's `environment` block in `docker-compose.yml`. Changing them in `.env` alone is not sufficient — the backend container reads them as environment variables, not from the file directly. --- ## Stopping and Restarting ```bash # Stop containers (data preserved in Docker volume) docker compose down # Stop and wipe the database volume (full reset) docker compose down -v # Restart without rebuilding docker compose up ```