2026-06-02 00:47:03 -04:00
import json
import os
from datetime import datetime , timezone
2026-06-07 00:21:08 -04:00
import config_utils
import factory
2026-06-02 00:47:03 -04:00
2026-06-09 11:32:33 -04:00
DNS_LOG_FILE = f ' { config_utils . BLOCKLISTS_DIR } /.log '
2026-06-03 00:45:04 -04:00
DNS_LOG_MAX = 50
2026-06-09 00:32:42 -04:00
BL_TYPE_LABELS = { ' community ' : ' Community ' , ' local ' : ' Local ' }
2026-06-03 00:45:04 -04:00
def _dnsblocking_log_tail ( cfg ) :
try :
log_max_kb = cfg . get ( ' dns_blocking ' , { } ) . get ( ' general ' , { } ) . get ( ' log_max_kb ' , 1024 )
size_kb = os . path . getsize ( DNS_LOG_FILE ) / 1024
with open ( DNS_LOG_FILE ) as f :
lines = f . readlines ( )
if not lines :
2026-06-09 11:37:48 -04:00
return ' (no log entries yet) ' , ' '
2026-06-03 00:45:04 -04:00
total = len ( lines )
tail = lines [ - DNS_LOG_MAX : ]
shown = len ( tail )
hidden = total - shown
pct = min ( 100 , round ( size_kb / log_max_kb * 100 ) ) if log_max_kb else 0
left = f ' Showing { shown } of { total } lines ( { hidden } not shown) ' if hidden > 0 else f ' Showing { shown } of { total } lines '
right = f ' Log file size: { size_kb : .1f } KB ( { pct } % of max) '
summary = (
' <div class= " text-muted " style= " display:flex;justify-content:space-between;margin-top:0.5em; " > '
f ' <span> { left } </span><span> { right } </span></div> '
)
return ' ' . join ( tail ) . strip ( ) , summary
except FileNotFoundError :
2026-06-09 11:37:48 -04:00
return ' (no log entries yet) ' , ' '
2026-06-03 00:45:04 -04:00
except Exception :
return ' (error reading log) ' , ' '
2026-06-02 00:47:03 -04:00
2026-06-09 11:22:36 -04:00
WARN_ICON = ' <span class= " tooltip-wrap " data-tooltip= " Cached blocklist used. Verify URL is correct. " style= " display:inline-flex;align-items:center; " ><img src= " /www/icons/warning.svg " style= " width:1em;height:1em; " ></span> '
ERROR_ICON = ' <span class= " tooltip-wrap " data-tooltip= " Blocklist not found. No cached version available. " style= " display:inline-flex;align-items:center; " ><img src= " /www/icons/error.svg " style= " width:1em;height:1em; " ></span> '
2026-06-09 11:00:37 -04:00
def _last_dl_time ( ) :
path = f ' { config_utils . BLOCKLISTS_DIR } /.last-dl '
try :
return int ( open ( path ) . read ( ) . strip ( ) )
except Exception :
return None
2026-06-02 12:49:39 -04:00
def blocklist_stats_html ( cfg ) :
2026-06-09 12:07:38 -04:00
db_rows = config_utils . _bl_db_rows ( )
last_dl = _last_dl_time ( )
bl_vlans = { }
for vlan in cfg . get ( ' vlans ' , [ ] ) :
for bl_name in vlan . get ( ' use_blocklists ' , [ ] ) :
bl_vlans . setdefault ( bl_name , [ ] ) . append ( vlan [ ' name ' ] )
2026-06-02 00:47:03 -04:00
rows = ' '
for bl in cfg . get ( ' dns_blocking ' , { } ) . get ( ' blocklists ' , [ ] ) :
2026-06-09 01:25:02 -04:00
name = bl . get ( ' name ' , ' ' )
2026-06-09 00:32:42 -04:00
is_local = bl . get ( ' bl_type ' ) == ' local '
2026-06-09 01:25:02 -04:00
db = db_rows . get ( name , { } )
count = db . get ( ' domain_count ' )
entries = f ' { count : , } ' if count is not None else ' - '
2026-06-09 00:32:42 -04:00
if is_local :
2026-06-09 01:25:02 -04:00
save_as = bl . get ( ' save_as ' , ' ' )
bl_path = f ' { config_utils . BLOCKLISTS_DIR } / { save_as } ' if save_as else ' '
2026-06-09 00:32:42 -04:00
try :
size_str = config_utils . fmt_bytes ( os . path . getsize ( bl_path ) )
except Exception :
2026-06-09 01:25:02 -04:00
size_str = ' - '
last_refreshed = ' Local '
2026-06-09 11:00:37 -04:00
warn = ' '
2026-06-09 00:32:42 -04:00
else :
2026-06-09 01:25:02 -04:00
fetched_at = db . get ( ' fetched_at ' )
if fetched_at :
2026-06-09 00:32:42 -04:00
last_refreshed = (
2026-06-09 01:25:02 -04:00
f ' { datetime . fromtimestamp ( fetched_at ) . strftime ( " % Y- % m- %d % H: % M " ) } '
f ' ( { config_utils . relative_time ( fetched_at , datetime . now ( tz = timezone . utc ) . timestamp ( ) ) } ago) '
2026-06-09 00:32:42 -04:00
)
2026-06-09 01:25:02 -04:00
else :
last_refreshed = ' Never '
save_as = bl . get ( ' save_as ' , ' ' )
bl_path = f ' { config_utils . BLOCKLISTS_DIR } / { save_as } ' if save_as else ' '
try :
size_str = config_utils . fmt_bytes ( os . path . getsize ( bl_path ) )
2026-06-09 11:00:37 -04:00
file_mtime = int ( os . path . getmtime ( bl_path ) )
2026-06-09 00:32:42 -04:00
except Exception :
2026-06-09 11:00:37 -04:00
size_str = ' - '
file_mtime = None
2026-06-09 11:22:36 -04:00
if file_mtime is None :
warn = ERROR_ICON
elif last_dl and file_mtime < last_dl :
2026-06-09 11:00:37 -04:00
warn = WARN_ICON
2026-06-09 11:22:36 -04:00
else :
warn = ' '
2026-06-09 12:07:38 -04:00
vlan_names = bl_vlans . get ( name , [ ] )
used_by = ' , ' . join ( factory . e ( v ) for v in vlan_names ) if vlan_names else ' <span class= " text-muted " >Not used by any VLANs</span> '
2026-06-02 00:47:03 -04:00
rows + = (
' <tr> '
2026-06-09 01:25:02 -04:00
f ' <td class= " table-cell " > { factory . e ( name ) } </td> '
2026-06-02 00:47:03 -04:00
f ' <td class= " table-cell " > { entries } </td> '
f ' <td class= " table-cell " > { size_str } </td> '
2026-06-09 11:00:37 -04:00
f ' <td class= " table-cell " > { factory . e ( last_refreshed ) } { warn } </td> '
2026-06-09 12:07:38 -04:00
f ' <td class= " table-cell " > { used_by } </td> '
2026-06-02 00:47:03 -04:00
' </tr> '
)
if not rows :
return ' '
return (
' <table class= " data-table " ><thead><tr> '
' <th class= " table-header " >Blocklist</th> '
' <th class= " table-header " >Entries</th> '
' <th class= " table-header " >Size</th> '
' <th class= " table-header " >Last Refreshed</th> '
2026-06-09 12:07:38 -04:00
' <th class= " table-header " >Used by VLAN(s)</th> '
2026-06-02 00:47:03 -04:00
' </tr></thead> '
f ' <tbody> { rows } </tbody></table> '
)
def collect_tokens ( cfg ) :
2026-06-07 00:21:08 -04:00
tokens = config_utils . collect_layout_tokens ( cfg )
2026-06-02 00:47:03 -04:00
dns_blk_gen = cfg . get ( ' dns_blocking ' , { } ) . get ( ' general ' , { } )
2026-06-02 12:49:39 -04:00
tokens [ ' GENERAL_LOG_MAX_KB ' ] = str ( dns_blk_gen . get ( ' log_max_kb ' , ' - ' ) )
tokens [ ' GENERAL_LOG_ERRORS_ONLY ' ] = ' true ' if dns_blk_gen . get ( ' log_errors_only ' ) else ' false '
tokens [ ' GENERAL_DAILY_EXECUTE_TIME ' ] = str ( dns_blk_gen . get ( ' daily_execute_time_24hr_local ' , ' - ' ) )
tokens [ ' BLOCKLIST_STATS_HTML ' ] = blocklist_stats_html ( cfg )
2026-06-03 00:45:04 -04:00
tokens [ ' DNS_LOG_TAIL ' ] , tokens [ ' DNS_LOG_SUMMARY ' ] = _dnsblocking_log_tail ( cfg )
2026-06-09 00:32:42 -04:00
blocklists = cfg . get ( ' dns_blocking ' , { } ) . get ( ' blocklists ' , [ ] )
tokens [ ' BLOCKLIST_EXISTING_NAMES_JS ' ] = json . dumps ( [ bl . get ( ' name ' , ' ' ) for bl in blocklists ] )
2026-06-09 02:23:57 -04:00
vlans = cfg . get ( ' vlans ' , [ ] )
vlan_checkboxes = ' ' . join (
f ' <label class= " form-checkbox-row " > '
f ' <input type= " checkbox " name= " used_by_vlans " value= " { factory . e ( v [ " name " ] ) } " class= " form-checkbox " > '
f ' <span class= " form-checkbox-label " > { factory . e ( v [ " name " ] ) } </span> '
f ' </label> '
for v in vlans if v . get ( ' name ' )
)
tokens [ ' VLAN_USED_BY_CHECKBOXES ' ] = (
2026-06-09 02:30:30 -04:00
f ' <div class= " form-group " ><label class= " form-label " >Used By VLAN(s)</label> '
2026-06-09 02:23:57 -04:00
f ' <div> { vlan_checkboxes } </div></div> '
) if vlan_checkboxes else ' '
2026-06-07 00:21:08 -04:00
content = factory . load_json ( f ' { factory . PAGES_DIR } /dnsblocking/content.json ' )
for table_item in factory . iter_table_items ( content . get ( ' items ' , [ ] ) ) :
2026-06-02 12:49:39 -04:00
ds = table_item . get ( ' datasource ' , ' ' )
2026-06-07 00:21:08 -04:00
tokens [ factory . table_token_key ( ds ) ] = factory . build_table ( table_item , tokens , config_utils . load_datasource ( ds ) )
2026-06-02 12:49:39 -04:00
return tokens