Страница /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
297 lines
13 KiB
Markdown
297 lines
13 KiB
Markdown
#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)
|
|
|
|
```python
|
|
# 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 list
|
|
- `task_type` → filter/summary grouping
|
|
- `status` → filter/summary grouping + color coding
|
|
- `started_at` → temporal display
|
|
- `updated_at` → sort default
|
|
- `summary` → display text
|
|
- `error_context` → error display + next actions
|
|
- `source_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_records` table (persisted task metadata)
|
|
- Existing `task_logs` table (persisted logs for Task Drawer)
|
|
- In-memory `TaskManager.get_all_tasks()` as the data source
|
|
|
|
## 3. New Frontend TypeScript DTOs
|
|
|
|
```typescript
|
|
// 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
|