v1.4.2: 修复日报/定制汇总重复发送刷屏(_maybe_daily元组嵌套bug+发送日期持久化) + 一键全部启用/停用 + 数据源快速筛选

This commit is contained in:
2026-08-31 12:27:35 +08:00
parent c9671880e8
commit 3f6abe7ad6
5 changed files with 91 additions and 14 deletions
+58 -3
View File
@@ -4,15 +4,37 @@
{% block content %}
<div class="page-head">
<h1>📡 数据源管理</h1>
<button class="btn accent" onclick="openModal()"> 新增数据源</button>
<div class="actions">
<button class="btn" onclick="setAllSources(1)">✅ 全部启用</button>
<button class="btn" onclick="setAllSources(0)">⏸ 全部停用</button>
<button class="btn accent" onclick="openModal()"> 新增数据源</button>
</div>
</div>
<div class="card" style="margin-bottom:14px;">
<div class="form-row" style="justify-content:flex-start;gap:10px;flex-wrap:wrap;">
<input id="f_keyword" placeholder="🔍 搜索名称 / 类型 / URL / 描述..."
style="flex:1;min-width:220px;" oninput="applyFilter()">
<select id="f_kind" onchange="applyFilter()" style="width:150px;">
<option value="">全部监控方式</option>
<option value="normal">普通数据源</option>
<option value="custom">🎯 定制监控</option>
</select>
<select id="f_enabled" onchange="applyFilter()" style="width:120px;">
<option value="">全部状态</option>
<option value="1">启用</option>
<option value="0">停用</option>
</select>
<button class="btn mini" onclick="clearFilter()">重置</button>
</div>
</div>
<div class="card">
<table>
<table id="srcTable">
<thead><tr><th>ID</th><th>名称</th><th>监控方式</th><th>获取方式</th><th>类型</th><th>权重</th><th>采集周期</th><th>状态</th><th>最近采样</th><th>条数</th><th>操作</th></tr></thead>
<tbody>
{% for s in sources %}
<tr data-sid="{{ s.id }}" data-name="{{ s.name }}" data-type="{{ s.type }}" data-url="{{ s.url }}" data-desc="{{ s.description }}" data-weight="{{ s.weight }}" data-kind="{{ s.kind }}" data-standard="{{ s.monitor_standard or '' }}" data-interval="{{ s.scan_interval_min or 0 }}" data-fetch="{{ s.fetch_method or 'auto' }}" data-capture="{{ s.capture_params or '{}' }}">
<tr data-sid="{{ s.id }}" data-enabled="{{ s.enabled }}" data-name="{{ s.name }}" data-type="{{ s.type }}" data-url="{{ s.url }}" data-desc="{{ s.description }}" data-weight="{{ s.weight }}" data-kind="{{ s.kind }}" data-standard="{{ s.monitor_standard or '' }}" data-interval="{{ s.scan_interval_min or 0 }}" data-fetch="{{ s.fetch_method or 'auto' }}" data-capture="{{ s.capture_params or '{}' }}">
<td>{{ s.id }}</td>
<td><b>{{ s.name }}</b><br><span class="muted small">{{ s.description }}</span><br><span class="muted small" style="word-break:break-all;">{{ s.url }}</span></td>
<td>{% if s.kind == 'custom' %}<span class="tag hot">🎯 定制监控</span>{% else %}<span class="tag ok">普通</span>{% endif %}</td>
@@ -304,11 +326,14 @@ async function toggleSource(id, cb){
const r = await API.json('/api/sources', {action:'toggle', id});
if (!r.ok){ toast('操作失败', false); cb.checked = orig; return; }
const on = cb.checked;
const row = document.querySelector(`tr[data-sid="${id}"]`);
if (row) row.setAttribute('data-enabled', on ? '1' : '0');
const tag = document.getElementById('st-'+id);
if (tag){ tag.textContent = on ? '启用' : '停用'; tag.className = 'tag ' + (on ? 'ok' : ''); }
const nameEl = document.querySelector(`tr[data-sid="${id}"] b`);
const name = nameEl ? nameEl.textContent : '';
toast((on ? '✅ 已启用「' : '⏸ 已停用「') + name + '」', true);
applyFilter();
}
function toggleFromButton(id){
const cb = document.querySelector(`tr[data-sid="${id}"] .switch input`);
@@ -316,6 +341,36 @@ function toggleFromButton(id){
cb.checked = !cb.checked;
toggleSource(id, cb);
}
// ---------- 快速筛选 ----------
function applyFilter(){
const kw = (document.getElementById('f_keyword').value || '').trim().toLowerCase();
const kind = document.getElementById('f_kind').value;
const en = document.getElementById('f_enabled').value;
document.querySelectorAll('#srcTable tbody tr').forEach(tr => {
const text = ((tr.dataset.name||'') + ' ' + (tr.dataset.type||'') + ' ' + (tr.dataset.url||'') + ' ' + (tr.dataset.desc||'')).toLowerCase();
const okKw = !kw || text.includes(kw);
const okKind = !kind || (tr.dataset.kind||'normal') === kind;
const okEn = en === '' || (tr.dataset.enabled||'1') === en;
tr.style.display = (okKw && okKind && okEn) ? '' : 'none';
});
}
function clearFilter(){
document.getElementById('f_keyword').value = '';
document.getElementById('f_kind').value = '';
document.getElementById('f_enabled').value = '';
applyFilter();
}
// ---------- 一键全部启用 / 全部停用 ----------
async function setAllSources(val){
const action = val ? 'enable_all' : 'disable_all';
if (!confirm(val ? '确定要【全部启用】所有数据源吗?' : '确定要【全部停用】所有数据源吗?')) return;
const r = await API.json('/api/sources', {action});
if (!r.ok){ toast('操作失败', false); return; }
toast((val ? '✅ 已全部启用 ' : '⏸ 已全部停用 ') + (r.count||0) + ' 个数据源', true);
setTimeout(()=>location.reload(), 400);
}
async function delSource(id){
if (!confirm('确认删除该数据源?')) return;
await API.json('/api/sources', {action:'delete', id}); toast('✅ 已删除'); setTimeout(()=>location.reload(), 400);