Files
adlist_mikrotik/templates/stats.html
Mateusz Gruszczyński bc45c91d92 refactor ciagl dalszy
2025-08-29 10:46:50 +02:00

216 lines
9.4 KiB
HTML

<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stats</title>
<meta name="theme-color" content="#0f1115">
<link rel="preload" href="{{ url_for('static', filename='css/main.css') }}" as="style">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
</head>
<body>
<header class="site-header">
<div class="brand">
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<path d="M4 4h16v4H4zM4 10h10v4H4zM4 16h16v4H4z" fill="currentColor" />
</svg>
<span>Hosts Converter</span>
</div>
<nav class="actions">
<button class="btn ghost" type="button" data-action="toggle-theme" aria-label="Toggle theme">🌓</button>
<a class="btn ghost" href="/" rel="nofollow">Home</a>
</nav>
</header>
<main class="container container--wide">
<!-- KPIs -->
<section class="card kpi-card">
<h2 class="section-title">Overview</h2>
<div class="kpi-grid">
<div class="kpi">
<div class="kpi-label">Convert requests</div>
<div class="kpi-value">{{ stats.get('stats:convert_requests', 0) }}</div>
</div>
<div class="kpi">
<div class="kpi-label">Successful conversions</div>
<div class="kpi-value">{{ stats.get('stats:conversions_success', 0) }}</div>
</div>
<div class="kpi">
<div class="kpi-label">Errors 4xx</div>
<div class="kpi-value">{{ stats.get('stats:errors_400', 0) }}</div>
</div>
<div class="kpi">
<div class="kpi-label">Errors 5xx</div>
<div class="kpi-value">{{ stats.get('stats:errors_500', 0) }}</div>
</div>
<div class="kpi">
<div class="kpi-label">Avg processing (s)</div>
<div class="kpi-value">{{ '%.3f' % detailed.processing_time_avg_sec }}</div>
</div>
<div class="kpi">
<div class="kpi-label">Avg content (bytes)</div>
<div class="kpi-value">{{ detailed.content_size_avg_bytes }}</div>
</div>
</div>
</section>
<!-- Recent converts -->
<section class="card">
<div class="section-head">
<h2>Recent converts (latest {{ recent|length }})</h2>
<div class="head-actions">
<input class="table-filter" type="search" placeholder="Filter…" data-action="filter-table"
data-target="#recent-table">
</div>
</div>
<div class="table-wrap">
<table id="recent-table" class="data-table">
<thead>
<tr>
<th>Time</th>
<th>URL</th>
<th>Target IP</th>
<th>Client</th>
<th>User agent</th>
<th></th>
</tr>
</thead>
<tbody>
{% for row in recent %}
{% set q = row.get('url','') %}
{% set parts = q.split('&ip=') %}
{% set url = parts[0].replace('/convert?url=', '') | urlencode %}
{% set ip = (parts[1] if parts|length > 1 else '') %}
<tr>
<td class="mono nowrap">{{ row.time|datetimeformat }}</td>
<td class="mono ellipsis" title="{{ url|safe }}">
{{ url|safe }}
</td>
<td class="mono">{{ ip }}</td>
<td class="mono ellipsis" title="{{ row.hostname }} ({{ row.ip }})">
{{ row.hostname }} ({{ row.ip }})
</td>
<td class="ellipsis" title="{{ row.user_agent }}">{{ row.user_agent }}</td>
<td class="actions">
<a class="btn tiny outline" href="{{ q }}" target="_blank" rel="noopener">Open</a>
<button class="btn tiny" data-action="copy-text" data-text="{{ q }}">Copy</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<!-- Top tables -->
<section class="card">
<div class="section-head">
<h2>Top sources</h2>
</div>
<div class="grid">
<div class="col-6">
<h3 class="subhead">Source URLs</h3>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th>URL</th>
<th class="right">Hits</th>
</tr>
</thead>
<tbody>
{% for u, c in url_requests.items()|sort(attribute=1, reverse=True) %}
<tr>
<td class="mono ellipsis" title="{{ u }}">{{ u }}</td>
<td class="right">{{ c }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col-6">
<h3 class="subhead">Target IPs</h3>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th>IP</th>
<th class="right">Hits</th>
</tr>
</thead>
<tbody>
{% for ip, c in target_ips.items()|sort(attribute=1, reverse=True) %}
<tr>
<td class="mono">{{ ip }}</td>
<td class="right">{{ c }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col-6">
<h3 class="subhead">User agents</h3>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th>User agent</th>
<th class="right">Hits</th>
</tr>
</thead>
<tbody>
{% for ua, c in user_agents.items()|sort(attribute=1, reverse=True) %}
<tr>
<td class="ellipsis" title="{{ ua }}">{{ ua }}</td>
<td class="right">{{ c }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col-6">
<h3 class="subhead">Client IPs</h3>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th>Client IP</th>
<th class="right">Hits</th>
</tr>
</thead>
<tbody>
{% for ip, c in client_ips.items()|sort(attribute=1, reverse=True) %}
<tr>
<td class="mono">{{ ip }}</td>
<td class="right">{{ c }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div>&copy; 2025 <a href="https://www.linuxiarz.pl" target="_blank" rel="noopener">linuxiarz.pl</a></div>
<div class="meta">Your IP: <strong>{{ request.remote_addr }}</strong></div>
</footer>
<div id="toast" role="status" aria-live="polite" aria-atomic="true"></div>
<script defer src="{{ url_for('static', filename='js/main.js') }}"></script>
<script defer src="{{ url_for('static', filename='js/stats.js') }}"></script>
<script>(function () { const t = localStorage.getItem('theme') || 'dark'; document.documentElement.setAttribute('data-theme', t); })();</script>
</body>
</html>