list recorded streamers

main
oscar 2 months ago
parent 0c40cee26d
commit ffa0a7c62c

@ -78,3 +78,18 @@ def api_add_moment(video_id):
new_id = add_moment(video_id, ts) new_id = add_moment(video_id, ts)
return jsonify(ok=True, id=new_id, timestamp=ts) return jsonify(ok=True, id=new_id, timestamp=ts)
@api.route("/api/get_recording/", methods=["GET"])
def get_online():
url = 'http://localhost:8000/get_streamers'
import requests
r = requests.get(url)
streamers = r.json()['streamers']
parsed_streamers = []
for streamer in streamers:
streamer['is_online'] = streamer['status'] == 'Channel online'
parsed_streamers.append(streamer)
return jsonify(parsed_streamers)

@ -7,6 +7,15 @@ from config import VIDEOS_PER_PAGE, DASHBOARD_PER_PAGE
web = Blueprint("web", __name__) web = Blueprint("web", __name__)
import requests
def _get_recording_streamers() -> set[str]:
try:
resp = requests.get("http://localhost:5000/api/get_recording/", timeout=3)
resp.raise_for_status()
return resp.json()
except Exception:
return set()
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
def _parse_dates(timeframe: str, start_str: str | None, end_str: str | None): def _parse_dates(timeframe: str, start_str: str | None, end_str: str | None):
"""Return (start, end) as date objects or (None, None). End inclusive by day.""" """Return (start, end) as date objects or (None, None). End inclusive by day."""
@ -37,22 +46,24 @@ def dashboard():
query = request.args.get("q", "").lower().strip() query = request.args.get("q", "").lower().strip()
sort = request.args.get("sort", "total_size") sort = request.args.get("sort", "total_size")
dir_ = request.args.get("dir", "desc") dir_ = request.args.get("dir", "desc")
reverse = (dir_ == "desc")
timeframe = request.args.get("timeframe", "all") timeframe = request.args.get("timeframe", "all")
start_str = request.args.get("start") start_str = request.args.get("start")
end_str = request.args.get("end") end_str = request.args.get("end")
reverse = (dir_ == "desc") show_online_first = request.args.get("online") == "1"
start, end = _parse_dates(timeframe, start_str, end_str) start, end = _parse_dates(timeframe, start_str, end_str)
# ---- build cache over timeframe ---- # ---- build cache over timeframe ----
cache = build_cache(start=start, end=end) cache = build_cache(start=start, end=end)
items = list(cache["storage_usage"].items()) items = list(cache["storage_usage"].items()) # [("user::platform", {...}), ...]
# ---- search ---- # ---- search ----
if query: if query:
items = [e for e in items if query in e[0].split("::")[0].lower()] items = [e for e in items if query in e[0].split("::")[0].lower()]
# ---- sort ---- # ---- sort keys ----
def k_user(x): return x[0].split("::")[0].lower() def k_user(x): return x[0].split("::")[0].lower()
def k_platform(x): return x[0].split("::")[1].lower() def k_platform(x): return x[0].split("::")[1].lower()
def k_total(x): return x[1]["total_size"] def k_total(x): return x[1]["total_size"]
@ -66,7 +77,48 @@ def dashboard():
"video_count": k_count, "video_count": k_count,
"avg_size": k_avg, "avg_size": k_avg,
} }
items.sort(key=key_map.get(sort, k_total), reverse=reverse) base_key = key_map.get(sort, k_total)
# ---- get recording list → two sets: online + recording_offline ----
# _get_recording_streamers() returns: [{"username": "...", "is_online": true/false}, ...]
online_usernames: set[str] = set()
recording_offline_usernames: set[str] = set()
if show_online_first:
try:
rec_list = _get_recording_streamers()
for s in rec_list or []:
u = (s.get("username") or "").lower()
if not u:
continue
if s.get("is_online"):
online_usernames.add(u)
else:
recording_offline_usernames.add(u)
except Exception:
pass
def user_of(item) -> str:
return item[0].split("::")[0]
def is_online(item) -> bool:
return user_of(item).lower() in online_usernames
def is_recording_offline(item) -> bool:
u = user_of(item).lower()
return (u in recording_offline_usernames) and (u not in online_usernames)
# ---- sort with optional grouping ----
if show_online_first:
online_items = [x for x in items if is_online(x)]
recording_offline_items= [x for x in items if is_recording_offline(x)]
the_rest = [x for x in items if (x not in online_items) and (x not in recording_offline_items)]
online_items.sort(key=base_key, reverse=reverse)
recording_offline_items.sort(key=base_key, reverse=reverse)
the_rest.sort(key=base_key, reverse=reverse)
items = online_items + recording_offline_items + the_rest
else:
items.sort(key=base_key, reverse=reverse)
# ---- paginate ---- # ---- paginate ----
page = max(1, int(request.args.get("page", 1))) page = max(1, int(request.args.get("page", 1)))
@ -85,7 +137,11 @@ def dashboard():
dir=dir_, dir=dir_,
timeframe=timeframe, timeframe=timeframe,
start_date=start_str, start_date=start_str,
end_date=end_str end_date=end_str,
online="1" if show_online_first else "0",
# send both sets so we can color dots
online_set=online_usernames,
recording_offline_set=recording_offline_usernames,
) )
@web.route("/refresh") @web.route("/refresh")

