You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
4.9 KiB
HTML
168 lines
4.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>📊 Video Storage Analytics</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
background: #111;
|
|
color: #eee;
|
|
text-align: center;
|
|
}
|
|
|
|
table {
|
|
margin: auto;
|
|
border-collapse: collapse;
|
|
width: 90%;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
th,
|
|
td {
|
|
border: 1px solid #444;
|
|
padding: 10px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
th {
|
|
background: #333;
|
|
}
|
|
|
|
tr:nth-child(even) {
|
|
background: #222;
|
|
}
|
|
|
|
th.sort-asc::after {
|
|
content: " ▲";
|
|
}
|
|
|
|
th.sort-desc::after {
|
|
content: " ▼";
|
|
}
|
|
|
|
#search {
|
|
margin: 10px;
|
|
padding: 8px;
|
|
width: 300px;
|
|
}
|
|
|
|
.pagination {
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.pagination button {
|
|
background: #222;
|
|
color: #eee;
|
|
border: 1px solid #444;
|
|
margin: 0 5px;
|
|
padding: 6px 12px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.pagination button.active {
|
|
background: #555;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<h1>📊 Video Storage Analytics</h1>
|
|
<button onclick="window.location.href='/refresh'">🔄 Refresh Data</button>
|
|
<input type="text" id="search" placeholder="Search users...">
|
|
|
|
<table id="analytics-table">
|
|
<thead>
|
|
<tr>
|
|
<th>User</th>
|
|
<th>Platform</th>
|
|
<th>Total Storage (GB)</th>
|
|
<th>Video Count</th>
|
|
<th>Avg Size per Video (GB)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for key, stats in storage_usage %}
|
|
{% set user, platform = key.split("::") %}
|
|
<tr>
|
|
<td><a href="/user/{{ user }}" style="color: #4af;">{{ user }}</a></td>
|
|
<td>{{ platform }}</td>
|
|
<td>{{ "%.2f"|format(stats.total_size) }}</td>
|
|
<td>{{ stats.video_count }}</td>
|
|
<td>{{ "%.2f"|format(avg_sizes[key]) }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="pagination" id="pagination"></div>
|
|
|
|
<script>
|
|
const table = document.getElementById('analytics-table');
|
|
const headers = table.querySelectorAll('th');
|
|
const searchInput = document.getElementById('search');
|
|
const rows = Array.from(table.querySelector('tbody').rows);
|
|
const pagination = document.getElementById('pagination');
|
|
let sortDirection = {};
|
|
let currentPage = 1;
|
|
const rowsPerPage = 100;
|
|
|
|
// Sorting
|
|
headers.forEach((header, index) => {
|
|
header.addEventListener('click', () => {
|
|
const isNumeric = index >= 2;
|
|
const dir = sortDirection[index] === 'asc' ? 'desc' : 'asc';
|
|
sortDirection = { [index]: dir };
|
|
headers.forEach(h => h.classList.remove('sort-asc', 'sort-desc'));
|
|
header.classList.add(dir === 'asc' ? 'sort-asc' : 'sort-desc');
|
|
|
|
rows.sort((a, b) => {
|
|
const aVal = isNumeric ? parseFloat(a.cells[index].innerText) : a.cells[index].innerText.toLowerCase();
|
|
const bVal = isNumeric ? parseFloat(b.cells[index].innerText) : b.cells[index].innerText.toLowerCase();
|
|
return dir === 'asc' ? (aVal > bVal ? 1 : -1) : (aVal < bVal ? 1 : -1);
|
|
});
|
|
updateTable();
|
|
});
|
|
});
|
|
|
|
// Search
|
|
searchInput.addEventListener('keyup', () => {
|
|
currentPage = 1;
|
|
updateTable();
|
|
});
|
|
|
|
// Pagination
|
|
function updatePagination(totalPages) {
|
|
pagination.innerHTML = '';
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
const btn = document.createElement('button');
|
|
btn.innerText = i;
|
|
btn.classList.toggle('active', i === currentPage);
|
|
btn.addEventListener('click', () => {
|
|
currentPage = i;
|
|
updateTable();
|
|
});
|
|
pagination.appendChild(btn);
|
|
}
|
|
}
|
|
|
|
function updateTable() {
|
|
const term = searchInput.value.toLowerCase();
|
|
const filtered = rows.filter(row => row.cells[0].innerText.toLowerCase().includes(term));
|
|
|
|
const totalPages = Math.ceil(filtered.length / rowsPerPage);
|
|
updatePagination(totalPages);
|
|
|
|
table.querySelector('tbody').innerHTML = '';
|
|
const start = (currentPage - 1) * rowsPerPage;
|
|
const paginated = filtered.slice(start, start + rowsPerPage);
|
|
|
|
paginated.forEach(row => table.querySelector('tbody').appendChild(row));
|
|
}
|
|
|
|
updateTable(); // Initial render
|
|
</script>
|
|
</body>
|
|
|
|
</html> |