fix(production-readiness): 3-batch production readiness cleanup — 12 tasks
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

Batch 1 — User-facing fixes:
- B1-1: Pipeline verified end-to-end (14 Rust commands, 8 frontend invoke, fully connected)
- B1-2: MessageSearch restored to ChatArea with search button in DeerFlow header
- B1-3: Viking cleanup — removed 5 orphan invokes (no Rust impl), added addWithMetadata + storeWithSummaries methods + summary generation UI
- B1-4: api-fallbacks transparency — added _isFallback markers + console.warn to all 6 fallback functions

Batch 2 — System health:
- B2-1: Document drift calibration — TRUTH.md/README.md numbers verified and updated
- B2-2: @reserved annotations on 15 SaaS handler functions with no frontend callers
- B2-3: Scheduled Task Admin V2 — new service + page + route + sidebar navigation
- B2-4: TRUTH.md Pipeline/Viking/ScheduledTask records corrected

Batch 3 — Long-term quality:
- B3-1: hand_run_status/hand_run_list verified as fully implemented (not stubs)
- B3-2: Identity snapshot rollback UI added to RightPanel
- B3-3: P2 code quality — 4 fixes (TODO comments, fire-and-forget notes, design notes, table name validation), 2 verified N/A, 1 upstream
- B3-4: Config PATCH→PUT alignment (admin-v2 config.ts matched to SaaS backend)
This commit is contained in:
iven
2026-04-03 21:34:56 +08:00
parent 305984c982
commit 2ceeeaba3d
17 changed files with 1157 additions and 81 deletions

View File

