8fa36b774e
Key changes: - New: data_loader.py SQLite persistence with drop_sqlite_table - New: db_utils.py retry_on_lock decorator (3 retries, exponential backoff) - New: tool_registry.py with 27 MCP tools (filter_and_cluster, compute_scores, etc.) - New: tls_ref.py for TLS cipher/reference data - New: import_tlsdb.py management command - New: scripts/start_server.py for portable runtime - New: migrations 0003-0007 for SQLite table, display_id, llm fields - Changed: views.py unified pipeline worker, retry_run, display_id everywhere - Changed: models.py with display_id auto-assignment, run_type, sqlite_table, llm_thinking, tool_calls_json - Changed: urls.py added retry_run route - Changed: session_store.py robust JSON persistence - Changed: AGENTS.md v7 fix summary added - Changed: templates — globe rewrite (inertia/polar flip), auto.html (thinking/tool accordions), base.html (toast/config), all pages use display_id - Changed: run.bat PYTHONUTF8=1 - Deleted: entity_detector.py, entity_aggregator.py (replaced by filter_and_cluster clustering pipeline) - Test: 92/92 unit tests passing
98 lines
4.4 KiB
HTML
98 lines
4.4 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}Analysis Runs - TLS Analyzer{% endblock %}
|
|
{% block content %}
|
|
<div class="card">
|
|
<h2>All Analysis Runs</h2>
|
|
|
|
<!-- Type filter -->
|
|
<div style="margin-bottom:1em;">
|
|
<form method="get" style="display:inline-flex;align-items:center;gap:0.5em;">
|
|
<label for="type-filter">类型筛选:</label>
|
|
<select name="type" id="type-filter" onchange="this.form.submit()">
|
|
<option value="">全部</option>
|
|
<option value="upload" {% if current_type == 'upload' %}selected{% endif %}>手动上传</option>
|
|
<option value="manual" {% if current_type == 'manual' %}selected{% endif %}>手动分析</option>
|
|
<option value="auto" {% if current_type == 'auto' %}selected{% endif %}>LLM自动</option>
|
|
</select>
|
|
{% if current_type %}
|
|
<a href="{% url 'analysis:run_list' %}" style="font-size:0.9em;">清除筛选</a>
|
|
{% endif %}
|
|
</form>
|
|
</div>
|
|
|
|
{% if runs %}
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Started</th>
|
|
<th>Status</th>
|
|
<th>Type</th>
|
|
<th>CSV Glob</th>
|
|
<th>Entities</th>
|
|
<th>Clusters</th>
|
|
<th></th>
|
|
<th>重试</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for run in runs %}
|
|
<tr>
|
|
<td>#{{ run.display_id }}</td>
|
|
<td>{{ run.created_at|date:"Y-m-d H:i" }}</td>
|
|
<td><span class="badge {% if run.status == 'completed' %}badge-success{% elif run.status == 'failed' %}badge-danger{% else %}badge-info{% endif %}">{{ run.get_status_display }}</span></td>
|
|
<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;">{{ run.csv_glob }}</td>
|
|
<td>{{ run.entity_count|default:"-" }}</td>
|
|
<td>{{ run.cluster_count|default:"-" }}</td>
|
|
<td><a href="{% url 'analysis:run_detail' run.display_id %}" class="btn btn-primary">Detail</a></td>
|
|
<td>
|
|
{% if run.status == 'failed' %}
|
|
<form method="post" action="{% url 'analysis:retry_run' run.display_id %}" style="display:inline;">
|
|
{% csrf_token %}
|
|
<button type="submit" style="background:#ffc107;color:#000;border:none;padding:2px 8px;font-size:0.85em;border-radius:3px;cursor:pointer;">重试</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
<td><button class="btn btn-danger" style="background:#dc3545;color:#fff;padding:0.25rem 0.5rem;font-size:0.8rem;" onclick="deleteRun({{ run.display_id }})">删除</button></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
<div class="pagination">
|
|
{% if page > 1 %}<a href="?page={{ page|add:-1 }}{% if current_type %}&type={{ current_type }}{% endif %}">← Prev</a>{% endif %}
|
|
<span class="active">{{ page }}</span>
|
|
{% if page < total_pages %}<a href="?page={{ page|add:1 }}{% if current_type %}&type={{ current_type }}{% endif %}">Next →</a>{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state"><p>No runs yet.</p></div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div style="display:none;">{% csrf_token %}</div>
|
|
|
|
<script>
|
|
async function deleteRun(runId) {
|
|
if (!confirm('确定要删除此运行记录吗?此操作不可撤销。')) return;
|
|
try {
|
|
const csrf = document.querySelector('[name=csrfmiddlewaretoken]');
|
|
if (!csrf) { showError('页面已过期,请刷新后重试'); return; }
|
|
const resp = await fetch('/runs/' + runId + '/delete/', {
|
|
method: 'POST',
|
|
headers: { 'X-CSRFToken': csrf.value },
|
|
});
|
|
if (!resp.ok) {
|
|
const text = await resp.text().catch(() => '');
|
|
showError('删除失败 (HTTP ' + resp.status + '): ' + (text.length > 200 ? text.substring(0, 200) + '…' : text || '服务器内部错误'));
|
|
return;
|
|
}
|
|
const data = await resp.json();
|
|
if (data.deleted) { showSuccess('已删除'); location.reload(); }
|
|
else { showError('删除失败: 服务器返回了意外的响应'); }
|
|
} catch (err) {
|
|
showError('删除失败: ' + err.message + ' — 请刷新页面后重试');
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|