Files
HartOMat/frontend/src/api/worker.ts
T
Hartmut 07e3d1e026 feat(phase8.1-8.2): dynamic worker concurrency via worker_configs
- Migration 054: worker_configs table (queue_name PK, max/min_concurrency,
  enabled, updated_at); seeds step_processing(8/2), thumbnail_rendering(1/1),
  ai_validation(4/1)
- WorkerConfig SQLAlchemy model
- apply_worker_concurrency beat task: reads enabled configs, broadcasts
  pool_grow to all Celery workers every 5min
- GET/PUT /api/worker/configs (admin): list + update per-queue concurrency
- docker-compose.yml: worker uses --autoscale=${MAX_CONCURRENCY:-8},${MIN_CONCURRENCY:-2};
  render-worker uses --autoscale=1,1 --concurrency=1
- WorkerManagement.tsx: "Concurrency Settings" section with +/- steppers
  and Save button per queue

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:41:57 +01:00

200 lines
5.1 KiB
TypeScript

import api from './client'
export interface RenderLog {
renderer: string
format?: string
engine?: string
engine_used?: string
samples?: number
cycles_device?: string
stl_quality?: string
smooth_angle?: number
width?: number
height?: number
total_duration_s?: number
stl_duration_s?: number
render_duration_s?: number
stl_size_bytes?: number
output_size_bytes?: number
parts_count?: number
log_lines?: string[]
fallback?: boolean
started_at?: string
completed_at?: string
}
export interface CadActivityEntry {
cad_file_id: string
original_name: string
file_size: number | null
processing_status: 'pending' | 'processing' | 'completed' | 'failed' | string
error_message: string | null
updated_at: string
created_at: string
order_numbers: string[]
render_log: RenderLog | null
}
export interface RenderJobEntry {
order_line_id: string
order_id: string | null
order_number: string | null
product_name: string | null
output_type_name: string | null
render_status: 'processing' | 'completed' | 'failed' | string
render_backend_used: string | null
render_started_at: string | null
render_completed_at: string | null
updated_at: string
}
export interface WorkerActivity {
cad_processing: CadActivityEntry[]
active_count: number
failed_count: number
render_jobs: RenderJobEntry[]
render_active_count: number
render_failed_count: number
}
export async function getWorkerActivity(): Promise<WorkerActivity> {
const res = await api.get<WorkerActivity>('/worker/activity')
return res.data
}
export async function reprocessCadFile(cad_file_id: string): Promise<void> {
await api.post(`/worker/activity/${cad_file_id}/reprocess`)
}
export interface RenderLogEntry {
ts: number
t: string
level: 'info' | 'error' | 'success' | string
msg: string
}
export async function getRenderLog(orderLineId: string, after: number = 0): Promise<{
entries: RenderLogEntry[]
total: number
next_after: number
}> {
const res = await api.get(`/worker/render-log/${orderLineId}`, { params: { after } })
return res.data
}
/** Returns the SSE URL for streaming render logs (needs token in query param). */
export function renderLogStreamUrl(orderLineId: string): string {
return `/api/worker/render-log/${orderLineId}/stream`
}
// ---------------------------------------------------------------------------
// Queue inspection + control
// ---------------------------------------------------------------------------
export interface QueueTask {
task_id: string
task_name: string
args: unknown[]
argsrepr: string
status: 'pending' | 'active' | 'reserved'
worker?: string
queue?: string
}
export interface QueueStatus {
queue_depths: Record<string, number>
pending_count: number
active: QueueTask[]
reserved: QueueTask[]
pending: QueueTask[]
}
export async function getQueueStatus(): Promise<QueueStatus> {
const res = await api.get<QueueStatus>('/worker/queue')
return res.data
}
export async function purgeQueue(): Promise<{ purged: number; message: string }> {
const res = await api.post<{ purged: number; message: string }>('/worker/queue/purge')
return res.data
}
export async function cancelTask(taskId: string): Promise<{ revoked: string }> {
const res = await api.post<{ revoked: string }>(`/worker/queue/cancel/${taskId}`)
return res.data
}
// ---------------------------------------------------------------------------
// Worker management
// ---------------------------------------------------------------------------
export interface CeleryWorkerTask {
name: string
id: string
}
export interface CeleryWorker {
name: string
queues: string[]
active_task_count: number
active_tasks: CeleryWorkerTask[]
total_tasks_processed: Record<string, number>
}
export interface CeleryWorkersResponse {
workers: CeleryWorker[]
error?: string
}
export interface ScaleRequest {
service: 'render-worker' | 'worker' | 'worker-thumbnail'
count: number
}
export interface ScaleResponse {
service: string
count: number
status: string
}
export async function getCeleryWorkers(): Promise<CeleryWorkersResponse> {
const res = await api.get<CeleryWorkersResponse>('/worker/celery-workers')
return res.data
}
export async function scaleWorkers(req: ScaleRequest): Promise<ScaleResponse> {
const res = await api.post<ScaleResponse>('/worker/scale', req)
return res.data
}
// ---------------------------------------------------------------------------
// Worker concurrency configuration
// ---------------------------------------------------------------------------
export interface WorkerConfig {
queue_name: string
max_concurrency: number
min_concurrency: number
enabled: boolean
updated_at: string
}
export interface WorkerConfigUpdate {
max_concurrency?: number
min_concurrency?: number
enabled?: boolean
}
export async function getWorkerConfigs(): Promise<WorkerConfig[]> {
const res = await api.get<WorkerConfig[]>('/worker/configs')
return res.data
}
export async function updateWorkerConfig(
queueName: string,
update: WorkerConfigUpdate,
): Promise<WorkerConfig> {
const res = await api.put<WorkerConfig>(`/worker/configs/${queueName}`, update)
return res.data
}