cron_job.html 3.9 KB
{% 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 %}