07e3d1e026
- 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>
200 lines
5.1 KiB
TypeScript
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
|
|
}
|