bethaus-app/templates/connections.html
2025-04-12 19:49:53 +00:00

211 lines
7.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Recent Connections</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<style>
/* Basic layout */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
.container-fluid {
height: 100%;
display: flex;
flex-direction: column;
}
.table-container {
flex: 1;
overflow-y: auto;
}
/* Animate the table body moving down via transform */
#connectionsTableBody {
transition: transform 0.5s ease-out;
transform: translateY(0);
}
/* Advanced animation for new rows */
@keyframes slideIn {
0% {
opacity: 0;
transform: scale(0.8);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.slide-in {
animation: slideIn 0.5s ease-out;
}
/* Animation for disappearing rows */
@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>
</head>
<body>
<!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-dark bg-secondary mb-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Downloads der letzten 10 Minuten</a>
<div class="navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a href="{{ url_for('index') }}" class="nav-link">App</a></li>
<li class="nav-item"><a href="{{ url_for('mylinks') }}" class="nav-link">Meine Links</a></li>
<li class="nav-item"><a href="{{ url_for('connections') }}" class="nav-link">Verbindungen</a></li>
<li class="nav-item"><a href="{{ url_for('dashboard') }}" class="nav-link">Auswertung-Downloads</a></li>
<li class="nav-item"><a href="{{ url_for('songs_dashboard') }}" class="nav-link">Auswertung-Wiederholungen</a></li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<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">
<!-- Rows are dynamically inserted here -->
</tbody>
</table>
</div>
</div>
<!-- Socket.IO client library -->
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<script>
const socket = io();
// Request initial data once connected.
socket.on("connect", () => {
socket.emit("request_initial_data");
});
// Helper: Create a table row.
// When applyAnimation is true, the row uses the slide-in animation.
function createRow(record, applyAnimation = true) {
const row = document.createElement("tr");
row.setAttribute("data-timestamp", record.timestamp);
if (applyAnimation) {
row.classList.add("slide-in");
row.addEventListener("animationend", () =>
row.classList.remove("slide-in"), { once: true });
}
row.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 row;
}
// Update the table body with the new data.
// New rows animate in; existing rows simply update.
function updateTable(data) {
const tbody = document.getElementById("connectionsTableBody");
// Map current rows by their timestamp.
const currentRows = {};
Array.from(tbody.children).forEach(row => {
currentRows[row.getAttribute("data-timestamp")] = row;
});
const fragment = document.createDocumentFragment();
data.forEach(record => {
let row;
if (currentRows[record.timestamp]) {
// Existing row: update content without new animation.
row = currentRows[record.timestamp];
row.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>
`;
row.classList.remove("slide-out");
} else {
// New row: create with slide-in animation.
row = createRow(record, true);
}
fragment.appendChild(row);
});
tbody.innerHTML = "";
tbody.appendChild(fragment);
}
// Animate the table body moving down by the height of a new row before updating.
function animateTableWithNewRow(data) {
const tbody = document.getElementById("connectionsTableBody");
// Identify any new records (by comparing timestamps).
const currentTimestamps = new Set(
Array.from(tbody.children).map(row => row.getAttribute("data-timestamp"))
);
const newRecords = data.filter(record => !currentTimestamps.has(record.timestamp));
if (newRecords.length > 0) {
// Create a temporary row to measure its height.
const tempRow = createRow(newRecords[0], false);
tempRow.style.visibility = "hidden";
tbody.appendChild(tempRow);
const newRowHeight = tempRow.getBoundingClientRect().height;
tempRow.remove();
// Animate the tbody moving down by the measured height.
tbody.style.transform = `translateY(${newRowHeight}px)`;
// After the transition, update the table and reset the transform.
setTimeout(() => {
updateTable(data);
// Remove the transform instantly.
tbody.style.transition = "none";
tbody.style.transform = "translateY(0)";
// Force reflow to apply the style immediately.
void tbody.offsetWidth;
tbody.style.transition = "transform 0.5s ease-out";
}, 500);
} else {
updateTable(data);
}
}
// Listen for incoming connection data from the server.
socket.on("recent_connections", function(data) {
animateTableWithNewRow(data);
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>