Files
MeshSate/app/templates/tasks.html

233 lines
9.5 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<link rel="icon" href="/static/logo.png">
<title>任务历史 - MeshSafe 控制台</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
margin: 0; background: #f4f6fa; color: #1e293b; }
{% include "_nav_style.html" %}
main { padding: 24px; max-width: 1500px; margin: 0 auto; }
.page-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 18px; }
.page-head h1 { margin: 0 0 6px; font-size: 21px; font-weight: 700; }
.page-head .sub { font-size: 13px; color: #64748b; }
.page-head .count-chip {
background: #fff; border: 1px solid #e2e8f0; border-radius: 8px;
padding: 8px 14px; font-size: 12.5px; color: #475569;
}
.page-head .count-chip b { color: #0f172a; }
.panel { background: #fff; border-radius: 12px; padding: 18px 20px;
box-shadow: 0 1px 3px rgba(15,23,42,.07), 0 6px 20px rgba(15,23,42,.04);
border: 1px solid #eef2f7; }
table { width: 100%; border-collapse: collapse; }
th { background: #f8fafc; padding: 10px 12px; text-align: left; font-size: 11.5px;
color: #64748b; font-weight: 600; border-bottom: 1px solid #e8edf4; white-space: nowrap; }
td { padding: 10px 12px; font-size: 12.5px; border-bottom: 1px solid #f4f7fb; color: #334155; }
tr:last-child td { border-bottom: none; }
tbody tr:hover { background: #fafbfd; }
td.mono { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11.5px; color: #64748b; }
td.fname { font-weight: 500; color: #1e293b; max-width: 230px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.verdict-cell { display: flex; align-items: center; gap: 6px; }
.mode-badge { display: inline-block; padding: 2px 8px; border-radius: 5px; font-size: 11px; }
.mode-sync { background: #e0f2fe; color: #0369a1; }
.mode-async { background: #f3e5f5; color: #7b1fa2; }
details > summary { cursor: pointer; color: #2563eb; user-select: none; font-size: 12px; }
details > summary:hover { text-decoration: underline; }
/* 详情列:限制宽度,内层内容横向滚动,不撑开外层表格 */
tbody td:last-child { min-width: 220px; max-width: 360px; vertical-align: top; }
details { display: block; width: 100%; }
details .findings { padding: 12px; font-size: 12px; background: #f8fafc;
border-radius: 8px; margin-top: 6px;
max-width: 100%; overflow-x: auto; overflow-y: visible; display: block; }
details .findings table { border: 1px solid #e8edf4; border-radius: 6px; overflow: hidden; min-width: 600px; }
details .findings th { font-size: 11px; padding: 6px 10px; white-space: nowrap; }
details .findings td { font-size: 11.5px; padding: 6px 10px; white-space: nowrap; }
.empty { text-align: center; padding: 50px; color: #94a3b8; font-size: 13px; }
/* ---------- 分页 ---------- */
.pager { display: flex; align-items: center; justify-content: space-between; margin-top: 16px;
padding-top: 14px; border-top: 1px solid #eef2f7; }
.pager .p-info { font-size: 12.5px; color: #64748b; }
.pager .p-info b { color: #0f172a; }
.pager .p-nav { display: flex; gap: 5px; align-items: center; }
.pager .p-nav a, .pager .p-nav span {
min-width: 32px; height: 30px; padding: 0 10px;
display: inline-flex; align-items: center; justify-content: center;
border-radius: 7px; font-size: 12.5px; text-decoration: none;
border: 1px solid #e2e8f0; color: #475569; background: #fff;
transition: all .15s;
}
.pager .p-nav a:hover { border-color: #93c5fd; color: #2563eb; background: #eff6ff; }
.pager .p-nav .cur { background: #1e3a5f; border-color: #1e3a5f; color: #fff; font-weight: 600; }
.pager .p-nav .disabled { opacity: .4; pointer-events: none; }
.pager .p-nav .dots { border: none; background: none; min-width: 20px; padding: 0; color: #94a3b8; }
/* ---------- 风险体系说明 ---------- */
.risk-legend { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 14px; padding-top: 14px;
border-top: 1px dashed #e8edf4; }
.risk-legend .rl { display: flex; align-items: flex-start; gap: 7px; font-size: 11.5px; color: #94a3b8; }
.risk-legend .rl .rl-name { color: #334155; font-weight: 600; font-size: 12px; white-space: nowrap; }
</style>
</head>
<body>
{% set nav_active = 'tasks' %}
{% include "_nav.html" %}
<main>
<div class="page-head">
<div>
<h1>任务历史</h1>
<div class="sub">全部扫描任务记录 · 每页 {{ page_size }} 条</div>
</div>
<div class="count-chip">
共 <b>{{ total }}</b> 条记录 · 第 <b>{{ page }}</b> / {{ total_pages }} 页
</div>
</div>
<div class="panel">
{% if tasks %}
<table>
<thead>
<tr>
<th>时间</th>
<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 t in tasks %}
<tr id="task-{{ t.id }}">
<td class="mono">{{ t.created_at|bj_time("%m-%d %H:%M:%S") if t.created_at else '' }}</td>
<td class="mono" title="{{ t.id }}">{{ t.id[0:10] }}...</td>
<td class="fname" title="{{ t.filename }}">{{ t.filename }}</td>
<td class="mono" title="{{ t.client_id }}">{{ t.channel|cn_channel }}</td>
<td class="mono" title="{{ t.real_type }}">{{ t.real_type[:16] if t.real_type else t.declared_ext }}</td>
<td class="mono">
{% if t.size > 1048576 %}{{ (t.size / 1048576)|round(1) }} MB
{% elif t.size > 1024 %}{{ (t.size / 1024)|round(1) }} KB
{% else %}{{ t.size }} B{% endif %}
</td>
<td><span class="mode-badge mode-{{ t.mode }}">{{ t.mode|cn_mode }}</span></td>
<td>
<div class="verdict-cell">
<span class="tag {{ t.verdict|cn_verdict_badge }}">{{ t.verdict|cn_verdict }}</span>
</div>
</td>
<td><span class="tag plain">{{ t.action|cn_action }}</span></td>
<td class="mono">{{ "%.0f"|format(t.score) }}</td>
<td class="mono">{{ t.duration_ms }} ms</td>
<td>
<details>
<summary>详情 ({{ t.findings|length }})</summary>
<div class="findings">
{% if t.findings %}
<table>
<thead>
<tr><th>检测引擎</th><th>严重度</th><th>命中规则</th><th>权重</th><th>细节</th></tr>
</thead>
<tbody>
{% for f in t.findings %}
<tr>
<td><span class="tag plain">{{ f.engine|cn_engine }}</span></td>
<td><span class="tag {{ f.severity|cn_severity_badge }}">{{ f.severity|cn_severity }}</span></td>
<td class="mono">{{ f.rule }}</td>
<td class="mono">{{ f.weight }}</td>
<td>{{ f.detail }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div style="color:#94a3b8;font-size:12px;">未检出任何风险信号</div>
{% endif %}
{% if t.sha256 %}
<div style="margin-top:10px;font-size:11px;color:#94a3b8;font-family:monospace;line-height:1.8;">
SHA256:{{ t.sha256 }}
{% if t.md5 %}<br>MD5:{{ t.md5 }}{% endif %}
<br>策略版本:{{ t.policy_version }}
{% if t.reason %}<br>裁决依据:{{ t.reason }}{% endif %}
</div>
{% endif %}
</div>
</details>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- 风险体系说明 -->
<div class="risk-legend">
{% for r in RISK_MATRIX %}
<div class="rl">
<span class="tag risk-{{ r.key }}">{{ r.label }}</span>
<span>{{ r.desc }}</span>
</div>
{% endfor %}
</div>
<!-- 分页 -->
<div class="pager">
<div class="p-info">
显示第 <b>{{ (page - 1) * page_size + 1 }}</b> - <b>{{ [page * page_size, total]|min }}</b> 条,共 <b>{{ total }}</b> 条
</div>
<div class="p-nav">
{% if has_prev %}
<a href="/console/tasks?page={{ prev_page }}">上一页</a>
{% else %}
<span class="disabled">上一页</span>
{% endif %}
{% if page_nums and page_nums[0] > 1 %}
<a href="/console/tasks?page=1">1</a>
{% if page_nums[0] > 2 %}<span class="dots">···</span>{% endif %}
{% endif %}
{% for p in page_nums %}
{% if p == page %}
<span class="cur">{{ p }}</span>
{% else %}
<a href="/console/tasks?page={{ p }}">{{ p }}</a>
{% endif %}
{% endfor %}
{% if page_nums and page_nums[-1] < total_pages %}
{% if page_nums[-1] < total_pages - 1 %}<span class="dots">···</span>{% endif %}
<a href="/console/tasks?page={{ total_pages }}">{{ total_pages }}</a>
{% endif %}
{% if has_next %}
<a href="/console/tasks?page={{ next_page }}">下一页</a>
{% else %}
<span class="disabled">下一页</span>
{% endif %}
</div>
</div>
{% else %}
<div class="empty">
<div style="font-size:28px;margin-bottom:12px;">📭</div>
暂无扫描记录。调用 <code>/api/v1/scans</code> 提交文件后,结果会自动出现在这里。
</div>
{% endif %}
</div>
</main>
</body>
</html>