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
75 lines
3.0 KiB
HTML
75 lines
3.0 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}Entity: {{ entity.entity_value }} - TLS Analyzer{% endblock %}
|
|
{% block content %}
|
|
<div class="card">
|
|
<a href="javascript:history.back()" style="color:#4361ee;text-decoration:none;font-size:0.9rem;">← Back</a>
|
|
<h2 style="margin-top:0.5rem;">Entity: <code>{{ entity.entity_value }}</code></h2>
|
|
<p>Run #{{ run.display_id }} | Cluster: <span class="badge badge-info">#{{ entity.cluster_label|default:"unassigned" }}</span></p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Aggregated Features</h2>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Feature</th>
|
|
<th>Value</th>
|
|
<th>Cluster Mean</th>
|
|
<th>Deviation (Z-score)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for col, dev in deviations.items %}
|
|
<tr>
|
|
<td><code>{{ col }}</code></td>
|
|
<td>{{ dev.value|floatformat:3 }}</td>
|
|
<td>{{ dev.cluster_mean|floatformat:3 }}</td>
|
|
<td style="color:{% if dev.zscore > 2 %}#f72585{% elif dev.zscore < -2 %}#4361ee{% else %}#666{% endif %};">
|
|
{{ dev.zscore|floatformat:2 }}
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="4">No feature data available.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if entity.embedding_x is not None and entity.embedding_y is not None %}
|
|
<div class="card">
|
|
<h2>PCA Position</h2>
|
|
<p>X: {{ entity.embedding_x|floatformat:3 }}, Y: {{ entity.embedding_y|floatformat:3 }}</p>
|
|
<canvas id="entityScatter" class="scatter-canvas" height="300"></canvas>
|
|
</div>
|
|
|
|
<script>
|
|
const canvas = document.getElementById('entityScatter');
|
|
if (canvas) {
|
|
const ctx = canvas.getContext('2d');
|
|
const W = canvas.parentElement.clientWidth || 400, H = 300;
|
|
canvas.width = W; canvas.height = H;
|
|
const pad = { top: 30, bottom: 40, left: 50, right: 20 };
|
|
const x = {{ entity.embedding_x }}, y = {{ entity.embedding_y }};
|
|
const margin = 0.2; // 20% padding around the point
|
|
const xRange = Math.abs(x) * margin * 2 || 1;
|
|
const yRange = Math.abs(y) * margin * 2 || 1;
|
|
const xMin = x - xRange, xMax = x + xRange;
|
|
const yMin = y - yRange, yMax = y + yRange;
|
|
|
|
const toX = v => pad.left + (v - xMin) / (xMax - xMin) * (W - pad.left - pad.right);
|
|
const toY = v => pad.top + H - pad.bottom - (v - yMin) / (yMax - yMin) * (H - pad.top - pad.bottom);
|
|
|
|
ctx.clearRect(0, 0, W, H);
|
|
ctx.strokeStyle = '#ddd'; ctx.lineWidth = 1;
|
|
ctx.beginPath(); ctx.moveTo(pad.left, toY(0)); ctx.lineTo(W - pad.right, toY(0)); ctx.stroke();
|
|
ctx.beginPath(); ctx.moveTo(toX(0), pad.top); ctx.lineTo(toX(0), H - pad.bottom); ctx.stroke();
|
|
|
|
ctx.fillStyle = '#f72585';
|
|
ctx.beginPath(); ctx.arc(toX(x), toY(y), 8, 0, Math.PI * 2); ctx.fill();
|
|
ctx.fillStyle = '#fff'; ctx.font = 'bold 10px sans-serif'; ctx.textAlign = 'center';
|
|
ctx.fillText('●', toX(x), toY(y) + 3);
|
|
}
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %}
|