@@ -0,0 +1,397 @@
// ============================================================
// 定时任务 — 管理页面
// ============================================================
import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Button, message, Tag, Modal, Form, Input, Select, Switch, Popconfirm, Space } from 'antd'
import type { ProColumns } from '@ant-design/pro-components'
import { ProTable } from '@ant-design/pro-components'
import { PlusOutlined } from '@ant-design/icons'
import { scheduledTaskService } from '@/services/scheduled-tasks'
import type { ScheduledTask, CreateScheduledTaskRequest, UpdateScheduledTaskRequest } from '@/services/scheduled-tasks'
import { PageHeader } from '@/components/PageHeader'
import { ErrorState } from '@/components/ErrorState'
const scheduleTypeLabels: Record<string, string> = {
cron: 'Cron',
interval: '间隔',
once: '一次性',
}
const scheduleTypeColors: Record<string, string> = {
cron: 'blue',
interval: 'green',
once: 'orange',
}
const targetTypeLabels: Record<string, string> = {
agent: 'Agent',
hand: 'Hand',
workflow: 'Workflow',
}
const targetTypeColors: Record<string, string> = {
agent: 'purple',
hand: 'cyan',
workflow: 'geekblue',
}
function formatDateTime(value: string | null): string {
if (!value) return '-'
return new Date(value).toLocaleString('zh-CN')
}
function formatDuration(ms: number | null): string {
if (ms === null) return '-'
if (ms < 1000) return `${ms}ms`
return `${(ms / 1000).toFixed(2)}s`
}
export default function ScheduledTasks() {
const queryClient = useQueryClient()
const [form] = Form.useForm()
const [modalOpen, setModalOpen] = useState(false)
const [editingId, setEditingId] = useState<string | null>(null)
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['scheduled-tasks'],
queryFn: ({ signal }) => scheduledTaskService.list(signal),
})
const createMutation = useMutation({
mutationFn: (data: CreateScheduledTaskRequest) => scheduledTaskService.create(data),
onSuccess: () => {
message.success('任务创建成功')
queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] })
closeModal()
},
onError: (err: Error) => message.error(err.message || '创建失败'),
})
const updateMutation = useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateScheduledTaskRequest }) =>
scheduledTaskService.update(id, data),
onSuccess: () => {
message.success('任务更新成功')
queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] })
closeModal()
},
onError: (err: Error) => message.error(err.message || '更新失败'),
})
const deleteMutation = useMutation({
mutationFn: (id: string) => scheduledTaskService.delete(id),
onSuccess: () => {
message.success('任务已删除')
queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] })
},
onError: (err: Error) => message.error(err.message || '删除失败'),
})
const toggleMutation = useMutation({
mutationFn: ({ id, enabled }: { id: string; enabled: boolean }) =>
scheduledTaskService.update(id, { enabled }),
onSuccess: () => {
message.success('状态已更新')
queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] })
},
onError: (err: Error) => message.error(err.message || '状态更新失败'),
})
const columns: ProColumns<ScheduledTask>[] = [
{
title: '任务名称',
dataIndex: 'name',
width: 160,
ellipsis: true,
},
{
title: '调度规则',
dataIndex: 'schedule',
width: 140,
ellipsis: true,
hideInSearch: true,
},
{
title: '调度类型',
dataIndex: 'schedule_type',
width: 100,
valueType: 'select',
valueEnum: {
cron: { text: 'Cron' },
interval: { text: '间隔' },
once: { text: '一次性' },
},
render: (_, record) => (
<Tag color={scheduleTypeColors[record.schedule_type]}>
{scheduleTypeLabels[record.schedule_type] || record.schedule_type}
</Tag>
),
},
{
title: '目标',
dataIndex: ['target', 'type'],
width: 140,
hideInSearch: true,
render: (_, record) => (
<Space size={4}>
<Tag color={targetTypeColors[record.target.type]}>
{targetTypeLabels[record.target.type] || record.target.type}
</Tag>
<span className="text-xs text-neutral-500 dark:text-neutral-400">{record.target.id}</span>
</Space>
),
},
{
title: '启用',
dataIndex: 'enabled',
width: 80,
hideInSearch: true,
render: (_, record) => (
<Switch
size="small"
checked={record.enabled}
onChange={(checked) => toggleMutation.mutate({ id: record.id, enabled: checked })}
/>
),
},
{
title: '执行次数',
dataIndex: 'run_count',
width: 90,
hideInSearch: true,
render: (_, record) => (
<span className="tabular-nums">{record.run_count}</span>
),
},
{
title: '上次执行',
dataIndex: 'last_run',
width: 170,
hideInSearch: true,
render: (_, record) => formatDateTime(record.last_run),
},
{
title: '下次执行',
dataIndex: 'next_run',
width: 170,
hideInSearch: true,
render: (_, record) => formatDateTime(record.next_run),
},
{
title: '上次耗时',
dataIndex: 'last_duration_ms',
width: 100,
hideInSearch: true,
render: (_, record) => formatDuration(record.last_duration_ms),
},
{
title: '上次错误',
dataIndex: 'last_error',
width: 160,
ellipsis: true,
hideInSearch: true,
render: (_, record) =>
record.last_error ? (
<span className="text-red-500 text-xs">{record.last_error}</span>
) : (
<span className="text-neutral-400">-</span>
),
},
{
title: '操作',
width: 140,
hideInSearch: true,
render: (_, record) => (
<Space>
<Button
size="small"
onClick={() => openEditModal(record)}
>
</Button>
<Popconfirm
title="确定删除此任务?"
description="删除后无法恢复"
onConfirm={() => deleteMutation.mutate(record.id)}
>
<Button size="small" danger></Button>
</Popconfirm>
</Space>
),
},
]
const openCreateModal = () => {
setEditingId(null)
form.resetFields()
form.setFieldsValue({ schedule_type: 'cron', enabled: true })
setModalOpen(true)
}
const openEditModal = (record: ScheduledTask) => {
setEditingId(record.id)
form.setFieldsValue({
name: record.name,
schedule: record.schedule,
schedule_type: record.schedule_type,
target_type: record.target.type,
target_id: record.target.id,
description: record.description ?? '',
enabled: record.enabled,
})
setModalOpen(true)
}
const closeModal = () => {
setModalOpen(false)
setEditingId(null)
form.resetFields()
}
const handleSave = async () => {
const values = await form.validateFields()
const payload: CreateScheduledTaskRequest | UpdateScheduledTaskRequest = {
name: values.name,
schedule: values.schedule,
schedule_type: values.schedule_type,
target: {
type: values.target_type,
id: values.target_id,
},
description: values.description || undefined,
enabled: values.enabled,
}
if (editingId) {
updateMutation.mutate({ id: editingId, data: payload })
} else {
createMutation.mutate(payload as CreateScheduledTaskRequest)
}
}
if (error) {
return (
<>
<PageHeader title="定时任务" description="管理系统定时任务的创建、调度与执行" />
<ErrorState message={(error as Error).message} onRetry={() => refetch()} />
</>
)
}
const tasks = Array.isArray(data) ? data : []
return (
<div>
<PageHeader
title="定时任务"
description="管理系统定时任务的创建、调度与执行"
actions={
<Button
type="primary"
icon={<PlusOutlined />}
onClick={openCreateModal}
>
</Button>
}
/>
<ProTable<ScheduledTask>
columns={columns}
dataSource={tasks}
loading={isLoading}
rowKey="id"
search={false}
toolBarRender={() => []}
pagination={{
showSizeChanger: true,
defaultPageSize: 20,
}}
options={{
density: false,
fullScreen: false,
reload: () => refetch(),
}}
/>
<Modal
title={
<span className="text-base font-semibold">
{editingId ? '编辑任务' : '新建任务'}
</span>
}
open={modalOpen}
onOk={handleSave}
onCancel={closeModal}
confirmLoading={createMutation.isPending || updateMutation.isPending}
width={520}
destroyOnClose
>
<Form form={form} layout="vertical" className="mt-4">
<Form.Item
name="name"
label="任务名称"
rules={[{ required: true, message: '请输入任务名称' }]}
>
<Input placeholder="例如:每日数据汇总" />
</Form.Item>
<Form.Item
name="schedule_type"
label="调度类型"
rules={[{ required: true, message: '请选择调度类型' }]}
>
<Select
options={[
{ value: 'cron', label: 'Cron 表达式' },
{ value: 'interval', label: '固定间隔' },
{ value: 'once', label: '一次性执行' },
]}
/>
</Form.Item>
<Form.Item
name="schedule"
label="调度规则"
rules={[{ required: true, message: '请输入调度规则' }]}
extra="Cron: 0 8 * * * 间隔: 30m / 1h / 24h 一次性: 2025-12-31T00:00:00Z"
>
<Input placeholder="0 8 * * *" />
</Form.Item>
<Form.Item
name="target_type"
label="目标类型"
rules={[{ required: true, message: '请选择目标类型' }]}
>
<Select
options={[
{ value: 'agent', label: 'Agent' },
{ value: 'hand', label: 'Hand' },
{ value: 'workflow', label: 'Workflow' },
]}
/>
</Form.Item>
<Form.Item
name="target_id"
label="目标 ID"
rules={[{ required: true, message: '请输入目标 ID' }]}
>
<Input placeholder="目标唯一标识符" />
</Form.Item>
<Form.Item name="description" label="描述">
<Input.TextArea rows={3} placeholder="可选的任务描述" />
</Form.Item>
<Form.Item name="enabled" label="启用" valuePropName="checked">
<Switch />
</Form.Item>
</Form>
</Modal>
</div>
)
}