136 lines
4.0 KiB
HTML
136 lines
4.0 KiB
HTML
{# templates/connections.html #}
|
|
{% extends 'base.html' %}
|
|
|
|
{% block title %}Recent Connections{% endblock %}
|
|
|
|
{% block head_extra %}
|
|
<style>
|
|
/* page-specific styles */
|
|
.table-container { flex: 1; overflow-y: auto; }
|
|
#connectionsTableBody {
|
|
transition: transform 0.5s ease-out;
|
|
transform: translateY(0);
|
|
}
|
|
@keyframes slideIn {
|
|
0% { opacity: 0; transform: scale(0.8); }
|
|
100% { opacity: 1; transform: scale(1); }
|
|
}
|
|
.slide-in { animation: slideIn 0.5s ease-out; }
|
|
@keyframes slideOut {
|
|
0% { opacity: 1; transform: scale(1); }
|
|
100% { opacity: 0; transform: scale(0.1); }
|
|
}
|
|
.slide-out { animation: slideOut 0.5s ease-in forwards; }
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<h2>Verbindungen der letzten 10 Minuten</h2>
|
|
<span class="ms-3">
|
|
Anzahl Verbindungen: <strong id="totalConnections">0</strong>
|
|
</span>
|
|
<div class="table-responsive table-container">
|
|
<table class="table table-hover">
|
|
<thead class="table-secondary">
|
|
<tr>
|
|
<th>Timestamp</th>
|
|
<th>Location</th>
|
|
<th>User Agent</th>
|
|
<th>File Path</th>
|
|
<th>File Size</th>
|
|
<th>MIME-Typ</th>
|
|
<th>Cached</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="connectionsTableBody">
|
|
{# dynamically populated via Socket.IO #}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
|
|
<script>
|
|
const socket = io();
|
|
socket.on("connect", () => socket.emit("request_initial_data"));
|
|
|
|
function updateStats(data) {
|
|
document.getElementById("totalConnections").textContent = data.length;
|
|
}
|
|
|
|
function createRow(record, animate=true) {
|
|
const tr = document.createElement("tr");
|
|
tr.dataset.timestamp = record.timestamp;
|
|
if (animate) {
|
|
tr.classList.add("slide-in");
|
|
tr.addEventListener("animationend", () =>
|
|
tr.classList.remove("slide-in"), { once: true });
|
|
}
|
|
tr.innerHTML = `
|
|
<td>${record.timestamp}</td>
|
|
<td>${record.location}</td>
|
|
<td>${record.user_agent}</td>
|
|
<td>${record.full_path}</td>
|
|
<td>${record.filesize}</td>
|
|
<td>${record.mime_typ}</td>
|
|
<td>${record.cached}</td>
|
|
`;
|
|
return tr;
|
|
}
|
|
|
|
function updateTable(data) {
|
|
const tbody = document.getElementById("connectionsTableBody");
|
|
const existing = {};
|
|
Array.from(tbody.children).forEach(r => existing[r.dataset.timestamp] = r);
|
|
|
|
const frag = document.createDocumentFragment();
|
|
data.forEach(rec => {
|
|
let row = existing[rec.timestamp];
|
|
if (row) {
|
|
row.innerHTML = createRow(rec, false).innerHTML;
|
|
row.classList.remove("slide-out");
|
|
} else {
|
|
row = createRow(rec, true);
|
|
}
|
|
frag.appendChild(row);
|
|
});
|
|
|
|
tbody.innerHTML = "";
|
|
tbody.appendChild(frag);
|
|
}
|
|
|
|
function animateTableWithNewRow(data) {
|
|
const tbody = document.getElementById("connectionsTableBody");
|
|
const currentTs = new Set([...tbody.children].map(r => r.dataset.timestamp));
|
|
const newRecs = data.filter(r => !currentTs.has(r.timestamp));
|
|
|
|
if (newRecs.length) {
|
|
const temp = createRow(newRecs[0], false);
|
|
temp.style.visibility = "hidden";
|
|
tbody.appendChild(temp);
|
|
const h = temp.getBoundingClientRect().height;
|
|
temp.remove();
|
|
|
|
tbody.style.transform = `translateY(${h}px)`;
|
|
setTimeout(() => {
|
|
updateTable(data);
|
|
tbody.style.transition = "none";
|
|
tbody.style.transform = "translateY(0)";
|
|
void tbody.offsetWidth;
|
|
tbody.style.transition = "transform 0.5s ease-out";
|
|
}, 500);
|
|
} else {
|
|
updateTable(data);
|
|
}
|
|
}
|
|
|
|
socket.on("recent_connections", data => {
|
|
updateStats(data);
|
|
animateTableWithNewRow(data);
|
|
});
|
|
</script>
|
|
{% endblock %}
|