Страница /reports трансформирована в Центр статусов задач:
Backend:
- GET /api/reports/summary — агрегированные счётчики тип×статус (5 корзин)
- GET/PUT /api/settings/reports — глобальные настройки отчётов
- _filter_tasks_by_rbac() — row-level фильтрация по роли
- normalize_task_report: LLM-валидация с ошибками → FAILED/PARTIAL
- get_summary(): 5 корзин pending/running/awaiting_input/success/failed
Frontend:
- TaskCenterModel.svelte.ts (400 строк) — Screen Model
- SummaryPanel — сводная панель с цветовым кодированием и active filter
- ReportCard — humanized labels, duration, task_id, failed border
- FilterBar — search + sort + time range с label'ами
- Pagination — showingText, уникальные id для select
- Quick views: «Упавшие», «В работе», «Успешные»
- TaskDrawer: scroll-to-error, footer скрыт для terminal, «Н/Д» fix
Тесты: 48 backend + 38 frontend (2521 всего)
Build: ✅ Console errors: 0
4.1 KiB
#region UxApiShapes [C:3] [TYPE ADR] [SEMANTICS ux,api,shapes,task-status-center] @defgroup Ux API interaction shapes for Task Status Center.
GET /api/reports/summary
Request:
GET /api/reports/summary
Authorization: Bearer <JWT>
Response variants:
| Status | Body | UI State |
|---|---|---|
| 200 | { by_type: [TaskTypeSummary], total_tasks: int, active_tasks: int } |
ready or empty (if total=0) |
| 401 | { detail: "Not authenticated" } |
Redirect to /login |
| 403 | { detail: "Permission denied for tasks:READ" } |
Toast "Недостаточно прав" |
| 503 | { detail: "Service unavailable" } |
error + auto-retry 10s |
Loading UX: Skeleton in left column (3-4 pulse cards). No debounce — single request.
GET /api/reports (enhanced)
Request:
GET /api/reports?page=1&page_size=20&task_types=migration,backup&statuses=running,failed&search=dashboard&time_from=...&time_to=...&sort_by=updated_at&sort_order=desc
Authorization: Bearer <JWT>
Response variants:
| Status | Body | UI State |
|---|---|---|
| 200 (results) | { items: [TaskReport], total, page, page_size, has_next, applied_filters } |
ready |
| 200 (empty) | { items: [], total: 0, page: 1, page_size: 20, has_next: false, applied_filters } |
filtered_empty |
| 401 | { detail: "Not authenticated" } |
Redirect to /login |
| 403 | { detail: "Permission denied" } |
Toast "Недостаточно прав" |
| 422 | { detail: [{ msg: "sort_by must be one of: ..." }] } |
Toast "Неверный параметр" + reset |
Loading UX: Skeleton rows in right column. On filter change: 200ms threshold (if response faster — instant swap, no skeleton).
GET /api/settings/reports (NEW)
Request:
GET /api/settings/reports
Authorization: Bearer <JWT>
Response variants:
| Status | Body | UI State |
|---|---|---|
| 200 | { disabled_task_types: ["clean_release"] } |
Merge with localStorage visibleTypes |
| 200 (default) | { disabled_task_types: [] } |
All types visible |
| 401 | { detail: "Not authenticated" } |
Redirect to /login |
| 403 | { detail: "Permission denied" } |
Fallback: all types visible |
PUT /api/settings/reports (NEW, admin only)
Request:
PUT /api/settings/reports
Authorization: Bearer <JWT>
Content-Type: application/json
{ "disabled_task_types": ["clean_release", "unknown"] }
Response variants:
| Status | Body | UI State |
|---|---|---|
| 200 | { disabled_task_types: ["clean_release", "unknown"] } |
Toast "Настройки сохранены" |
| 401 | { detail: "Not authenticated" } |
Redirect to /login |
| 403 | { detail: "Permission denied for settings:WRITE" } |
Toast "Недостаточно прав" |
| 422 | { detail: [{ msg: "invalid task type" }] } |
Inline error on field |
Loading UX: Spinner on "Сохранить" button. Save button hidden for non-admins.
WebSocket /ws/task-events (existing, reused)
Connection:
ws://host/ws/task-events?token=<JWT>
Message format (existing):
{ "type": "task_status", "task_id": "abc-123", "task": { "id", "plugin_id", "status", "started_at", "finished_at", "user_id", "params", "result" } }
UI reactions:
| Event | List | Summary |
|---|---|---|
| New task (PENDING) | slide-in top | +1 pending |
| PENDING→RUNNING | badge gray→blue | -1 pending, +1 running |
| RUNNING→SUCCESS | badge→green, row flash green | -1 running, +1 success |
| RUNNING→FAILED | badge→red, row flash red | -1 running, +1 failed |
| →AWAITING_INPUT | badge→yellow, "Ответить" button | +1 awaiting_input |
| Task removed (retention) | row disappears | -1 from last status |
Debounce: 100ms buffer → batch apply → $derived recompute.
Reconnect: 1s→2s→4s→8s→max 30s. On reconnect: GET /api/reports/summary to sync missed events.
WebSocket /ws/logs/{task_id} (existing, for Task Drawer)
Unchanged. Task Drawer connects on open, streams live logs. Closes on drawer close or task completion.
#endregion UxApiShapes