added categorization visualization and itmeline

This commit is contained in:
Patrick Di Fazio
2026-01-07 18:24:43 +01:00
parent 02aed9e65a
commit 7690841029
4 changed files with 240 additions and 8 deletions

View File

@@ -284,8 +284,8 @@ def generate_dashboard(stats: dict) -> str:
}}
.radar-chart {{
position: relative;
width: 180px;
height: 180px;
width: 220px;
height: 220px;
overflow: visible;
}}
.radar-legend {{
@@ -352,6 +352,72 @@ def generate_dashboard(stats: dict) -> str:
color: #58a6ff;
border: 1px solid #58a6ff;
}}
.timeline-container {{
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #30363d;
}}
.timeline-title {{
color: #58a6ff;
font-size: 13px;
font-weight: 600;
margin-bottom: 10px;
}}
.timeline {{
position: relative;
padding-left: 30px;
}}
.timeline::before {{
content: '';
position: absolute;
left: 12px;
top: 5px;
bottom: 5px;
width: 3px;
background: #30363d;
}}
.timeline-item {{
position: relative;
padding-bottom: 15px;
}}
.timeline-item:last-child {{
padding-bottom: 0;
}}
.timeline-marker {{
position: absolute;
left: -26px;
width: 16px;
height: 16px;
border-radius: 50%;
border: 2px solid #0d1117;
}}
.timeline-marker.attacker {{
background: #f85149;
}}
.timeline-marker.good-crawler {{
background: #3fb950;
}}
.timeline-marker.bad-crawler {{
background: #f0883e;
}}
.timeline-marker.regular-user {{
background: #58a6ff;
}}
.timeline-content {{
font-size: 12px;
}}
.timeline-category {{
font-weight: 600;
}}
.timeline-timestamp {{
color: #8b949e;
font-size: 11px;
margin-top: 2px;
}}
.timeline-arrow {{
color: #8b949e;
margin: 0 7px;
}}
</style>
</head>
@@ -658,11 +724,45 @@ def generate_dashboard(stats: dict) -> str:
html += '</div>';
}}
// Category History Timeline
if (stats.category_history && stats.category_history.length > 0) {{
html += '<div class="timeline-container">';
html += '<div class="timeline-title">Behavior Timeline</div>';
html += '<div class="timeline">';
stats.category_history.forEach((change, index) => {{
const categoryClass = change.new_category.toLowerCase().replace('_', '-');
const timestamp = new Date(change.timestamp).toLocaleString();
html += '<div class="timeline-item">';
html += `<div class="timeline-marker ${{categoryClass}}"></div>`;
html += '<div class="timeline-content">';
if (change.old_category) {{
const oldCategoryBadge = 'category-' + change.old_category.toLowerCase().replace('_', '-');
html += `<span class="category-badge ${{oldCategoryBadge}}">${{change.old_category}}</span>`;
html += '<span class="timeline-arrow">→</span>';
}} else {{
html += '<span style="color: #8b949e;">Initial:</span> ';
}}
const newCategoryBadge = 'category-' + change.new_category.toLowerCase().replace('_', '-');
html += `<span class="category-badge ${{newCategoryBadge}}">${{change.new_category}}</span>`;
html += `<div class="timeline-timestamp">${{timestamp}}</div>`;
html += '</div>';
html += '</div>';
}});
html += '</div>';
html += '</div>';
}}
html += '</div>';
// Radar chart on the right
if (stats.category_scores && Object.keys(stats.category_scores).length > 0) {{
html += '<div class="stats-right">';
html += '<div style="font-size: 13px; font-weight: 600; color: #58a6ff; margin-bottom: 10px;">Category Score</div>';
html += '<svg class="radar-chart" viewBox="-30 -30 260 260" preserveAspectRatio="xMidYMid meet">';
const scores = {{
@@ -705,7 +805,7 @@ def generate_dashboard(stats: dict) -> str:
// Draw axes
const angles = [0, 90, 180, 270];
const keys = ['attacker', 'good_crawler', 'bad_crawler', 'regular_user'];
const keys = ['good_crawler', 'regular_user', 'bad_crawler', 'attacker'];
angles.forEach((angle, i) => {{
const rad = (angle - 90) * Math.PI / 180;
@@ -713,8 +813,8 @@ def generate_dashboard(stats: dict) -> str:
const y2 = cy + maxRadius * Math.sin(rad);
html += `<line x1="${{cx}}" y1="${{cy}}" x2="${{x2}}" y2="${{y2}}" stroke="#30363d" stroke-width="0.5"/>`;
// Add labels
const labelDist = maxRadius + 30;
// Add labels at consistent distance
const labelDist = maxRadius + 35;
const lx = cx + labelDist * Math.cos(rad);
const ly = cy + labelDist * Math.sin(rad);
html += `<text x="${{lx}}" y="${{ly}}" fill="#8b949e" font-size="12" text-anchor="middle" dominant-baseline="middle">${{labels[keys[i]]}}</text>`;
@@ -755,7 +855,7 @@ def generate_dashboard(stats: dict) -> str:
keys.forEach(key => {{
html += '<div class="radar-legend-item">';
html += `<div class="radar-legend-color" style="background: ${{colors[key]}};"></div>`;
html += `<span style="color: #8b949e;">${{labels[key]}}: ${{scores[key]}}%</span>`;
html += `<span style="color: #8b949e;">${{labels[key]}}: ${{scores[key]}} pt</span>`;
html += '</div>';
}});
html += '</div>';