release: bump version to 0.45.0
This commit is contained in:
@@ -30,6 +30,7 @@ import axios, { type AxiosResponse } from 'axios'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import ScrollbarOverlay from '../../components/Scrollbar/ScrollbarOverlay'
|
||||
import { formatDateTimeZhCN } from '../../utils/datetime'
|
||||
import { formatPhaseMetric, getPhaseDisplay, getPhaseSummary } from '../../utils/phaseProgress'
|
||||
|
||||
const { Text } = Typography
|
||||
const COLLECTION_REFRESH_DELAY_MS = 800
|
||||
@@ -54,6 +55,11 @@ interface BuiltInDataSource {
|
||||
task_id: number | null
|
||||
progress: number | null
|
||||
phase?: string | null
|
||||
phase_progress?: number | null
|
||||
phase_message?: string | null
|
||||
phase_current?: number | null
|
||||
phase_total?: number | null
|
||||
phase_unit?: string | null
|
||||
records_processed: number | null
|
||||
total_records: number | null
|
||||
is_free?: boolean
|
||||
@@ -107,6 +113,11 @@ interface UnifiedDataSource {
|
||||
is_running?: boolean
|
||||
progress?: number | null
|
||||
phase?: string | null
|
||||
phase_progress?: number | null
|
||||
phase_message?: string | null
|
||||
phase_current?: number | null
|
||||
phase_total?: number | null
|
||||
phase_unit?: string | null
|
||||
task_id?: number | null
|
||||
created_at?: string
|
||||
updated_at?: string | null
|
||||
@@ -129,6 +140,11 @@ type TriggerDatasourceConflict = {
|
||||
message?: string
|
||||
progress?: number | null
|
||||
phase?: string | null
|
||||
phase_progress?: number | null
|
||||
phase_message?: string | null
|
||||
phase_current?: number | null
|
||||
phase_total?: number | null
|
||||
phase_unit?: string | null
|
||||
records_processed?: number | null
|
||||
total_records?: number | null
|
||||
}
|
||||
@@ -142,6 +158,11 @@ type DatasourceTaskStatus = {
|
||||
task_id?: number | null
|
||||
progress?: number | null
|
||||
phase?: string | null
|
||||
phase_progress?: number | null
|
||||
phase_message?: string | null
|
||||
phase_current?: number | null
|
||||
phase_total?: number | null
|
||||
phase_unit?: string | null
|
||||
records_processed?: number | null
|
||||
total_records?: number | null
|
||||
status?: string | null
|
||||
@@ -166,6 +187,11 @@ function normalizeBuiltin(source: BuiltInDataSource): UnifiedDataSource {
|
||||
is_running: source.is_running,
|
||||
progress: source.progress,
|
||||
phase: source.phase,
|
||||
phase_progress: source.phase_progress,
|
||||
phase_message: source.phase_message,
|
||||
phase_current: source.phase_current,
|
||||
phase_total: source.phase_total,
|
||||
phase_unit: source.phase_unit,
|
||||
task_id: source.task_id,
|
||||
is_free: source.is_free,
|
||||
requires_credentials: source.requires_credentials,
|
||||
@@ -291,7 +317,7 @@ function DataSources() {
|
||||
content: (
|
||||
<div>
|
||||
<p>{taskInfo?.message || '当前采集任务仍在运行,重新触发会丢失本次未完成进度。'}</p>
|
||||
<p>当前阶段: {taskInfo?.phase || 'running'}</p>
|
||||
<p>当前阶段: {getPhaseDisplay(taskInfo || {})}</p>
|
||||
<p>当前进度: {typeof taskInfo?.progress === 'number' ? `${Math.round(taskInfo.progress)}%` : '未知'}</p>
|
||||
<p>确认后会强制取消当前采集,并重新开始采集。</p>
|
||||
</div>
|
||||
@@ -474,7 +500,11 @@ function DataSources() {
|
||||
return <Tag color={record.is_active ? 'green' : 'default'}>{record.is_active ? '启用' : '禁用'}</Tag>
|
||||
}
|
||||
if (record.is_running) {
|
||||
return <Tag color="processing">{record.phase || '运行中'}{record.progress ? ` ${Math.round(record.progress)}%` : ''}</Tag>
|
||||
return (
|
||||
<Tooltip title={getPhaseDisplay(record)}>
|
||||
<Tag color="processing">{getPhaseSummary(record)}</Tag>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
if (!record.last_status) return <Tag>未执行</Tag>
|
||||
return <Tag color={record.last_status === 'success' ? 'success' : record.last_status === 'failed' ? 'error' : 'default'}>{record.last_status}</Tag>
|
||||
@@ -603,8 +633,13 @@ function DataSources() {
|
||||
{source.source}{source.task_id ? ` · #${source.task_id}` : ''}
|
||||
</Text>
|
||||
</Space>
|
||||
<Tag color="processing">{source.phase || 'running'}</Tag>
|
||||
<Tooltip title={getPhaseDisplay(source)}>
|
||||
<Tag color="processing">{getPhaseSummary(source)}</Tag>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{source.phase_message ? (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>{source.phase_message}</Text>
|
||||
) : null}
|
||||
<Progress
|
||||
percent={Math.round(source.progress || 0)}
|
||||
size="small"
|
||||
@@ -612,8 +647,9 @@ function DataSources() {
|
||||
strokeColor="#1677ff"
|
||||
/>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
已处理 {source.records_processed ?? 0}
|
||||
{source.total_records ? ` / ${source.total_records}` : ''}
|
||||
{source.phase_unit === 'bytes'
|
||||
? `下载 ${formatPhaseMetric(source) || '准备中'}`
|
||||
: `已处理 ${source.records_processed ?? 0}${source.total_records ? ` / ${source.total_records}` : ''}`}
|
||||
</Text>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Table, Tag, Card, Row, Col, Statistic, Button } from 'antd'
|
||||
import { Table, Tag, Card, Row, Col, Statistic, Button, Tooltip } from 'antd'
|
||||
import { ReloadOutlined, CheckCircleOutlined, CloseCircleOutlined, SyncOutlined } from '@ant-design/icons'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
|
||||
import { formatDateTimeZhCN } from '../../utils/datetime'
|
||||
import { getPhaseDisplay, getPhaseSummary } from '../../utils/phaseProgress'
|
||||
|
||||
interface Task {
|
||||
id: number
|
||||
collector: string
|
||||
collector?: string
|
||||
datasource_name?: string
|
||||
status: 'success' | 'failed' | 'running' | 'pending'
|
||||
phase?: string | null
|
||||
phase_progress?: number | null
|
||||
phase_message?: string | null
|
||||
phase_current?: number | null
|
||||
phase_total?: number | null
|
||||
phase_unit?: string | null
|
||||
records_processed: number
|
||||
total_records?: number | null
|
||||
progress?: number | null
|
||||
started_at: string
|
||||
completed_at: string
|
||||
duration_seconds: number
|
||||
@@ -53,7 +63,20 @@ function Tasks() {
|
||||
title: '收集器',
|
||||
dataIndex: 'collector',
|
||||
key: 'collector',
|
||||
render: (c: string) => <Tag color="blue">{c}</Tag>,
|
||||
render: (_: string, task: Task) => <Tag color="blue">{task.collector || task.datasource_name || '-'}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '阶段',
|
||||
dataIndex: 'phase',
|
||||
key: 'phase',
|
||||
render: (_: string, task: Task) => {
|
||||
const detail = task.phase ? getPhaseDisplay(task) : null
|
||||
return detail ? (
|
||||
<Tooltip title={detail}>
|
||||
<Tag color={task.status === 'running' ? 'processing' : 'default'}>{getPhaseSummary(task)}</Tag>
|
||||
</Tooltip>
|
||||
) : '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
|
||||
61
frontend/src/utils/phaseProgress.ts
Normal file
61
frontend/src/utils/phaseProgress.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
export type PhaseProgressLike = {
|
||||
phase?: string | null
|
||||
phase_progress?: number | null
|
||||
phase_message?: string | null
|
||||
phase_current?: number | null
|
||||
phase_total?: number | null
|
||||
phase_unit?: string | null
|
||||
}
|
||||
|
||||
const BYTE_UNIT_BASE = 1024
|
||||
const BYTE_UNITS = ['B', 'KB', 'MB', 'GB'] as const
|
||||
|
||||
export function formatBytes(value: number): string {
|
||||
if (!Number.isFinite(value) || value <= 0) return '0 B'
|
||||
let size = value
|
||||
let unitIndex = 0
|
||||
while (size >= BYTE_UNIT_BASE && unitIndex < BYTE_UNITS.length - 1) {
|
||||
size /= BYTE_UNIT_BASE
|
||||
unitIndex += 1
|
||||
}
|
||||
const digits = unitIndex === 0 ? 0 : size >= 10 ? 1 : 2
|
||||
return `${size.toFixed(digits)} ${BYTE_UNITS[unitIndex]}`
|
||||
}
|
||||
|
||||
export function formatPhaseMetric(source: Pick<PhaseProgressLike, 'phase_current' | 'phase_total' | 'phase_unit'>): string | null {
|
||||
const current = source.phase_current
|
||||
const total = source.phase_total
|
||||
if (typeof current !== 'number') return null
|
||||
if (source.phase_unit === 'bytes') {
|
||||
return typeof total === 'number' && total > 0
|
||||
? `${formatBytes(current)} / ${formatBytes(total)}`
|
||||
: formatBytes(current)
|
||||
}
|
||||
if (typeof total === 'number' && total > 0) {
|
||||
return `${current} / ${total}${source.phase_unit ? ` ${source.phase_unit}` : ''}`
|
||||
}
|
||||
return `${current}${source.phase_unit ? ` ${source.phase_unit}` : ''}`
|
||||
}
|
||||
|
||||
export function getPhaseDisplay(source: PhaseProgressLike, fallback = 'running'): string {
|
||||
const phase = source.phase || fallback
|
||||
const parts = [phase]
|
||||
if (typeof source.phase_progress === 'number') {
|
||||
parts.push(`${Math.round(source.phase_progress)}%`)
|
||||
}
|
||||
if (source.phase_message) {
|
||||
parts.push(source.phase_message)
|
||||
}
|
||||
const metric = formatPhaseMetric(source)
|
||||
if (metric) {
|
||||
parts.push(metric)
|
||||
}
|
||||
return parts.join(' · ')
|
||||
}
|
||||
|
||||
export function getPhaseSummary(source: PhaseProgressLike, fallback = 'running'): string {
|
||||
const phase = source.phase || fallback
|
||||
return typeof source.phase_progress === 'number'
|
||||
? `${phase} ${Math.round(source.phase_progress)}%`
|
||||
: phase
|
||||
}
|
||||
Reference in New Issue
Block a user