cron_job.html
3.9 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
81
82
83
84
85
86
87
88
89
90
91
{% extends "base.html" %}
{% block title %}{{ job.name }} - Cron 日志{% endblock %}
{% block page_title %}/ cron / {{ job.name }}{% endblock %}
{% block body %}
<style>
body{overflow-y:auto!important;height:auto!important;margin:0;padding:0;background:#1e1e1e;color:#27ae60;}
.cron-job-page{max-width:1100px;margin:0 auto;padding:24px 20px;min-height:100vh;}
.job-header{display:flex;align-items:center;gap:14px;margin-bottom:28px;flex-wrap:wrap;padding-bottom:18px;border-bottom:1px solid #333;}
.job-header .back-btn{color:#27ae60;text-decoration:none;font-size:18px;transition:.15s;flex-shrink:0;}
.job-header .back-btn:hover{color:#2ecc71;}
.job-header h1{font-size:22px;color:#e0e0e0;margin:0;flex:1;min-width:0;}
.job-header .meta{display:flex;align-items:center;gap:12px;flex-wrap:wrap;}
.job-header .schedule{font-size:12px;color:#888;background:#2d2d2d;padding:4px 12px;border-radius:10px;}
.job-header .status-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;}
.job-header .status-dot.on{background:#27ae60;box-shadow:0 0 6px #27ae60;}
.job-header .status-dot.off{background:#555;}
.job-header .status-dot.paused{background:#f39c12;}
.job-header .refresh-btn{cursor:pointer;font-size:12px;color:#27ae60;background:#2d2d2d;border:1px solid #333;padding:6px 14px;border-radius:6px;transition:.15s;}
.job-header .refresh-btn:hover{background:#3d3d3d;}
.timeline{position:relative;padding-left:24px;}
.timeline::before{content:'';position:absolute;left:6px;top:0;bottom:0;width:1px;background:#333;}
.log-entry{position:relative;margin-bottom:20px;padding:16px 20px;background:#252525;border-radius:8px;border:1px solid #333;transition:.15s;}
.log-entry:hover{border-color:#27ae60;}
.log-entry .log-dot{position:absolute;left:-22px;top:20px;width:8px;height:8px;border-radius:50%;background:#27ae60;}
.log-entry .log-time{font-size:12px;color:#888;margin-bottom:8px;font-family:monospace;}
.log-entry .log-content{font-size:13px;color:#b0b0b0;font-family:monospace;white-space:pre-wrap;word-break:break-all;line-height:1.6;max-height:none;overflow:visible;}
.empty-state{text-align:center;padding:60px 20px;color:#666;}
.empty-state .icon{font-size:48px;margin-bottom:12px;}
.empty-state p{font-size:15px;}
.loading{text-align:center;padding:40px;color:#666;font-size:14px;}
</style>
<div class="cron-job-page">
<div class="job-header">
<a href="/cron" class="back-btn">←</a>
<span class="status-dot {{ 'on' if job.enabled else 'paused' }}"></span>
<h1>{{ job.name }}</h1>
<div class="meta">
<span class="schedule">{{ job.schedule }}</span>
<button class="refresh-btn" onclick="loadLogs()">🔄 刷新</button>
</div>
</div>
<div id="log-area" class="loading">加载中...</div>
</div>
<script>
var JOB_ID = "{{ job.id }}";
function escHtml(s) {
var d = document.createElement('div');
d.appendChild(document.createTextNode(s));
return d.innerHTML;
}
function loadLogs() {
var el = document.getElementById('log-area');
el.className = 'loading';
el.innerHTML = '加载中...';
fetch('/api/cron/logs/' + JOB_ID).then(function(r){return r.json()}).then(function(j){
if (!j.ok || !j.data || !j.data.length) {
el.className = 'empty-state';
el.innerHTML = '<div class="icon">📄</div><p>暂无执行记录</p>';
return;
}
var h = '<div class="timeline">';
for (var i = 0; i < j.data.length; i++) {
var log = j.data[i];
var ts = log.timestamp || log.filename || '';
var content = log.content || '';
h += '<div class="log-entry">';
h += '<div class="log-dot"></div>';
h += '<div class="log-time">' + escHtml(ts) + '</div>';
h += '<div class="log-content">' + escHtml(content) + '</div>';
h += '</div>';
}
h += '</div>';
el.innerHTML = h;
}).catch(function(e){
el.innerHTML = '<div class="empty-state"><div class="icon">⚠️</div><p>加载失败: ' + e.message + '</p></div>';
});
}
loadLogs();
</script>
{% endblock %}