# Connections Dashboard with Background Health Checks ## Context Users currently access Evotor connection via a dedicated `/evotor` page linked from the navbar. As more integrations are planned, we need a unified **Connections** page where users can manage all their connections: add new ones, view status, edit, and delete. The dashboard starts empty — users explicitly add each connection they need. Supported connection types: **Evotor**, **VK** (one per type per user). ## Data Design ### Current state (separate models) `EvotorConnection` and `VkConnection` remain as-is — they hold service-specific fields (store_id/store_name for Evotor, vk_user_id/first_name/last_name for VK). The connections dashboard reads from both tables. No new unified "connection" table needed. The dashboard builds a virtual list by querying both tables. The "add" flow is just a gateway to the existing per-service OAuth pages. ### Model additions (both `EvotorConnection` and `VkConnection`) Already planned: - `is_online` (Boolean, default=False, server_default="0") - `last_checked_at` (DateTime, nullable) ## Plan ### 1. Model Changes — `web/models.py` Add `is_online` and `last_checked_at` to both `EvotorConnection` and `VkConnection`. ### 2. Alembic Migration Add health check fields to both connection tables. ### 3. Config Addition — `web/config.py` Add `HEALTH_CHECK_INTERVAL_SECONDS: int = 600` (10 minutes default). ### 4. Background Health Checker — `web/health_checker.py` (new) - `check_evotor_connection(access_token) -> bool` — async, `GET https://api.evotor.ru/stores` with Bearer token - `check_vk_connection(access_token) -> bool` — async, `GET https://api.vk.com/method/users.get` with token - `run_health_checks()` — queries all connection rows, checks each, updates `is_online` and `last_checked_at` - `health_check_loop(interval)` — infinite loop with `asyncio.sleep` ### 5. Wire Background Task — `web/main.py` Add FastAPI lifespan context manager: - On startup: `asyncio.create_task(health_check_loop(...))` - On shutdown: cancel the task - Register connections router ### 6. Connections Route — `web/routes/connections.py` (new) **`GET /connections`** — Main dashboard. Requires auth. Queries both `EvotorConnection` and `VkConnection` for the current user. Builds a list of available service types and their connection state: ```python SERVICE_TYPES = [ {"type": "evotor", "name": "Эвотор", "icon": "bi-shop", "connect_url": "/evotor", "disconnect_url": "/evotor/disconnect"}, {"type": "vk", "name": "ВКонтакте", "icon": "bi-chat-dots", "connect_url": "/vk", "disconnect_url": "/vk/disconnect"}, ] ``` For each type, attach the connection record (or None). Template renders based on state. **`GET /connections/add`** — "Add connection" page. Shows only service types the user has NOT yet connected: - Card per available type with service name, icon, short description - "Подключить" button linking to the service's OAuth page (`/evotor` or `/vk`) - If all types already connected — message "Все доступные сервисы подключены" - Back link to `/connections` **`POST /connections/delete?type=evotor|vk`** — Delete a connection. Same as existing disconnect endpoints but accessed from the dashboard. Deletes the connection record, redirects to `/connections`. (The existing `/evotor/disconnect` and `/vk/disconnect` routes remain as aliases.) ### 7. Templates **`web/templates/connections.html`** — Dashboard: ``` ┌─────────────────────────────────────────────────┐ │ Подключения [+ Добавить] │ ├─────────────────────────────────────────────────┤ │ │ │ ┌─ Card ─────────────────────────────────────┐ │ │ │ 🏪 Эвотор ● (green) │ │ │ │ Магазин "Чайная" │ │ │ │ Последняя проверка: 06.03.2026 14:30 │ │ │ │ │ │ │ │ [Настроить] [Отключить] │ │ │ └────────────────────────────────────────────┘ │ │ │ │ ┌─ Card ─────────────────────────────────────┐ │ │ │ 💬 ВКонтакте ● (green) │ │ │ │ Иван Иванов │ │ │ │ Последняя проверка: 06.03.2026 14:30 │ │ │ │ │ │ │ │ [Настроить] [Отключить] │ │ │ └────────────────────────────────────────────┘ │ │ │ │ (Нет подключений — нажмите «Добавить») │ │ │ └─────────────────────────────────────────────────┘ ``` Each connection card: - Icon + service name + status indicator (green/red/grey) - Details line (store name for Evotor, profile name for VK) - Last checked timestamp in card footer - "Настроить" button → links to service page (`/evotor` or `/vk`) for reconnect/details - "Отключить" button → POST to `/connections/delete?type=...` with confirmation Empty state: message prompting user to add their first connection. **`web/templates/connections_add.html`** — Add connection page: ``` ┌─────────────────────────────────────────────────┐ │ Добавить подключение │ ├─────────────────────────────────────────────────┤ │ │ │ ┌─ Card ─────────────────────────────────────┐ │ │ │ 🏪 Эвотор │ │ │ │ Подключите кассу Эвотор для синхронизации │ │ │ │ каталога товаров. │ │ │ │ [Подключить →] │ │ │ └────────────────────────────────────────────┘ │ │ │ │ ┌─ Card ─────────────────────────────────────┐ │ │ │ 💬 ВКонтакте │ │ │ │ Подключите аккаунт ВКонтакте для │ │ │ │ публикации товаров в вашу группу. │ │ │ │ [Подключить →] │ │ │ └────────────────────────────────────────────┘ │ │ │ │ ← Вернуться к подключениям │ │ │ └─────────────────────────────────────────────────┘ ``` ### 8. Navbar Update — `web/templates/base.html` Replace "Эвотор" link with "Подключения" → `/connections`. ### 9. Evotor/VK Callback Updates On successful OAuth callback in both `/evotor/callback` and `/vk/callback`: - Set `is_online=True` and `last_checked_at=now()` - Redirect to `/connections` (already done for Evotor) ### 10. Evotor/VK Template Back Links Change back links on `/evotor` and `/vk` pages: "Вернуться к подключениям" → `/connections`. ### 11. Delete Confirmation The "Отключить" button on the dashboard uses a simple JS `confirm()` dialog: "Вы уверены, что хотите отключить {service name}?" before submitting the POST form. ## Files Summary | File | Action | |------|--------| | `web/models.py` | Modify — add `is_online`, `last_checked_at` to both connection models | | `web/config.py` | Modify — add `HEALTH_CHECK_INTERVAL_SECONDS` | | `web/main.py` | Modify — lifespan + register connections router | | `web/routes/evotor.py` | Modify — set online on callback, redirect to /connections | | `web/routes/vk.py` | Modify — set online on callback, redirect to /connections | | `web/routes/connections.py` | Create — dashboard, add page, delete endpoint | | `web/health_checker.py` | Create — background checks for both Evotor and VK | | `web/templates/connections.html` | Create — dashboard with cards | | `web/templates/connections_add.html` | Create — add connection page | | `web/templates/base.html` | Modify — navbar link | | `web/templates/evotor.html` | Modify — back link to /connections | | `web/templates/vk.html` | Modify — back link to /connections | | Alembic migration | Create | ## Verification 1. Run `alembic upgrade head` 2. Start the app, verify background task logs appear 3. Visit `/connections` — empty state, "Добавить" button visible 4. Click "Добавить" → shows Evotor and VK as available services 5. Add Evotor → goes through OAuth → returns to `/connections` with green status card 6. Add VK → same flow → both connections visible 7. Click "Добавить" again → shows "Все доступные сервисы подключены" 8. Click "Отключить" on Evotor → confirmation dialog → connection removed → card disappears 9. Click "Добавить" → Evotor is available again 10. Wait for health check cycle → verify `is_online` and `last_checked_at` update on remaining connections