release: bump version to 0.45.0

This commit is contained in:
rayd1o
2026-04-29 23:43:54 +08:00
parent 9dafbf4f6e
commit ba54545ac7
32 changed files with 602 additions and 29 deletions

View File

@@ -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>

View File

@@ -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: '状态',

View 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
}