Страница /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
13 KiB
#region DataModel [C:4] [TYPE ADR] [SEMANTICS data-model, schema, pydantic, typescript, dto, task-status-center] @BRIEF Data model for the Task Status Center feature — backend Pydantic schemas (new + modified), frontend TypeScript DTOs, and the cross-stack contract bridge. @RATIONALE The data model defines WHAT flows between backend and frontend. Without explicit DTO contracts, type drift between Python and TypeScript is inevitable over long-horizon development. @REJECTED Auto-generating TypeScript from Pydantic was rejected (R7) — the schema set is small enough that manual sync is proportionate and avoids build-step complexity.
1. Entity Relationship Summary
TaskRecord (SQLAlchemy, persisted)
└── Task (Pydantic, in-memory via TaskManager)
└── TaskReport (Pydantic, normalized for UI)
├── ReportCollection (paginated list)
├── ReportDetailView (single task detail)
└── TaskSummary (aggregated counts) ← NEW
TaskSummary
└── TaskTypeSummary[] (one per task_type)
└── StatusCounts (pending, running, awaiting_input, success, failed)
2. New Backend Schemas
2.1 TaskSummary (NEW)
# backend/src/models/report.py (add to existing module)
# #region TaskSummary [C:3] [TYPE Class] [SEMANTICS summary, aggregation, task, status, count]
# @ingroup ReportModels
# @BRIEF Aggregated task counts by type and status for the summary dashboard.
# @DATA_CONTRACT Output: TaskSummary → frontend interface TaskSummary
# @RELATION DEPENDS_ON -> [TaskReport]
# @TEST_CONTRACT TaskSummaryModel -> { invariants: ["all counts >= 0", "total == sum of all status counts across all types"] }
class StatusCounts(BaseModel):
"""Counts per status for a single task type."""
pending: int = Field(default=0, ge=0)
running: int = Field(default=0, ge=0)
awaiting_input: int = Field(default=0, ge=0)
success: int = Field(default=0, ge=0)
failed: int = Field(default=0, ge=0)
class TaskTypeSummary(BaseModel):
"""Summary for one task type."""
task_type: TaskType
display_label: str
icon_token: str
counts: StatusCounts
total: int = Field(default=0, ge=0)
class TaskSummary(BaseModel):
"""Full summary response for the summary dashboard."""
by_type: list[TaskTypeSummary]
total_tasks: int = Field(default=0, ge=0)
active_tasks: int = Field(default=0, ge=0) # pending + running + awaiting_input
# #endregion TaskSummary
2.2 Enhanced ReportQuery (MODIFIED)
The existing ReportQuery already supports task_types[], statuses[], search, time_from, time_to, sort_by, sort_order. No schema changes needed — the filtering logic in the service method will be enhanced to use these parameters more thoroughly.
Change: sort_by validator adds "created_at" to allowed values (currently only updated_at, status, task_type).
2.3 Enhanced TaskReport (MODIFIED)
No new fields needed. The existing TaskReport already carries all required fields for the spec:
report_id→ task identifier in listtask_type→ filter/summary groupingstatus→ filter/summary grouping + color codingstarted_at→ temporal displayupdated_at→ sort defaultsummary→ display texterror_context→ error display + next actionssource_ref→ linked resource (dashboard, dataset, config)
2.4 No DB Schema Changes
No new database tables or columns. The feature works entirely with:
- Existing
task_recordstable (persisted task metadata) - Existing
task_logstable (persisted logs for Task Drawer) - In-memory
TaskManager.get_all_tasks()as the data source
3. New Frontend TypeScript DTOs
// frontend/src/types/reports.ts (NEW FILE)
// #region ReportsTypes [C:3] [TYPE Module] [SEMANTICS types, dto, reports, task-status-center]
// @BRIEF TypeScript DTOs matching backend Pydantic schemas for the Task Status Center.
// @DATA_CONTRACT Input: TypeScript interfaces → Output: typed API responses
// @RELATION DEPENDS_ON -> [ReportModels] via @DATA_CONTRACT cross-stack
// @RATIONALE Typed API responses prevent field mismatch bugs and enable IDE autocomplete.
/** Canonical task types matching backend TaskType enum. */
export type TaskType =
| 'llm_verification'
| 'backup'
| 'migration'
| 'documentation'
| 'clean_release'
| 'unknown';
/** Normalized report status matching backend ReportStatus enum. */
export type ReportStatus =
| 'success'
| 'failed'
| 'in_progress'
| 'partial';
/** Error/recovery context for failed tasks. */
export interface ErrorContext {
code: string | null;
message: string;
next_actions: string[];
}
/** Single task report — matches backend TaskReport. */
export interface TaskReport {
report_id: string;
task_id: string;
task_type: TaskType;
status: ReportStatus;
started_at: string | null;
updated_at: string;
summary: string;
details?: Record<string, unknown> | null;
validation_record?: Record<string, unknown> | null;
error_context: ErrorContext | null;
source_ref?: Record<string, unknown> | null;
}
/** Query parameters for report list filtering — matches backend ReportQuery. */
export interface ReportQuery {
page: number;
page_size: number;
task_types: TaskType[];
statuses: ReportStatus[];
time_from?: string | null;
time_to?: string | null;
search?: string | null;
sort_by: 'updated_at' | 'created_at' | 'status' | 'task_type';
sort_order: 'asc' | 'desc';
}
/** Paginated collection — matches backend ReportCollection. */
export interface ReportCollection {
items: TaskReport[];
total: number;
page: number;
page_size: number;
has_next: boolean;
applied_filters: ReportQuery;
}
/** Detailed view — matches backend ReportDetailView. */
export interface ReportDetailView {
report: TaskReport;
timeline: Record<string, unknown>[];
diagnostics: Record<string, unknown> | null;
next_actions: string[];
}
// --- NEW for Task Status Center ---
/** Status counts for one task type in the summary. */
export interface StatusCounts {
pending: number;
running: number;
awaiting_input: number;
success: number;
failed: number;
}
/** Per-type summary entry. */
export interface TaskTypeSummary {
task_type: TaskType;
display_label: string;
icon_token: string;
counts: StatusCounts;
total: number;
}
/** Full summary response — matches backend TaskSummary. */
export interface TaskSummary {
by_type: TaskTypeSummary[];
total_tasks: number;
active_tasks: number;
}
/** WebSocket task status event from /ws/task-events. */
export interface TaskStatusEvent {
type: 'task_status';
task_id: string;
task: {
id: string;
plugin_id: string;
status: string;
started_at: string | null;
finished_at: string | null;
user_id: string | null;
params: Record<string, unknown> | null;
result: Record<string, unknown> | null;
};
}
/** Filter state for the Task Center UI (broader than ReportQuery). */
export interface TaskCenterFilter {
task_types: TaskType[];
statuses: ReportStatus[];
search: string;
time_range: '1h' | '24h' | '7d' | '30d' | 'all';
sort_by: 'updated_at' | 'created_at' | 'status' | 'task_type';
sort_order: 'asc' | 'desc';
}
/** UI screen states for the Task Center page. */
export type ScreenState =
| 'loading'
| 'ready'
| 'empty'
| 'error'
| 'disconnected'
| 'reconnecting';
// #endregion ReportsTypes
4. Cross-Stack Contract Bridge
Backend (Pydantic) Frontend (TypeScript)
───────────────── ─────────────────────
TaskType (str Enum) ←──→ TaskType (union type)
ReportStatus (str Enum) ←──→ ReportStatus (union type)
TaskReport (BaseModel) ←──→ interface TaskReport
ReportQuery (BaseModel) ←──→ interface ReportQuery
ReportCollection ←──→ interface ReportCollection
ReportDetailView ←──→ interface ReportDetailView
TaskSummary (NEW) ←──→ interface TaskSummary
StatusCounts (NEW) ←──→ interface StatusCounts
TaskTypeSummary (NEW) ←──→ interface TaskTypeSummary
Each bridge pair carries @DATA_CONTRACT annotation referencing the counterpart:
- Backend:
@DATA_CONTRACT Output: TaskSummary → frontend interface TaskSummary - Frontend:
@DATA_CONTRACT Input: TypeScript interface → Output: typed API response
5. Data Flow Diagram
┌─────────────────────────────────────────────────────────────────┐
│ BROWSER │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ TaskCenterModel (.svelte.ts) │ │
│ │ │ │
│ │ loadInitialData() │ │
│ │ ├─ GET /api/reports/summary ──────► TaskSummary │ │
│ │ └─ GET /api/reports?page=1&... ──► ReportCollection │ │
│ │ │ │
│ │ connectWebSocket() │ │
│ │ └─ ws://host/ws/task-events ────► TaskStatusEvent x N │ │
│ │ │ │ │
│ │ └─ 100ms debounce buffer │ │
│ │ └─ apply to tasks[] ──► $derived summary │ │
│ │ │ │
│ │ selectTask(taskId) │ │
│ │ └─ openDrawerForTask(taskId) ──► TaskDrawer + logs WS │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ +page.svelte (thin render layer ~60 lines) │ │
│ │ ├─ SummaryPanel ← m.summary │ │
│ │ ├─ FilterBar ← m.filters, m.applyFilter() │ │
│ │ └─ TaskList ← m.filteredTasks, m.selectTask() │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ BACKEND (FastAPI) │
│ │
│ GET /api/reports/summary ───► ReportsService.get_summary() │
│ └─ task_manager.get_all_tasks() │
│ └─ filter by RBAC (role-based) │
│ └─ normalize → aggregate → TaskSummary │
│ │
│ GET /api/reports?... ───► ReportsService.list_reports() │
│ └─ same flow → filter, sort, paginate → ReportCollection │
│ │
│ ws://host/ws/task-events ───► EventBus.broadcast_status() │
│ └─ already broadcasts on every task status transition │
│ └─ NO CODE CHANGES NEEDED │
└─────────────────────────────────────────────────────────────────┘
#endregion DataModel