import { useState } from 'react'
import { Link, useNavigate, useParams } from 'react-router'
import { useDataProvider, useList, useOne } from '@refinedev/core'
import {
Alert, Button, Card, CardContent, DetailList, Field, PageHeader, PageState, Select, StatusPill,
} from './lib/ui.jsx'
import { useTitle } from './lib/hooks.js'
const stateTone = { queued: 'neutral', executing: 'info', succeeded: 'success', failed: 'danger', needs_confirmation: 'warning', policy_hold: 'warning', cancelled: 'neutral' }
const stateText = { queued: '已排队', executing: '执行中', succeeded: '已成功', failed: '失败', needs_confirmation: '需要人工确认', policy_hold: '策略暂停', cancelled: '已取消' }
const holdText = {
account_paused: '账号已暂停', account_revoked: '账号授权已撤销', binding_missing: '运行环境绑定缺失',
environment_missing: '原运行环境不可用', exit_missing: '固定出口缺失', exit_unhealthy: '固定出口失败或发生漂移',
runtime_stop_pending: '停止结果未知,等待人工核验', runtime_missing: '原运行实例不可用', runtime_lease_expired: '运行实例租约已过期',
execution_lease_expired: '执行租约过期,结果未知', task_result_uncertain: '执行结果不确定', task_policy_hold: '执行器请求策略暂停',
account_version_changed: '账号版本已变化', draft_version_changed: '草稿版本已变化', confirmation_missing: '确认快照缺失',
confirmation_version_changed: '确认版本已变化', binding_version_changed: '环境或出口版本已变化', legacy_confirmation_required: '历史任务结果未知,等待人工核验',
}
const stateLabel = state => stateText[state] || `未知状态:${state}`
const holdLabel = reason => reason ? holdText[reason] || `未知停止原因:${reason}` : '无'
function actionErrorText(error) {
const reason = error?.body?.reason_code
if (error?.status === 409) return `当前版本仍不允许恢复(409):${holdLabel(reason)}`
if (error?.status === 503) return `原资源尚未恢复(503):${holdLabel(reason)}`
return error?.message || '操作失败'
}
function TaskCard({ task }) {
return (
{task.id}
账号:{task.account_id}
确认:{task.confirmation_id || '未确认'}
{holdLabel(task.hold_reason)}
{new Date(task.updated_at || task.created_at).toLocaleString('zh-CN')}
)
}
export function TaskList() {
const [accountFilter, setAccountFilter] = useState('')
const [stateFilter, setStateFilter] = useState('')
const { result, query } = useList({
resource: 'tasks',
filters: [
{ field: 'account_id', value: accountFilter },
{ field: 'state', value: stateFilter },
],
})
const error = query.error
const isPending = query.isPending
const tasks = result.data ?? []
useTitle('CreatorHub · 任务中心')
return (
setAccountFilter(event.target.value)} placeholder="账号 ID" className="h-9 w-full rounded-md border border-[#c4ccd9] bg-white px-3 text-sm focus:border-primary focus:outline-none" />
{error ?
{error.message} : null}
{tasks.map(task => )}
)
}
export function TaskDetail() {
const { id } = useParams()
const navigate = useNavigate()
const dataProvider = useDataProvider()('default')
const [verification, setVerification] = useState('not_executed')
const [busy, setBusy] = useState(false)
const [notice, setNotice] = useState(null)
const { result, error, isPending, query } = useOne({ resource: 'tasks', id, queryOptions: { retry: false } })
const task = result
useTitle('CreatorHub · 任务详情')
if (isPending) return
if (error || !task) return {error?.message || '任务不存在'}
async function runAction(action, data) {
setBusy(true)
setNotice(null)
try {
await dataProvider.taskAction(task.id, action, data)
setNotice({ variant: 'success', text: action === 'verify' ? '人工核验已记录;现在只能按该结论恢复或结束。' : action === 'resume' ? '任务已恢复排队。' : '任务已按核验结论结束。' })
await query.refetch()
} catch (reason) {
setNotice({ variant: 'destructive', text: actionErrorText(reason) })
} finally {
setBusy(false)
}
}
const confirmation = task.confirmation || {}
const unknownHold = task.hold_reason && !holdText[task.hold_reason]
return (
{task.id}}>
{notice ?
{notice.text} : null}
{unknownHold ?
后端返回了未知停止原因“{task.hold_reason}”;为避免不安全操作,本页不提供恢复动作。 : null}
当前状态
{holdLabel(task.hold_reason)}],
['人工核验', task.verification_result || '尚未记录'],
]} />
确认快照
{confirmation.id || '未记录'} · v{confirmation.version || '—'}],
['版本', <>账号版本 {confirmation.account_version || '—'} · 草稿版本 {confirmation.draft_version || '—'}>],
['环境与出口', {confirmation.browser_env_alias || '未记录'} · {confirmation.network_exit_id || '未记录'} · 绑定版本:{confirmation.binding_version || '未记录'}],
]} />
执行关联
{task.browser_env_alias || '未记录'}],
['固定出口', {task.network_exit_id || '未记录'}],
['运行实例 / 绑定版本', {task.runtime_instance_id || '未记录'} · {task.binding_version || '未记录'}],
]} />
{task.browser_env_alias ? : null}
{task.network_exit_id ? : null}
唯一安全动作
{task.readiness_reason ? 当前仍被阻断:{holdLabel(task.readiness_reason)}。修复原资源并刷新后,才会开放恢复。 : null}
{task.allowed_action === 'verify' ? (
<>
先在原环境和原出口核验真实结果。选择“未执行”不会立即重试,仍需下一步显式恢复。
>
) : null}
{task.allowed_action === 'resume' ? : null}
{task.allowed_action === 'finish' ? : null}
{task.allowed_action === 'reconfirm' ? (
<>
版本已变化,旧确认不可继续使用。
>
) : null}
{task.allowed_action ? null : 当前没有可安全执行的动作。
}
执行尝试
{task.attempts?.length ? (
{task.attempts.map(attempt => (
{attempt.id}
{attempt.outcome || '执行中'} · {new Date(attempt.started_at).toLocaleString('zh-CN')}
脱敏证据:{attempt.evidence?.mock_outcome || '无可公开证据'}
))}
) : (
尚未产生执行尝试。
)}
)
}