Files
MeBox/web/src/pages/SchedulerPage.tsx
T
ShukeBta 5baf9515ea TG Bot 命令交互 + UI 圆角色差深度优化 + 通知配置修复
## TG Bot 交互命令系统 (新增)
- telegram_bot.go: 命令路由 + /start /help /status /search /downloads /stats
- telegram_webhook.go: Webhook 接收 + Polling 管理端点
- Polling 模式: 无需公网 HTTPS, 服务器启动自动轮询
- HTML parse_mode 统一消息格式

## 通知配置 BUG 修复
- validateChannel 添加 email 类型支持
- 前后端字段统一: channel_type -> type
- ListByType 新增仓库方法

## UI 深度优化
- glass-panel: 添加 rounded-2xl + 可见边框 + padding
- card: 添加 p-4 sm:p-6
- 全局 rounded-md -> rounded-xl, 裸 rounded -> rounded-lg
- 500+ 深色硬编码类名清零
- text-gray-400 -> gray-500 色差提升
- 按钮组 flex-wrap 防溢出
- data-table 表格溢出截断
2026-05-28 08:35:59 +08:00

85 lines
2.9 KiB
TypeScript

import { useEffect, useState } from 'react'
import toast from 'react-hot-toast'
import { Clock, Play } from 'lucide-react'
import { schedulerAPI, type JobStatus } from '../api/scheduler'
export function SchedulerPage() {
const [jobs, setJobs] = useState<JobStatus[]>([])
const [running, setRunning] = useState<string>('')
const refresh = () => schedulerAPI.status().then(setJobs)
useEffect(() => {
refresh().catch(() => undefined)
const id = window.setInterval(refresh, 5_000)
return () => window.clearInterval(id)
}, [])
const runNow = async (name: string) => {
setRunning(name)
try {
await schedulerAPI.run(name)
toast.success(`${name} 已运行`)
await refresh()
} catch (err: unknown) {
const msg =
(err as { response?: { data?: { error?: string } } })?.response?.data?.error ??
'运行失败'
toast.error(msg)
} finally {
setRunning('')
}
}
return (
<div className="space-y-6">
<header className="flex items-center gap-3">
<Clock className="h-6 w-6 text-brand-500" />
<div>
<h1 className="font-display text-3xl font-bold text-ink-600">定时任务</h1>
<p className="text-sm text-ink-50">
后端周期性任务(媒体库扫描、转码缓存清理、回收站自动清理),每 5 秒刷新状态。
</p>
</div>
</header>
<div className="glass-panel">
<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 className="text-right">操作</th>
</tr>
</thead>
<tbody>
{jobs.map((j) => (
<tr key={j.name} className="border-t border-gray-200">
<td className="py-2 font-mono text-ink-600">{j.name}</td>
<td className="text-ink-100">{j.interval}</td>
<td className="text-ink-50">
{j.last_run && new Date(j.last_run).getFullYear() > 2000
? new Date(j.last_run).toLocaleString()
: '尚未运行'}
</td>
<td className="text-red-400">{j.last_err || '—'}</td>
<td className="py-2 text-right">
<button
onClick={() => runNow(j.name)}
disabled={running === j.name}
className="rounded-lg border border-primary-400/40 px-2 py-1 text-xs text-brand-500 hover:bg-primary-400/10"
>
<Play size={12} className="inline" /> 立即运行
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}