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.
230 lines
7.3 KiB
HTML
230 lines
7.3 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">
|
|
<form method="get" action="{{ url_for('web.dashboard') }}"
|
|
style="display:inline-flex; gap:8px; align-items:center; margin-left:10px;">
|
|
<label>Timeframe:</label>
|
|
<select name="timeframe" onchange="this.form.submit()">
|
|
<option value="all" {{ 'selected' if timeframe=='all' else '' }}>All time</option>
|
|
<option value="week" {{ 'selected' if timeframe=='week' else '' }}>This week</option>
|
|
<option value="month" {{ 'selected' if timeframe=='month' else '' }}>Last 30 days</option>
|
|
<option value="year" {{ 'selected' if timeframe=='year' else '' }}>Last 365 days</option>
|
|
<option value="custom" {{ 'selected' if timeframe=='custom' else '' }}>Custom…</option>
|
|
</select>
|
|
|
|
<input type="date" name="start" value="{{ start_date or '' }}" placeholder="Start" />
|
|
<input type="date" name="end" value="{{ end_date or '' }}" placeholder="End" />
|
|
|
|
<input type="hidden" name="q" value="{{ query or '' }}">
|
|
<input type="hidden" name="sort" value="{{ sort }}">
|
|
<input type="hidden" name="dir" value="{{ dir }}">
|
|
|
|
<button type="submit">Apply</button>
|
|
</form>
|
|
|
|
<button onclick="window.location.href='/refresh'">🔄 Refresh Data</button>
|
|
|
|
<!-- Server-side search -->
|
|
<form method="get" action="{{ url_for('web.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('web.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('web.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('web.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('web.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('web.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('web.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('web.dashboard', page=p, q=query) }}">{{ p }}</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page < total_pages %} <a href="{{ url_for('web.dashboard', page=page+1, q=query) }}">Next »</a>
|
|
{% else %}
|
|
<span>Next »</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</body>
|
|
|
|
</html> |