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.
163 lines
6.2 KiB
HTML
163 lines
6.2 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; }
|
|
th { background:#333; }
|
|
tr:nth-child(even){ background:#222; }
|
|
.controls { margin: 10px 0; }
|
|
.pagination { margin-top: 15px; }
|
|
.pagination a, .pagination span {
|
|
display:inline-block; background:#222; color:#eee; border:1px solid #444;
|
|
margin:0 5px; padding:6px 12px; text-decoration:none;
|
|
}
|
|
.pagination .active { background:#555; }
|
|
input[type="text"] { padding:8px; width:300px; }
|
|
button { padding:8px 12px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<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();
|
|
});
|
|
|
|
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>
|
|
|
|
<h1>📊 Video Storage Analytics</h1>
|
|
|
|
<div class="controls">
|
|
<button onclick="window.location.href='/refresh'">🔄 Refresh Data</button>
|
|
|
|
<!-- Server-side search -->
|
|
<form method="get" action="{{ url_for('dashboard') }}" style="display:inline-block; margin-left:10px;">
|
|
<input type="text" name="q" placeholder="Search users..." value="{{ query or '' }}">
|
|
<button type="submit">Search</button>
|
|
</form>
|
|
</div>
|
|
|
|
<table id="analytics-table">
|
|
<thead>
|
|
<tr>
|
|
{% set next_user_dir = 'asc' if sort != 'user' or dir == 'desc' else 'desc' %}
|
|
{% set next_platform_dir = 'asc' if sort != 'platform' or dir == 'desc' else 'desc' %}
|
|
{% set next_total_dir = 'asc' if sort != 'total_size' or dir == 'desc' else 'desc' %}
|
|
{% set next_count_dir = 'asc' if sort != 'video_count' or dir == 'desc' else 'desc' %}
|
|
{% set next_avg_dir = 'asc' if sort != 'avg_size' or dir == 'desc' else 'desc' %}
|
|
|
|
<th>
|
|
<a href="{{ url_for('dashboard', q=query, page=1, sort='user', dir=next_user_dir) }}">
|
|
User{% if sort=='user' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
|
|
</a>
|
|
</th>
|
|
<th>
|
|
<a href="{{ url_for('dashboard', q=query, page=1, sort='platform', dir=next_platform_dir) }}">
|
|
Platform{% if sort=='platform' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
|
|
</a>
|
|
</th>
|
|
<th>
|
|
<a href="{{ url_for('dashboard', q=query, page=1, sort='total_size', dir=next_total_dir) }}">
|
|
Total Storage (GB){% if sort=='total_size' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
|
|
</a>
|
|
</th>
|
|
<th>
|
|
<a href="{{ url_for('dashboard', q=query, page=1, sort='video_count', dir=next_count_dir) }}">
|
|
Video Count{% if sort=='video_count' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
|
|
</a>
|
|
</th>
|
|
<th>
|
|
<a href="{{ url_for('dashboard', q=query, page=1, sort='avg_size', dir=next_avg_dir) }}">
|
|
Avg Size per Video (GB){% if sort=='avg_size' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
|
|
</a>
|
|
</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>
|
|
|
|
<!-- Server-side pagination -->
|
|
{% if total_pages > 1 %}
|
|
<div class="pagination">
|
|
{% if page > 1 %}
|
|
<a href="{{ url_for('dashboard', page=page-1, q=query) }}">« Prev</a>
|
|
{% else %}
|
|
<span>« Prev</span>
|
|
{% endif %}
|
|
|
|
{% for p in range(1, total_pages + 1) %}
|
|
{% if p == page %}
|
|
<span class="active">{{ p }}</span>
|
|
{% else %}
|
|
<a href="{{ url_for('dashboard', page=p, q=query) }}">{{ p }}</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page < total_pages %}
|
|
<a href="{{ url_for('dashboard', page=page+1, q=query) }}">Next »</a>
|
|
{% else %}
|
|
<span>Next »</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</body>
|
|
</html>
|