Files
ss-tools/specs/034-task-status-center/contracts/ux/api-ux.md
busya 33ee976c48 feat(reports): Task Status Center — unified /reports dashboard
Страница /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
2026-07-02 18:53:58 +03:00

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