import { Pencil, Plus, Trash2 } from 'lucide-react' import { useState } from 'react' import { api } from '@shared/api/client' import type { AgentDto, KindDto, Page, ParamDto, TaskDto } from '@shared/api/types' import { fmtTime } from '@shared/format' import { useQuery } from '@shared/hooks' import { Chip, Confirm, Dialog, Empty, ErrorNotice, Field, Segmented, Spinner, useAction } from '@shared/ui' type ScopeFilter = 'all' | 'template' | 'override' | 'target' const kindOf = (t: TaskDto): ScopeFilter => (t.scope === 'target' ? 'target' : t.agent_id ? 'override' : 'template') function summary(params: Record): string { const entries = Object.entries(params) return entries.length === 0 ? '默认参数' : entries.map(([k, v]) => `${k}=${String(v)}`).join(' ') } /** 按 Kind 声明的参数规格生成表单:只提交和默认值不同的那些。 */ function ParamForm({ params, values, onChange }: { params: ParamDto[]; values: Record; onChange: (v: Record) => void }) { const set = (name: string, value: unknown, fallback: unknown) => { const next = { ...values } if (value === '' || value === undefined || value === fallback) delete next[name] else next[name] = value onChange(next) } return (
{params.map((p) => { const current = values[p.name] const hint = [p.description, p.min !== null && p.min !== undefined ? `最小 ${p.min}` : '', p.max !== null && p.max !== undefined ? `最大 ${p.max}` : '', p.unit ?? ''].filter(Boolean).join(' · ') return ( {p.type === 'bool' ? ( ) : p.values && p.values.length > 0 ? ( ) : ( set(p.name, p.type === 'string' ? e.target.value : e.target.value === '' ? '' : Number(e.target.value), p.default)} /> )} ) })}
) } function TaskDialog({ task, kinds, agents, templates, onClose, onSaved }: { task?: TaskDto; kinds: KindDto[]; agents: AgentDto[]; templates: TaskDto[]; onClose: () => void; onSaved: () => void }) { const { busy, run } = useAction() const editing = Boolean(task) const [mode, setMode] = useState<'template' | 'override' | 'target'>(task ? (kindOf(task) as 'template' | 'override' | 'target') : 'override') const [kindId, setKindId] = useState(task?.kind ?? '') const [agentId, setAgentId] = useState(task?.agent_id ?? agents[0]?.id ?? '') const [baseId, setBaseId] = useState(task?.base_task_id ?? '') const [target, setTarget] = useState(task?.target ?? '') const [values, setValues] = useState>(task ? { ...(task.params as Record) } : {}) const [enabled, setEnabled] = useState(task?.enabled ?? true) const base = templates.find((t) => t.id === baseId) const effectiveKind = mode === 'override' ? (base?.kind ?? '') : kindId const kind = kinds.find((k) => k.id === effectiveKind) const kindChoices = kinds.filter((k) => k.scopes.includes(mode === 'target' ? 'target' : 'line')) const submit = async () => { const ok = await run(async () => { if (task) { await api.patch(`/api/admin/tasks/${task.id}`, { params: values, enabled, target: mode === 'target' ? target : undefined }) } else { await api.post('/api/admin/tasks', { scope: mode === 'target' ? 'target' : 'line', kind: effectiveKind, target: mode === 'target' ? target : null, params: values, enabled, agent_id: mode === 'template' ? null : agentId, base_task_id: mode === 'override' ? baseId : null, }) } }, '已保存') if (ok) { onSaved() onClose() } } return ( } >
{!editing ? ( { setMode(v) setValues({}) setKindId('') }} /> ) : null} {mode !== 'template' ? ( ) : null} {mode === 'override' && !editing ? ( ) : null} {mode !== 'override' && !editing ? ( ) : null} {mode === 'target' ? ( setTarget(e.target.value)} /> ) : null} {kind ? ( <> {kind.description ?

{kind.description}

: null} ) : null}
) } export function Tasks() { const tasks = useQuery(() => api.get>('/api/admin/tasks'), []) const kinds = useQuery(() => api.get>('/api/public/catalog/kinds'), []) const agents = useQuery(() => api.get>('/api/admin/agents'), []) const [filter, setFilter] = useState('all') const [editing, setEditing] = useState() const [deleting, setDeleting] = useState() const { run } = useAction() const all = tasks.data?.items ?? [] const list = all.filter((t) => filter === 'all' || kindOf(t) === filter) const agentName = (id: string | null) => (id ? (agents.data?.items.find((a) => a.id === id)?.name ?? id) : '全部 Agent') return ( <>

任务

{tasks.loading && !tasks.data ? : null}
{list.length === 0 ? ( {tasks.data ? '没有任务' : '加载中…'} ) : ( {list.map((t) => { const k = kindOf(t) return ( ) })}
采集方法 类型 作用于 参数 状态 更新
{t.kind} {k === 'template' ? '线路模板' : k === 'override' ? 'Agent 覆盖' : '目标任务'} {agentName(t.agent_id)} {t.target ?
{t.target}
: null}
{summary(t.params as Record)} {fmtTime(t.updated_at)}
)}
{editing && kinds.data && agents.data ? ( kindOf(t) === 'template')} onClose={() => setEditing(undefined)} onSaved={tasks.reload} /> ) : null} {deleting ? ( setDeleting(undefined)} onConfirm={async () => { await run(() => api.del(`/api/admin/tasks/${deleting.id}`), '已删除') tasks.reload() }} > 删除之后,Agent 和 Probe 会在下一次同步时停止这个任务。 ) : null} ) }