mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 11:36:36 +08:00
fix organize path mapping and task flow
This commit is contained in:
@@ -8,9 +8,31 @@ export interface ActiveTranscode {
|
||||
playlist_ok: boolean
|
||||
}
|
||||
|
||||
export interface BackgroundTask {
|
||||
id: string
|
||||
kind: string
|
||||
name: string
|
||||
status: 'running' | 'completed' | 'failed'
|
||||
stage?: string
|
||||
source_path?: string
|
||||
dest_path?: string
|
||||
message?: string
|
||||
error?: string
|
||||
metrics?: Record<string, number>
|
||||
started_at: string
|
||||
updated_at: string
|
||||
finished_at?: string
|
||||
}
|
||||
|
||||
export interface BackgroundTaskSnapshot {
|
||||
active: BackgroundTask[]
|
||||
recent: BackgroundTask[]
|
||||
}
|
||||
|
||||
export interface TasksSnapshot {
|
||||
transcodes: ActiveTranscode[]
|
||||
torrents: QBitTorrent[] | null
|
||||
background_tasks?: BackgroundTaskSnapshot
|
||||
}
|
||||
|
||||
export const tasksAPI = {
|
||||
|
||||
@@ -67,7 +67,7 @@ type AutoOrganizeConfig = {
|
||||
const AUTO_ORGANIZE_DEFAULTS: AutoOrganizeConfig = {
|
||||
enabled: 'false',
|
||||
afterDownload: 'false',
|
||||
scrapeAfter: 'false',
|
||||
scrapeAfter: 'true',
|
||||
sourceDir: '',
|
||||
targetDir: '',
|
||||
transferMode: 'hardlink',
|
||||
@@ -127,7 +127,7 @@ export function FileManagerPage() {
|
||||
const [organizeTransferMode, setOrganizeTransferMode] = useState('hardlink')
|
||||
const [organizeMediaType, setOrganizeMediaType] = useState('auto')
|
||||
const [scanAfter, setScanAfter] = useState(true)
|
||||
const [scrapeAfter, setScrapeAfter] = useState(false)
|
||||
const [scrapeAfter, setScrapeAfter] = useState(true)
|
||||
const [organizeBusy, setOrganizeBusy] = useState('')
|
||||
const [previewItems, setPreviewItems] = useState<Array<{
|
||||
source: string
|
||||
|
||||
+100
-1
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Activity } from 'lucide-react'
|
||||
|
||||
import { tasksAPI, type TasksSnapshot } from '../api/tasks'
|
||||
import { tasksAPI, type BackgroundTask, type TasksSnapshot } from '../api/tasks'
|
||||
|
||||
function fmtBytes(n: number): string {
|
||||
if (!n || n <= 0) return '0 B'
|
||||
@@ -15,6 +15,90 @@ function fmtBytes(n: number): string {
|
||||
return `${v.toFixed(2)} ${u[i]}`
|
||||
}
|
||||
|
||||
const metricLabels: Record<string, string> = {
|
||||
organized: '新增',
|
||||
replaced: '替换',
|
||||
skipped: '跳过',
|
||||
errors: '错误',
|
||||
scans: '扫描库',
|
||||
scan_visited: '访问',
|
||||
scan_added: '入库',
|
||||
scan_updated: '更新',
|
||||
scan_removed: '移除',
|
||||
scan_errors: '扫描错误',
|
||||
scrapes: '刮削库',
|
||||
scrape_matched: '匹配',
|
||||
scrape_skipped: '刮削跳过',
|
||||
scrape_errors: '刮削错误',
|
||||
visited: '访问',
|
||||
added: '入库',
|
||||
updated: '更新',
|
||||
probed: '探测',
|
||||
local_metadata: '本地元数据',
|
||||
removed: '移除',
|
||||
matched: '匹配',
|
||||
processed: '处理',
|
||||
queued: '排队',
|
||||
}
|
||||
|
||||
function formatMetrics(metrics?: Record<string, number>): string {
|
||||
if (!metrics) return ''
|
||||
return Object.entries(metrics)
|
||||
.filter(([, value]) => Number.isFinite(value) && value !== 0)
|
||||
.map(([key, value]) => `${metricLabels[key] ?? key} ${value}`)
|
||||
.join(' · ')
|
||||
}
|
||||
|
||||
function statusBadge(task: BackgroundTask) {
|
||||
if (task.status === 'failed') {
|
||||
return <span className="rounded-lg border border-red-400/40 px-1.5 py-0.5 text-xs text-red-500">failed</span>
|
||||
}
|
||||
if (task.status === 'completed') {
|
||||
return <span className="rounded-lg border border-emerald-400/40 px-1.5 py-0.5 text-xs text-emerald-500">done</span>
|
||||
}
|
||||
return <span className="rounded-lg border border-yellow-400/40 px-1.5 py-0.5 text-xs text-yellow-500">running</span>
|
||||
}
|
||||
|
||||
function BackgroundTaskTable({ tasks, empty }: { tasks: BackgroundTask[]; empty: string }) {
|
||||
if (tasks.length === 0) return <p className="text-sand-500">{empty}</p>
|
||||
return (
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="text-xs uppercase tracking-wider text-sand-500">
|
||||
<tr>
|
||||
<th className="py-2">任务</th>
|
||||
<th>阶段</th>
|
||||
<th>状态</th>
|
||||
<th>结果</th>
|
||||
<th>时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tasks.map((task) => (
|
||||
<tr key={task.id} className="border-t border-gray-200 align-top">
|
||||
<td className="max-w-md py-2">
|
||||
<div className="font-medium text-ink-600">{task.name}</div>
|
||||
<div className="truncate font-mono text-xs text-sand-500" title={task.source_path || task.dest_path}>
|
||||
{task.source_path || task.dest_path || task.message || '-'}
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-ink-100">{task.stage || '-'}</td>
|
||||
<td>{statusBadge(task)}</td>
|
||||
<td className="max-w-md text-ink-100">
|
||||
<div>{task.error || task.message || '-'}</div>
|
||||
{formatMetrics(task.metrics) && (
|
||||
<div className="mt-1 text-xs text-sand-500">{formatMetrics(task.metrics)}</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-ink-100">
|
||||
{new Date(task.finished_at || task.updated_at || task.started_at).toLocaleTimeString()}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
// TasksPage shows everything the backend is doing right now: ffmpeg
|
||||
// transcodes + qBittorrent downloads. Refreshes every 3 s.
|
||||
export function TasksPage() {
|
||||
@@ -37,6 +121,7 @@ export function TasksPage() {
|
||||
if (!snap) return <p className="text-sand-500">加载中…</p>
|
||||
|
||||
const torrents = snap.torrents ?? []
|
||||
const background = snap.background_tasks ?? { active: [], recent: [] }
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
@@ -45,6 +130,20 @@ export function TasksPage() {
|
||||
<h1 className="font-display text-3xl font-bold text-ink-600">实时任务</h1>
|
||||
</header>
|
||||
|
||||
<section className="glass-panel">
|
||||
<h2 className="mb-3 font-display text-lg font-semibold text-ink-600">整理 / 重命名 / 入库 / 刮削任务</h2>
|
||||
<div className="space-y-5">
|
||||
<div>
|
||||
<h3 className="mb-2 text-sm font-semibold text-ink-500">运行中</h3>
|
||||
<BackgroundTaskTable tasks={background.active} empty="暂无运行中的整理、重命名、入库或刮削任务。" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-2 text-sm font-semibold text-ink-500">最近完成</h3>
|
||||
<BackgroundTaskTable tasks={background.recent.slice(0, 10)} empty="暂无最近完成的后台任务。" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="glass-panel">
|
||||
<h2 className="mb-3 font-display text-lg font-semibold text-ink-600">转码任务</h2>
|
||||
{snap.transcodes.length === 0 && <p className="text-sand-500">暂无运行中转码。</p>}
|
||||
|
||||
Reference in New Issue
Block a user