127 lines
5.3 KiB
Plaintext
127 lines
5.3 KiB
Plaintext
<%- include('../partials/admin-header') %>
|
|
<h1>监控任务 & 检测记录</h1>
|
|
|
|
<% if (flash) { %>
|
|
<div class="flash-msg"><%= decodeURIComponent(flash) %></div>
|
|
<% } %>
|
|
|
|
<div class="form-card">
|
|
<h2>创建监控任务</h2>
|
|
<form method="POST" action="/admin/tasks">
|
|
<div class="form-row" style="display:flex;flex-wrap:wrap;gap:16px;align-items:end">
|
|
<div style="flex:1;min-width:200px">
|
|
<label>产品</label>
|
|
<input type="text" id="product-search" placeholder="搜索产品..." autocomplete="off" style="width:100%;margin-bottom:4px">
|
|
<select name="product_id" id="product-select" required style="width:100%;max-height:300px">
|
|
<option value="">选择产品</option>
|
|
<% products.forEach(p => { %>
|
|
<option value="<%= p.id %>"><%= p.merchant_name %> — <%= p.name %><% if(p.location) { %> (<%= p.location %>)<% } %></option>
|
|
<% }) %>
|
|
</select>
|
|
</div>
|
|
<div style="min-width:150px">
|
|
<label>推送频道</label>
|
|
<select name="tg_channel_id" style="width:100%">
|
|
<option value="">不推送</option>
|
|
<% channels.forEach(c => { %>
|
|
<option value="<%= c.id %>"><%= c.name %></option>
|
|
<% }) %>
|
|
</select>
|
|
</div>
|
|
<div style="min-width:150px">
|
|
<label>Cron 表达式</label>
|
|
<input name="cron_expr" value="*/5 * * * *" placeholder="*/5 * * * *" style="width:100%">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">创建</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('product-search').addEventListener('input', function(e) {
|
|
var search = e.target.value.toLowerCase();
|
|
var select = document.getElementById('product-select');
|
|
var options = select.querySelectorAll('option');
|
|
options.forEach(function(opt) {
|
|
if (!opt.value) return;
|
|
opt.style.display = opt.text.toLowerCase().indexOf(search) >= 0 ? '' : 'none';
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<div class="form-card" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
|
|
<h2 style="margin:0">调度器</h2>
|
|
<span class="badge <%= schedulerRunning ? 'badge-on' : 'badge-off' %>"><%= schedulerRunning ? '运行中' : '已停止' %></span>
|
|
<% if (schedulerRunning) { %>
|
|
<form method="POST" action="/admin/tasks/scheduler/stop" class="inline">
|
|
<button class="btn btn-danger btn-sm">停止调度器</button>
|
|
</form>
|
|
<% } else { %>
|
|
<form method="POST" action="/admin/tasks/scheduler/start" class="inline">
|
|
<button class="btn btn-primary btn-sm">启动调度器</button>
|
|
</form>
|
|
<% } %>
|
|
<form method="POST" action="/admin/tasks/test-push" class="inline" style="margin-left:auto">
|
|
<button class="btn btn-sm" style="background:#28a745;color:#fff">🧪 测试 TG 推送</button>
|
|
</form>
|
|
</div>
|
|
|
|
<h2 style="margin-bottom:12px">任务列表</h2>
|
|
<table>
|
|
<thead><tr><th>ID</th><th>产品</th><th>频道</th><th>Cron</th><th>状态</th><th>上次运行</th><th>操作</th></tr></thead>
|
|
<tbody>
|
|
<% tasks.forEach(t => { %>
|
|
<tr>
|
|
<td><%= t.id %></td>
|
|
<td><%= t.merchant_name %> — <%= t.product_name %></td>
|
|
<td><%= t.channel_name || '-' %></td>
|
|
<td><code><%= t.cron_expr %></code></td>
|
|
<td><span class="badge <%= t.enabled ? 'badge-on' : 'badge-off' %>"><%= t.enabled ? '启用' : '禁用' %></span></td>
|
|
<td><%= t.last_run || '-' %></td>
|
|
<td>
|
|
<form class="inline" method="POST" action="/admin/tasks/<%= t.id %>/run">
|
|
<button class="btn btn-sm" style="background:#17a2b8;color:#fff" title="手动执行一次检测">▶ 执行</button>
|
|
</form>
|
|
<form class="inline" method="POST" action="/admin/tasks/<%= t.id %>/push">
|
|
<button class="btn btn-sm" style="background:#6f42c1;color:#fff" title="手动推送产品消息">📨 推送</button>
|
|
</form>
|
|
<form class="inline" method="POST" action="/admin/tasks/<%= t.id %>/toggle">
|
|
<button class="btn btn-sm btn-primary"><%= t.enabled ? '禁用' : '启用' %></button>
|
|
</form>
|
|
<form class="inline" method="POST" action="/admin/tasks/<%= t.id %>/delete" onsubmit="return confirm('确定删除?')">
|
|
<button class="btn btn-danger btn-sm">删除</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<% }) %>
|
|
<% if (tasks.length === 0) { %>
|
|
<tr><td colspan="7" style="text-align:center;color:#999">暂无任务,请先创建</td></tr>
|
|
<% } %>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h2 style="margin:24px 0 12px">最近检测记录(最新 50 条)</h2>
|
|
<table>
|
|
<thead><tr><th>ID</th><th>产品</th><th>状态</th><th>消息</th><th>已推送</th><th>时间</th></tr></thead>
|
|
<tbody>
|
|
<% logs.forEach(l => { %>
|
|
<tr>
|
|
<td><%= l.id %></td>
|
|
<td><%= l.product_name || l.product_id %></td>
|
|
<td>
|
|
<% if (l.status === 'in_stock') { %><span class="badge badge-on">有货</span>
|
|
<% } else if (l.status === 'out_of_stock') { %><span class="badge badge-off">缺货</span>
|
|
<% } else { %><span class="badge"><%= l.status %></span><% } %>
|
|
</td>
|
|
<td><%= l.message || '-' %></td>
|
|
<td><%= l.notified ? '✅' : '❌' %></td>
|
|
<td><%= l.created_at %></td>
|
|
</tr>
|
|
<% }) %>
|
|
<% if (logs.length === 0) { %>
|
|
<tr><td colspan="6" style="text-align:center;color:#999">暂无记录</td></tr>
|
|
<% } %>
|
|
</tbody>
|
|
</table>
|
|
<%- include('../partials/footer') %>
|