@ -65,6 +65,35 @@
padding: 8px 12px; padding: 8px 12px;
} }
</style> </style>
<style>
.status-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-left: 6px;
vertical-align: middle;
}
.dot-online {
background: #22c55e;
}
/* green */
.dot-record {
background: #ef4444;
}
/* red */
.dot-offline {
background: #666;
}
/* grey */
</style>
</head> </head>
<body> <body>
@ -122,6 +151,23 @@
<h1>📊 Video Storage Analytics</h1> <h1>📊 Video Storage Analytics</h1>
<div class="controls"> <div class="controls">
<form method="get" action="{{ url_for('web.dashboard') }}"
style="display:inline-flex; gap:8px; align-items:center;">
<input type="hidden" name="q" value="{{ query or '' }}">
<input type="hidden" name="sort" value="{{ sort }}">
<input type="hidden" name="dir" value="{{ dir }}">
<input type="hidden" name="timeframe" value="{{ timeframe }}">
<input type="hidden" name="start" value="{{ start_date or '' }}">
<input type="hidden" name="end" value="{{ end_date or '' }}">
<input type="hidden" name="online" value="{{ '0' if online=='1' else '1' }}">
<button type="submit">
{{ '📶 Show online (ON)' if online=='1' else '📶 Show online (OFF)' }}
</button>
</form>
<form method="get" action="{{ url_for('web.dashboard') }}" <form method="get" action="{{ url_for('web.dashboard') }}"
style="display:inline-flex; gap:8px; align-items:center; margin-left:10px;"> style="display:inline-flex; gap:8px; align-items:center; margin-left:10px;">
<label>Timeframe:</label> <label>Timeframe:</label>
@ -162,27 +208,30 @@
{% set next_avg_dir = 'asc' if sort != 'avg_size' or dir == 'desc' else 'desc' %} {% set next_avg_dir = 'asc' if sort != 'avg_size' or dir == 'desc' else 'desc' %}
<th> <th>
<a href="{{ url_for('web.dashboard', q=query, page=1, sort='user', dir=next_user_dir) }}"> <a href="{{ url_for('web.dashboard', q=query, page=1, sort='user', dir=next_user_dir, online=online) }}">
User{% if sort=='user' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %} User{% if sort=='user' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
</a> </a>
</th> </th>
<th> <th>
<a href="{{ url_for('web.dashboard', q=query, page=1, sort='platform', dir=next_platform_dir) }}"> <a
href="{{ url_for('web.dashboard', q=query, page=1, sort='platform', dir=next_platform_dir, online=online) }}">
Platform{% if sort=='platform' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %} Platform{% if sort=='platform' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
</a> </a>
</th> </th>
<th> <th>
<a href="{{ url_for('web.dashboard', q=query, page=1, sort='total_size', dir=next_total_dir) }}"> <a
href="{{ url_for('web.dashboard', q=query, page=1, sort='total_size', dir=next_total_dir, online=online) }}">
Total Storage (GB){% if sort=='total_size' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %} Total Storage (GB){% if sort=='total_size' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
</a> </a>
</th> </th>
<th> <th>
<a href="{{ url_for('web.dashboard', q=query, page=1, sort='video_count', dir=next_count_dir) }}"> <a
href="{{ url_for('web.dashboard', q=query, page=1, sort='video_count', dir=next_count_dir, online=online) }}">
Video Count{% if sort=='video_count' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %} Video Count{% if sort=='video_count' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
</a> </a>
</th> </th>
<th> <th>
<a href="{{ url_for('web.dashboard', q=query, page=1, sort='avg_size', dir=next_avg_dir) }}"> <a href="{{ url_for('web.dashboard', q=query, page=1, sort='avg_size', dir=next_avg_dir, online=online) }}">
Avg Size per Video (GB){% if sort=='avg_size' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %} Avg Size per Video (GB){% if sort=='avg_size' %} {{ '▲' if dir=='asc' else '▼' }}{% endif %}
</a> </a>
</th> </th>
@ -192,8 +241,20 @@
{% for key, stats in storage_usage %} {% for key, stats in storage_usage %}
{% set user, platform = key.split("::") %} {% set user, platform = key.split("::") %}
<tr> <tr>
<td><a href="/user/{{ user }}" style="color:#4af;">{{ user }}</a></td> <td>
<td>{{ platform }}</td> <a href="/user/{{ user }}" style="color:#4af;">{{ 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 }}" style="color:#4af;">{{ platform }}</a>
</td>
<td>{{ "%.2f"|format(stats.total_size) }}</td> <td>{{ "%.2f"|format(stats.total_size) }}</td>
<td>{{ stats.video_count }}</td> <td>{{ stats.video_count }}</td>
<td>{{ "%.2f"|format(avg_sizes[key]) }}</td> <td>{{ "%.2f"|format(avg_sizes[key]) }}</td>

Loading…
Cancel
Save