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.
108 lines
5.6 KiB
HTML
108 lines
5.6 KiB
HTML
{% extends "base.html" %}
|
|
{% from "_pagination.html" import pager %}
|
|
{% block title %}📊 Video Storage Analytics{% endblock %}
|
|
{% block content %}
|
|
<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;">
|
|
<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>
|
|
|
|
<form method="get" action="{{ url_for('web.dashboard') }}" style="display:inline-block;">
|
|
<input type="text" name="q" placeholder="Search users..." value="{{ query or '' }}">
|
|
<button type="submit">Search</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="table-wrap">
|
|
<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' %}
|
|
{% set next_last_dir = 'asc' if sort != 'last_online' 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>
|
|
<th><a href="{{ url_for('web.dashboard', q=query, page=1, sort='last_online', dir=next_last_dir) }}">Last Online{% if sort=='last_online' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}</a></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for key, stats in storage_usage %}
|
|
{% set user, platform = key.split("::") %}
|
|
<tr data-username="{{ user|lower }}">
|
|
<td>
|
|
<a href="/user/{{ user }}">{{ user }}</a>
|
|
{% set uname = user.lower() %}
|
|
{% if uname in online_set %}
|
|
<span class="status-dot dot-online" title="Online"></span>
|
|
{% elif uname in recording_offline_set %}
|
|
<span class="status-dot dot-record" title="Recording (offline)"></span>
|
|
{% else %}
|
|
<span class="status-dot dot-offline" title="Offline"></span>
|
|
{% endif %}
|
|
</td>
|
|
<td><a href="https://{{ platform }}.com/{{ user }}">{{ platform }}</a></td>
|
|
<td>{{ "%.2f"|format(stats.total_size) }}</td>
|
|
<td>{{ stats.video_count }}</td>
|
|
<td>{{ "%.2f"|format(avg_sizes[key]) }}</td>
|
|
<td>
|
|
{% if stats.last_online %}
|
|
{{ stats.last_online.strftime('%Y-%m-%d') }}
|
|
{% else %}
|
|
—
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{{ pager('web.dashboard', page, total_pages, q=query, sort=sort, dir=dir, timeframe=timeframe, start=start_str, end=end_str) }}
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
async function refreshStatusDots() {
|
|
try {
|
|
const resp = await fetch("/api/get_recording/");
|
|
if (!resp.ok) return;
|
|
const streamers = await resp.json();
|
|
const online = new Set(streamers.filter(s => s.is_online).map(s => (s.username||"").toLowerCase()));
|
|
const recOff = new Set(streamers.filter(s => !s.is_online).map(s => (s.username||"").toLowerCase()));
|
|
|
|
document.querySelectorAll('#analytics-table tbody tr').forEach(tr => {
|
|
const uname = (tr.dataset.username || "").toLowerCase();
|
|
const dot = tr.querySelector('.status-dot'); if (!dot) return;
|
|
dot.classList.remove('dot-online','dot-record','dot-offline');
|
|
if (online.has(uname)) { dot.classList.add('dot-online'); dot.title='Online'; }
|
|
else if (recOff.has(uname)) { dot.classList.add('dot-record'); dot.title='Recording (offline)'; }
|
|
else { dot.classList.add('dot-offline'); dot.title='Offline'; }
|
|
});
|
|
} catch(e) { console.error('status refresh failed:', e); }
|
|
}
|
|
refreshStatusDots(); setInterval(refreshStatusDots, 20000);
|
|
</script>
|
|
{% endblock %}
|