Update web/templates/tools/login_attempts.html
This commit is contained in:
parent
7d93563f43
commit
254d624302
@ -6,6 +6,21 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="page-title">Security · Login Attempts for the last 7 Days</h1>
|
<h1 class="page-title">Security · Login Attempts for the last 7 Days</h1>
|
||||||
|
|
||||||
|
<!-- Quick stats -->
|
||||||
|
<div class="card" style="padding:14px 16px; margin-bottom:14px;">
|
||||||
|
<div style="display:flex; gap:12px; flex-wrap:wrap; align-items:center;">
|
||||||
|
<div style="display:inline-flex; align-items:center; gap:8px; background:#eef2ff; color:#1f2937; border-radius:999px; padding:6px 12px;">
|
||||||
|
<strong>Total:</strong> <span id="stat-total">—</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:inline-flex; align-items:center; gap:8px; background:#ecfdf5; color:#065f46; border-radius:999px; padding:6px 12px;">
|
||||||
|
<strong>Successes:</strong> <span id="stat-success">—</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:inline-flex; align-items:center; gap:8px; background:#fff7ed; color:#7c2d12; border-radius:999px; padding:6px 12px;">
|
||||||
|
<strong>Success rate:</strong> <span id="stat-rate">—</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card" style="padding:20px; width:100%;">
|
<div class="card" style="padding:20px; width:100%;">
|
||||||
|
|
||||||
<div class="table-wrap" style="overflow-x:auto;">
|
<div class="table-wrap" style="overflow-x:auto;">
|
||||||
@ -18,16 +33,23 @@
|
|||||||
<th style="padding:10px 14px; text-align:left;">Status</th>
|
<th style="padding:10px 14px; text-align:left;">Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="attempts-body">
|
||||||
{% for a in attempts %}
|
{% for a in attempts %}
|
||||||
<tr style="background:#fff; box-shadow:0 1px 3px rgba(0,0,0,0.08);">
|
<tr
|
||||||
|
style="background:#fff; box-shadow:0 1px 3px rgba(0,0,0,0.08);"
|
||||||
|
data-ip="{{ a.ip_address|default_if_none:'' }}"
|
||||||
|
data-success="{% if a.success %}1{% else %}0{% endif %}"
|
||||||
|
>
|
||||||
<td style="padding:10px 14px;">{{ a.timestamp|date:"Y-m-d H:i:s" }}</td>
|
<td style="padding:10px 14px;">{{ a.timestamp|date:"Y-m-d H:i:s" }}</td>
|
||||||
<td style="padding:10px 14px;">{{ a.username }}</td>
|
<td style="padding:10px 14px;">{{ a.username }}</td>
|
||||||
<td style="padding:10px 14px;">
|
<td style="padding:10px 14px;">
|
||||||
{% if a.ip_address %}
|
{% if a.ip_address %}
|
||||||
<a href="http://{{ a.ip_address }}" target="_blank"
|
<a
|
||||||
style="display:inline-block; padding:4px 10px; border-radius:999px;
|
href="https://ipinfo.io/{{ a.ip_address }}"
|
||||||
background:#e5e7eb; color:#111; text-decoration:none; font-size:0.85em;">
|
target="_blank" rel="noopener noreferrer"
|
||||||
|
title="Open IP details on ipinfo.io"
|
||||||
|
style="display:inline-block; padding:4px 10px; border-radius:999px;
|
||||||
|
background:#e5e7eb; color:#111; text-decoration:none; font-size:0.85em;">
|
||||||
{{ a.ip_address }}
|
{{ a.ip_address }}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -36,15 +58,10 @@
|
|||||||
</td>
|
</td>
|
||||||
<td style="padding:10px 14px;">
|
<td style="padding:10px 14px;">
|
||||||
{% if a.success %}
|
{% if a.success %}
|
||||||
<span style="display:inline-block; padding:4px 12px; border-radius:999px;
|
<span class="pill pill-green">Success</span>
|
||||||
background:#22c55e; color:#fff; font-size:0.85em;">
|
|
||||||
Success
|
|
||||||
</span>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span style="display:inline-block; padding:4px 12px; border-radius:999px;
|
<span class="pill pill-red">Failed</span>
|
||||||
background:#ef4444; color:#fff; font-size:0.85em;">
|
<!-- red-dot gets injected here via JS when this IP has 2+ fails -->
|
||||||
Failed
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -61,4 +78,67 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Inline pill styles (kept here so you don't have to touch app.css) -->
|
||||||
|
<style>
|
||||||
|
.pill {
|
||||||
|
display:inline-block; padding:4px 12px; border-radius:999px;
|
||||||
|
color:#fff; font-size:0.85em; line-height:1;
|
||||||
|
}
|
||||||
|
.pill-green { background:#22c55e; } /* green-500 */
|
||||||
|
.pill-red { background:#ef4444; } /* red-500 */
|
||||||
|
.dot-red {
|
||||||
|
display:inline-block; width:8px; height:8px; border-radius:50%;
|
||||||
|
background:#dc2626; margin-left:8px; vertical-align:middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Lightweight JS: compute success rate and flag IPs with 2+ fails -->
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
var tbody = document.getElementById('attempts-body');
|
||||||
|
if(!tbody) return;
|
||||||
|
|
||||||
|
var rows = Array.from(tbody.querySelectorAll('tr[data-success]'));
|
||||||
|
var total = 0, ok = 0;
|
||||||
|
var failedCountByIp = Object.create(null);
|
||||||
|
|
||||||
|
// First pass: counts
|
||||||
|
rows.forEach(function(row){
|
||||||
|
var success = row.getAttribute('data-success') === '1';
|
||||||
|
var ip = row.getAttribute('data-ip') || '';
|
||||||
|
total += 1;
|
||||||
|
if (success) ok += 1;
|
||||||
|
else {
|
||||||
|
if (!failedCountByIp[ip]) failedCountByIp[ip] = 0;
|
||||||
|
failedCountByIp[ip] += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
var rate = total ? Math.round((ok / total) * 100) : 0;
|
||||||
|
var elTotal = document.getElementById('stat-total');
|
||||||
|
var elSuccess = document.getElementById('stat-success');
|
||||||
|
var elRate = document.getElementById('stat-rate');
|
||||||
|
if (elTotal) elTotal.textContent = String(total);
|
||||||
|
if (elSuccess) elSuccess.textContent = String(ok);
|
||||||
|
if (elRate) elRate.textContent = total ? (rate + '%') : '—';
|
||||||
|
|
||||||
|
// Second pass: flag rows with 2+ fails from the same IP
|
||||||
|
rows.forEach(function(row){
|
||||||
|
var success = row.getAttribute('data-success') === '1';
|
||||||
|
if (success) return;
|
||||||
|
var ip = row.getAttribute('data-ip') || '';
|
||||||
|
if (ip && failedCountByIp[ip] >= 2) {
|
||||||
|
var statusCell = row.cells[3];
|
||||||
|
if (statusCell) {
|
||||||
|
var dot = document.createElement('span');
|
||||||
|
dot.className = 'dot-red';
|
||||||
|
dot.title = '2+ failed attempts from this IP in the last 7 days';
|
||||||
|
statusCell.appendChild(dot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue
Block a user