jobs.html
3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
{% extends "base.html" %}
{% block title %}任务监控 - WeChat Viewer{% endblock %}
{% block body %}
<style>
body{overflow-y:auto!important;height:auto!important;}
.jobs-container{max-width:960px;margin:0 auto;padding:20px;}
.jobs-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;}
.jobs-header h1{font-size:20px;color:#2c3e50;}
.jobs-header button{padding:6px 14px;background:#3498db;color:white;border:none;border-radius:6px;cursor:pointer;font-size:13px;}
.job-card{display:flex;align-items:center;gap:14px;background:white;border-radius:8px;padding:14px 18px;margin-bottom:10px;box-shadow:0 1px 2px rgba(0,0,0,0.05);cursor:pointer;text-decoration:none;color:inherit;}
.job-card:hover{background:#f8f9fa;}
.job-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;}
.job-dot.running{background:#27ae60;}
.job-dot.completed{background:#3498db;}
.job-dot.failed{background:#e74c3c;}
.job-info{flex:1;min-width:0;}
.job-info .name{font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.job-info .meta{font-size:11px;color:#999;margin-top:3px;}
.job-badge{font-size:10px;padding:2px 8px;border-radius:10px;font-weight:600;flex-shrink:0;}
.job-badge.daily_report{background:#2c3e50;color:white;}
.job-badge.report_image{background:#e67e22;color:white;}
.job-badge.article_summary{background:#3498db;color:white;}
.job-badge.article_learn{background:#9b59b6;color:white;}
.job-badge.article_image{background:#1abc9c;color:white;}
.job-arrow{color:#ccc;font-size:18px;}
.empty-state{text-align:center;padding:60px 20px;color:#999;}
.empty-state .icon{font-size:48px;margin-bottom:12px;}
.loading{text-align:center;padding:40px;color:#999;}
.spinner{display:inline-block;width:24px;height:24px;border:3px solid #ddd;border-top-color:#3498db;border-radius:50%;animation:spin .6s linear infinite;}
@keyframes spin{to{transform:rotate(360deg)}}
</style>
<div class="jobs-container">
<div class="jobs-header">
<h1>🔧 任务监控</h1>
<button onclick="refresh()">🔄 刷新</button>
</div>
<div id="list"></div>
</div>
<script>
var TYPES = {daily_report:"日报生成",report_image:"日报图片",article_summary:"文章摘要",article_learn:"文章学习",article_image:"文章图片"};
var STATS = {running:"运行中",completed:"已完成",failed:"失败"};
function refresh() {
var el = document.getElementById("list");
el.innerHTML = '<div class="loading"><div class="spinner"></div><p style="margin-top:12px;">加载中...</p></div>';
fetch("/api/jobs").then(function(r){return r.json()}).then(function(j){
if (!j.ok || !j.data.length) {
el.innerHTML = '<div class="empty-state"><div class="icon">📋</div><p>暂无任务</p></div>';
return;
}
var h = "";
for (var i = 0; i < j.data.length; i++) {
var t = j.data[i];
var tn = TYPES[t.job_type] || t.job_type;
var sn = STATS[t.status] || t.status;
var name = t.job_name || "未命名";
var meta = t.started_at || "";
if (t.completed_at) meta += " → " + t.completed_at;
meta += " · " + sn;
h += "<a href=/job/" + t.task_id + " class=job-card>";
h += "<span class='job-dot " + t.status + "'></span>";
h += "<div class=job-info><div class=name>" + escHtml(name) + "</div>";
h += "<div class=meta>" + escHtml(meta) + "</div></div>";
h += "<span class='job-badge " + t.job_type + "'>" + escHtml(tn) + "</span>";
h += "<span class=job-arrow>›</span></a>";
}
el.innerHTML = h;
}).catch(function(e){
el.innerHTML = '<div class="empty-state"><p>加载失败: ' + e.message + '</p></div>';
});
}
function escHtml(s) {
return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");
}
refresh();
setInterval(refresh, 5000);
</script>
{% endblock %}