jobs.html 3.76 KB
{% 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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");
}

refresh();
setInterval(refresh, 5000);
</script>
{% endblock %}