@ -211,9 +211,10 @@ def favorites_page():
@web.route ( " /users " )
@web.route ( " /users " )
def users ( ) :
def users ( ) :
# ---- filters ----
import math
# ---- filters ----
q = ( request . args . get ( " q " ) or " " ) . lower ( ) . strip ( )
q = ( request . args . get ( " q " ) or " " ) . lower ( ) . strip ( )
sort = request . args . get ( " sort " , " total_size " ) # user|site|total_size|video_count
sort = request . args . get ( " sort " , " total_size " ) # user|site|total_size|video_count
dir_ = request . args . get ( " dir " , " desc " )
dir_ = request . args . get ( " dir " , " desc " )
reverse = ( dir_ == " desc " )
reverse = ( dir_ == " desc " )
timeframe = request . args . get ( " timeframe " , " all " )
timeframe = request . args . get ( " timeframe " , " all " )
@ -237,19 +238,19 @@ def users():
params [ " end " ] = end
params [ " end " ] = end
where_sql = " AND " . join ( where )
where_sql = " AND " . join ( where )
# ---- ORDER BY ----
# ---- ORDER BY (use computed GB alias) ----
sort_map = {
sort_map = {
" user " : " username " ,
" user " : " username " ,
" site " : " site " ,
" site " : " site " ,
" total_size " : " total_ bytes " ,
" total_size " : " total_ g b" , # <- sort by GB, not raw MB sum
" video_count " : " video_count " ,
" video_count " : " video_count " ,
}
}
order_col = sort_map . get ( sort , " total_ bytes " )
order_col = sort_map . get ( sort , " total_ g b" )
order_dir = " DESC " if reverse else " ASC "
order_dir = " DESC " if reverse else " ASC "
# ---- pagination ----
# ---- pagination ----
page = max ( 1 , int ( request . args . get ( " page " , 1 ) ) )
page = max ( 1 , int ( request . args . get ( " page " , 1 ) ) )
per_page = 100 # or your DASHBOARD_PER_PAGE
per_page = 100
offset = ( page - 1 ) * per_page
offset = ( page - 1 ) * per_page
# ---- count distinct (username, site) for pager ----
# ---- count distinct (username, site) for pager ----
@ -262,16 +263,18 @@ def users():
) t ;
) t ;
"""
"""
# ---- aggregate page ----
# size is in **MB**, convert to GB:
# 1 GiB = 1024 MB → divide by 1024.0
# (If you really want decimal GB, change 1024.0 to 1000.0)
agg_sql = f """
agg_sql = f """
SELECT
SELECT
username ,
username ,
site ,
site ,
COUNT ( * ) AS video_count ,
COUNT ( * ) AS video_count ,
SUM ( size ) AS total_ bytes ,
SUM ( size ) AS total_ m b,
AVG ( size ) AS avg_ bytes ,
AVG ( size ) AS avg_ m b,
SUM ( size ) : : numeric / 10 00000000.0 AS total_gb ,
( SUM ( size ) : : numeric / 10 24.0) AS total_gb ,
AVG ( size ) : : numeric / 10 00000000.0 AS avg_gb
( AVG ( size ) : : numeric / 10 24.0) AS avg_gb
FROM videos
FROM videos
WHERE { where_sql }
WHERE { where_sql }
GROUP BY username , site
GROUP BY username , site
@ -290,9 +293,9 @@ def users():
cur . execute ( agg_sql , params )
cur . execute ( agg_sql , params )
rows = cur . fetchall ( )
rows = cur . fetchall ( )
# rows: (username, site, video_count, total_ bytes, avg_bytes )
# rows: (username, site, video_count, total_ mb, avg_mb, total_gb, avg_gb )
# ---- get recording sets (for status dots ) ----
# ---- online/recording status sets (optional ) ----
online_usernames : set [ str ] = set ( )
online_usernames : set [ str ] = set ( )
recording_offline_usernames : set [ str ] = set ( )
recording_offline_usernames : set [ str ] = set ( )
if show_online_first :
if show_online_first :
@ -309,64 +312,55 @@ def users():
except Exception :
except Exception :
pass
pass
# ---- thumbnail subquery (only for current page) ----
# ---- thumbnail candidates per (user, site) ----
tcur = conn . cursor ( )
thumb_sql = """
thumb_sql = """
SELECT thumbnail
SELECT thumbnail
FROM videos
FROM videos
WHERE username = % ( u ) s
WHERE username = % ( u ) s
AND site = % ( s ) s
AND site = % ( s ) s
AND thumbnail IS NOT NULL
AND thumbnail IS NOT NULL
AND thumbnail < > ' '
AND thumbnail < > ' '
ORDER BY created_at DESC
ORDER BY created_at DESC
LIMIT 3 ;
LIMIT 3 ;
"""
"""
tcur = conn . cursor ( )
def to_gb ( n ) : return ( n or 0 ) / 1_000_000_000.0
cards = [ ]
cards = [ ]
for ( username , site , video_count , total_ bytes , avg_ bytes , total_gb , avg_gb ) in rows :
for ( username , site , video_count , total_mb , avg_mb , total_gb , avg_gb ) in rows :
# fetch up to 3 recent thumbnails (unchanged logic you already added)
# fetch up to 3 recent thumbnails
thumb_urls = [ ]
thumb_urls = [ ]
try :
try :
tcur . execute (
tcur . execute ( thumb_sql , { " u " : username , " s " : site } )
"""
SELECT thumbnail
FROM videos
WHERE username = % ( u ) s AND site = % ( s ) s
AND thumbnail IS NOT NULL AND thumbnail < > ' '
ORDER BY created_at DESC
LIMIT 3 ;
""" ,
{ " u " : username , " s " : site } ,
)
thumb_urls = [ r [ 0 ] for r in tcur . fetchall ( ) if r and r [ 0 ] ]
thumb_urls = [ r [ 0 ] for r in tcur . fetchall ( ) if r and r [ 0 ] ]
except Exception :
except Exception :
pass
pass
# ---- PRE-FORMAT display strings here (avoid Jinja float filter entirely) ----
total_gb_val = float ( total_gb or 0.0 )
total_gb_val = float ( total_gb or 0 )
avg_gb_val = float ( avg_gb or 0.0 )
avg_gb_val = float ( avg_gb or 0 )
uname_low = ( username or " " ) . lower ( )
uname_low = ( username or " " ) . lower ( )
cards . append ( {
cards . append ( {
" user " : username ,
" user " : username ,
" site " : site ,
" site " : site ,
" total_size " : total_gb_val , # keep the raw number if you need it
" avg_size " : avg_gb_val , # keep raw
" total_size_display " : f " { total_gb_val : .2f } " , # <— use this in HTML
" avg_size_display " : f " { avg_gb_val : .2f } " , # <— use this in HTML
" video_count " : int ( video_count ) ,
" video_count " : int ( video_count ) ,
# numeric
" total_size " : total_gb_val ,
" avg_size " : avg_gb_val ,
# preformatted strings for display
" total_size_display " : f " { total_gb_val : .2f } " ,
" avg_size_display " : f " { avg_gb_val : .2f } " ,
" thumb_urls " : thumb_urls ,
" thumb_urls " : thumb_urls ,
" is_online " : uname_low in online_usernames ,
" is_online " : uname_low in online_usernames ,
" is_recording_offline " : ( uname_low in recording_offline_usernames ) and ( uname_low not in online_usernames ) ,
" is_recording_offline " : ( uname_low in recording_offline_usernames ) and ( uname_low not in online_usernames ) ,
} )
} )
# ---- optional: reorder with online-first grouping ----
if show_online_first :
if show_online_first :
online_cards = [ c for c in cards if c [ " is_online " ] ]
online_cards = [ c for c in cards if c [ " is_online " ] ]
rec_off_cards = [ c for c in cards if c [ " is_recording_offline " ] and not c [ " is_online " ] ]
rec_off_cards = [ c for c in cards if c [ " is_recording_offline " ] and not c [ " is_online " ] ]
the_rest = [ c for c in cards if ( c not in online_cards ) and ( c not in rec_off_cards ) ]
the_rest = [ c for c in cards if ( c not in online_cards ) and ( c not in rec_off_cards ) ]
key_map = {
key_map = {
" user " : lambda c : c [ " user " ] . lower ( ) ,
" user " : lambda c : c [ " user " ] . lower ( ) ,
@ -380,7 +374,6 @@ def users():
the_rest . sort ( key = k , reverse = reverse )
the_rest . sort ( key = k , reverse = reverse )
cards = online_cards + rec_off_cards + the_rest
cards = online_cards + rec_off_cards + the_rest
# ---- render users.html ----
return render_template (
return render_template (
" users.html " ,
" users.html " ,
cards = cards ,
cards = cards ,
@ -393,4 +386,4 @@ def users():
start_date = start_str ,
start_date = start_str ,
end_date = end_str ,
end_date = end_str ,
online = " 1 " if show_online_first else " 0 " ,
online = " 1 " if show_online_first else " 0 " ,
)
)