Fix timestamps missing after restart + align detail modal
Docker Build and Push / check-secrets (push) Successful in 6s
Docker Build and Push / build-and-push (cpu, latest) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 11.8) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 12.6) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 12.8) (push) Has been skipped
Docker Build and Push / build-and-push (directml, latest) (push) Has been skipped
Build Windows CPU / build (push) Has been cancelled
Build Windows CUDA 11.8 / build (push) Has been cancelled
Build Windows CUDA 12.6 / build (push) Has been cancelled
Build Windows CUDA 12.8 / build (push) Has been cancelled
Build Windows DirectML / build (push) Has been cancelled

- Add started_at / completed_at columns to tasks table (were missing)
- Migration adds columns to existing DB without data loss
- _save_task_to_db now persists created_at / started_at / completed_at
- _load_tasks_from_db restores all three timestamp fields on startup
- Detail modal shows same created/started/elapsed layout as table row
  with identical fmtTime / fmtDur helpers, live elapsed for in-progress tasks

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
liuchen
2026-06-27 14:48:58 +08:00
co-authored by Claude Haiku 4.5
parent d5468ec958
commit 76f5d1c857
2 changed files with 42 additions and 15 deletions
+25 -10
View File
@@ -1007,9 +1007,18 @@ function openDetail(id) {
el.className = `badge ${cls}`;
el.innerHTML = `<span class="bdot"></span>${label}`;
const dur = (t.started_at && t.completed_at)
? `${Math.round(t.completed_at - t.started_at)}s`
: (t.started_at ? '进行中…' : '-');
// 与列表完全一致的格式函数
const fmtTime = ts => ts ? new Date(ts*1000).toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit',second:'2-digit'}) : '-';
const fmtDur = () => {
if (!t.started_at) return '-';
const end = t.completed_at || (Date.now()/1000);
const s = Math.round(end - t.started_at);
const m = Math.floor(s/60), sec = s%60;
const str = m > 0 ? `${m}m ${sec}s` : `${sec}s`;
return t.completed_at ? str : `<span style="color:var(--warn)">${str}…</span>`;
};
const modeLabel = { 'sttn-auto':'STTN 自动','sttn-det':'STTN','lama':'LaMa','propainter':'ProPainter','opencv':'OpenCV' };
const body = document.getElementById('detailBody');
body.innerHTML = `
@@ -1017,23 +1026,29 @@ function openDetail(id) {
<div class="item-row">
<span class="item-title" style="word-break:break-all">${esc(t.url)}</span>
</div>
<div class="subtle mono">${esc(t.id)}</div>
<div class="subtle">算法: ${t.inpaint_mode} · 检测: ${t.detect_mode} · 耗时: ${dur}</div>
${t.error ? `<div class="badge bad" style="align-self:start;word-break:break-all">${esc(t.error)}</div>` : ''}
<div class="subtle mono" style="margin-top:2px">${esc(t.id)}</div>
<div style="display:grid;grid-template-columns:repeat(3,auto);gap:4px 20px;margin-top:8px;width:fit-content">
<span class="subtle">创建时间</span><span class="subtle">开始处理</span><span class="subtle">耗时</span>
<span style="font-size:13px">${fmtTime(t.created_at)}</span>
<span style="font-size:13px">${fmtTime(t.started_at)}</span>
<span style="font-size:13px">${fmtDur()}</span>
</div>
<div class="subtle" style="margin-top:6px">算法: ${modeLabel[t.inpaint_mode]||t.inpaint_mode} · 检测: ${t.detect_mode==='smart'?'水印模式':'文字 OCR'}</div>
${t.error ? `<div class="badge bad" style="align-self:start;word-break:break-all;margin-top:4px">${esc(t.error)}</div>` : ''}
${t.status==='downloading' ? `
<div style="margin-top:4px">
<div style="margin-top:8px">
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">下载进度 ${t.download_progress||0}%</div>
<div class="prog-track"><div class="prog-fill dl" style="width:${t.download_progress||0}%"></div></div>
</div>
` : ''}
${t.status==='processing' ? `
<div style="margin-top:4px;display:grid;gap:5px">
<div style="margin-top:8px;display:grid;gap:5px">
<div class="phase-row"><span class="phase-label">检测</span><div class="phase-track"><div class="phase-fill detect" style="width:${t.detect_pct||0}%"></div></div><span class="phase-pct">${t.detect_pct||0}%</span></div>
<div class="phase-row"><span class="phase-label">处理</span><div class="phase-track"><div class="phase-fill process" style="width:${t.process_pct||0}%"></div></div><span class="phase-pct">${t.process_pct||0}%</span></div>
<div class="phase-row"><span class="phase-label">合并</span><div class="phase-track"><div class="phase-fill merge" style="width:${t.merge_pct||0}%"></div></div><span class="phase-pct">${t.merge_pct||0}%</span></div>
</div>
` : ''}
<div style="display:flex;gap:8px;margin-top:4px;flex-wrap:wrap">
<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap">
${t.cache_path ? `
<button class="button secondary" style="min-height:32px;padding:0 14px;font-size:13px"
onclick="playVideo('${t.id}', 'source')">▶ 原视频</button>
@@ -1052,7 +1067,7 @@ function openDetail(id) {
background:#f7f9fc;border:1px solid var(--line);border-radius:var(--radius-sm);
padding:12px 14px;max-height:240px;overflow-y:auto;
font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.7;
">${(t.logs||[]).map(l => `<div>${esc(l)}</div>`).join('') || '<div style="color:var(--muted)">暂无日志</div>'}</div>
">${(t.logs||[]).map(l => `<div>${esc(l)}</div>`).join('') || '<div style="color:var(--muted)">暂无日志(点「日志」按钮从数据库加载完整记录)</div>'}</div>
</div>
`;
openModal('modal-detail');
+17 -5
View File
@@ -167,7 +167,9 @@ def init_db():
output_path TEXT,
error TEXT,
platform_task_id INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at REAL,
started_at REAL,
completed_at REAL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
@@ -186,9 +188,15 @@ def init_db():
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_task_logs_task_id ON task_logs(task_id)")
for col in ("preview_mode", "detect_mode", "platform_task_id"):
for col, default in [
("preview_mode", "BOOLEAN DEFAULT 0"),
("detect_mode", "TEXT DEFAULT 'text'"),
("platform_task_id", "INTEGER"),
("started_at", "REAL"),
("completed_at", "REAL"),
]:
try:
conn.execute(f"ALTER TABLE tasks ADD COLUMN {col} {'BOOLEAN DEFAULT 0' if col == 'preview_mode' else ('TEXT DEFAULT ' + repr('text') if col == 'detect_mode' else 'INTEGER')}")
conn.execute(f"ALTER TABLE tasks ADD COLUMN {col} {default}")
except Exception:
pass
conn.commit()
@@ -281,8 +289,9 @@ def _save_task_to_db(task: dict):
INSERT OR REPLACE INTO tasks
(id, url, is_cache_file, cache_path, inpaint_mode, subtitle_areas,
preview_mode, detect_mode, status, progress, download_progress,
cached, output_path, error, platform_task_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
cached, output_path, error, platform_task_id,
created_at, started_at, completed_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
task["id"], task["url"], task.get("is_cache_file", False),
task.get("cache_path", ""), task["inpaint_mode"],
@@ -290,6 +299,7 @@ def _save_task_to_db(task: dict):
task.get("detect_mode", "text"), task["status"], task["progress"],
task["download_progress"], task["cached"], task["output_path"],
task["error"], task.get("platform_task_id"),
task.get("created_at"), task.get("started_at"), task.get("completed_at"),
))
conn.commit()
conn.close()
@@ -317,6 +327,8 @@ def _load_tasks_from_db():
"download_progress": r["download_progress"],
"cached": r["cached"], "output_path": r["output_path"],
"error": r["error"], "platform_task_id": r.get("platform_task_id"),
"created_at": r.get("created_at"), "started_at": r.get("started_at"),
"completed_at": r.get("completed_at"),
"logs": [],
}
with task_lock: