feat: 定时执行工单 — 新建工单+自动任务管理全面重构

后端:
- Ticket 模型新增 scheduled_at 字段 (DateTime, nullable)
- TicketStatus 新增 SCHEDULED 状态
- 状态转换图: approved→scheduled, scheduled→executing/approved
- execute_ticket: scheduled_at 在未来时进入 scheduled 状态
- ticket_scheduler.py: asyncio 后台定时执行器
- GET /tickets/scheduled/list: 列出定时执行中工单
- POST /tickets/{id}/cancel-schedule: 取消定时执行

前端:
- TicketCreatePage: 新增定时执行 checkbox + datetime-local picker (秒级精度)
- AutoTaskPage: 全面重写为真实定时任务管理页
  - 实时倒计时显示 (Xh Xm 后执行)
  - DB 类型图标 (MySQL/PG/Redis/MongoDB)
  - SQL 预览 + 取消定时执行
  - 每 30 秒自动刷新
This commit is contained in:
logan committed 2026-05-25 00:02:21 +08:00
1 parent 478e663313
commit ffb3d24e2a
6 files changed
+418 -262

No files matched your search

+24
View File
@@ -98,6 +98,8 @@ export default function TicketCreatePage() {
const [database, setDatabase] = useState('');
const [sqlContent, setSqlContent] = useState('');
const [reason, setReason] = useState('');
const [scheduledAt, setScheduledAt] = useState('');
const [enableSchedule, setEnableSchedule] = useState(false);
// -- Data / Loading --
const [instances, setInstances] = useState<InstanceItem[]>([]);
@@ -287,6 +289,7 @@ export default function TicketCreatePage() {
sql_content: sqlContent,
reason: reason.trim(),
priority: 'medium',
scheduled_at: enableSchedule && scheduledAt ? new Date(scheduledAt).toISOString() : undefined,
});
navigate('/tickets');
} catch (e: unknown) {
@@ -700,6 +703,27 @@ export default function TicketCreatePage() {
</div>
</div>
{/* 定时执行 */}
<div className="flex items-center gap-4">
<label className="flex items-center gap-2 cursor-pointer select-none">
<input type="checkbox" checked={enableSchedule} onChange={(e) => setEnableSchedule(e.target.checked)}
className="w-4 h-4 rounded border-[var(--dg-border)] bg-[var(--dg-bg-primary)] text-blue-500 focus:ring-blue-500 focus:ring-offset-0" />
<Clock className="w-4 h-4 text-[var(--dg-text-muted)]" />
<span className="text-sm">定时执行</span>
</label>
{enableSchedule && (
<input type="datetime-local" value={scheduledAt} onChange={(e) => setScheduledAt(e.target.value)}
step="1"
className="px-3 py-2 bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-lg text-sm focus:outline-none focus:border-blue-500 transition-colors"
min={new Date().toISOString().slice(0, 16)} />
)}
{enableSchedule && scheduledAt && (
<span className="text-xs text-[var(--dg-text-muted)]">
将于 {new Date(scheduledAt).toLocaleString('zh-CN')} 执行
</span>
)}
</div>
{/* SQL Editor + Browser Panel */}
<div className="grid grid-cols-1 lg:grid-cols-5 gap-4">
<div className="lg:col-span-3 space-y-3">