130 lines
3.6 KiB
TypeScript
130 lines
3.6 KiB
TypeScript
export interface MonitorTask {
|
||
history?: { at: string; kind: string; detail: string }[]
|
||
id: string
|
||
title: string
|
||
path: string
|
||
source_id: string
|
||
source_name: string
|
||
target_scope: string
|
||
target_kb: string
|
||
action: string
|
||
stage: string
|
||
status: string
|
||
detail: string
|
||
updated_at: string
|
||
retry_kind: string
|
||
failure_id: string
|
||
retry_count: number
|
||
version_id: string
|
||
}
|
||
export interface MonitorTaskPage {
|
||
items: MonitorTask[]
|
||
total: number
|
||
limit: number
|
||
offset: number
|
||
sources: { id: string; name: string }[]
|
||
targets: { id: string; name: string }[]
|
||
retryable: number
|
||
}
|
||
export interface MonitorFilters {
|
||
q: string
|
||
source: string
|
||
scope: string
|
||
target: string
|
||
action: string
|
||
status: string
|
||
stage: string
|
||
since: string
|
||
until: string
|
||
}
|
||
export const defaultMonitorFilters: MonitorFilters = {
|
||
q: '',
|
||
source: '',
|
||
scope: '',
|
||
target: '',
|
||
action: '',
|
||
status: 'active',
|
||
stage: '',
|
||
since: '',
|
||
until: '',
|
||
}
|
||
export const actionLabels: Record<string, string> = { add: '新增', update: '更新', remove: '移除' }
|
||
export const stageLabels: Record<string, string> = {
|
||
waiting: '等待调度',
|
||
fetch: '取件',
|
||
upload: '上传',
|
||
parsing: '解析',
|
||
indexing: '索引增强',
|
||
removal: '测试库移除',
|
||
finished: '结束',
|
||
}
|
||
export const taskStatusLabels: Record<string, string> = {
|
||
waiting: '等待中',
|
||
running: '执行中',
|
||
retrying: '重试中',
|
||
attention: '需人工处理',
|
||
completed: '已完成',
|
||
cancelled: '已取消',
|
||
}
|
||
export const canRetryTask = (item: MonitorTask) =>
|
||
item.status === 'attention' && ['upload', 'fetch', 'remove'].includes(item.retry_kind)
|
||
const record = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null
|
||
export async function fetchMonitorTasks(
|
||
filters: Partial<MonitorFilters> & { id?: string },
|
||
offset = 0,
|
||
limit = 20,
|
||
signal?: AbortSignal
|
||
): Promise<MonitorTaskPage> {
|
||
const query = new URLSearchParams({ limit: String(limit), offset: String(offset) })
|
||
Object.entries(filters).forEach(([k, v]) => {
|
||
if (v) query.set(k, v)
|
||
})
|
||
const response = await fetch(`/api/v1/monitor/tasks?${query}`, { headers: { Accept: 'application/json' }, signal })
|
||
if (!response.ok) throw new Error(`加载文件处理状态失败(HTTP ${response.status})`)
|
||
const value: unknown = await response.json()
|
||
if (!record(value) || !record(value.data)) throw new Error('文件处理响应无效')
|
||
const d = value.data
|
||
const count = (v: unknown) => Number.isInteger(v) && Number(v) >= 0
|
||
if (
|
||
!Array.isArray(d.items) ||
|
||
!count(d.total) ||
|
||
!count(d.limit) ||
|
||
!count(d.offset) ||
|
||
!count(d.retryable) ||
|
||
!Array.isArray(d.sources) ||
|
||
!Array.isArray(d.targets)
|
||
)
|
||
throw new Error('文件处理分页响应无效')
|
||
for (const item of d.items) {
|
||
if (
|
||
!record(item) ||
|
||
![
|
||
'id',
|
||
'title',
|
||
'path',
|
||
'source_id',
|
||
'source_name',
|
||
'target_scope',
|
||
'target_kb',
|
||
'action',
|
||
'stage',
|
||
'status',
|
||
'detail',
|
||
'updated_at',
|
||
'retry_kind',
|
||
'failure_id',
|
||
'version_id',
|
||
].every((k) => typeof item[k] === 'string') ||
|
||
!count(item.retry_count) ||
|
||
!Object.hasOwn(actionLabels, String(item.action)) ||
|
||
!Object.hasOwn(stageLabels, String(item.stage)) ||
|
||
!Object.hasOwn(taskStatusLabels, String(item.status))
|
||
)
|
||
throw new Error('文件处理条目无效')
|
||
}
|
||
for (const option of [...d.sources, ...d.targets])
|
||
if (!record(option) || typeof option.id !== 'string' || typeof option.name !== 'string')
|
||
throw new Error('文件处理筛选项无效')
|
||
return d as unknown as MonitorTaskPage
|
||
}
|