Development

This commit is contained in:
Matthew Grotke 2026-06-09 20:52:50 -04:00
parent 6f0dac01b0
commit e9166d8a6a
3 changed files with 159 additions and 159 deletions

View file

@ -49,7 +49,7 @@ def _dns_providers_table(servers):
)
return (
'<table class="data-table" style="margin-top:0.75rem"><thead><tr>'
'<th class="table-header">Server</th>'
'<th class="table-header">Upstream DNS Provider</th>'
'<th class="table-header">Queries Sent</th>'
'<th class="table-header">Retried</th>'
'<th class="table-header">Failed</th>'
@ -89,9 +89,92 @@ def load_dns_metrics():
return empty
def count_blocked_today():
out = factory.run("journalctl -u 'dnsmasq-routlin-*' --since '24 hours ago' --no-pager 2>/dev/null | grep -c ' is 0\\.0\\.0\\.0'")
return out.strip() or '0'
DNS_QUERIES_DB = f'{config_utils.CONFIGS_DIR}/dns-queries.db'
def has_query_logging(cfg):
return any(v.get('dnsmasq_log_queries') for v in cfg.get('vlans', []))
def blocked_domains_table():
no_data = '<p class="text-muted" style="margin:0.5rem 0 0">No query data collected yet.</p>'
try:
import sqlite3
if not os.path.exists(DNS_QUERIES_DB):
return no_data
con = sqlite3.connect(DNS_QUERIES_DB)
rows = con.execute(
'SELECT domain, COUNT(*) as cnt FROM dns_queries WHERE blocked=1 '
'GROUP BY domain ORDER BY cnt DESC LIMIT 10'
).fetchall()
con.close()
if not rows:
return no_data
trs = ''.join(
f'<tr><td class="table-cell">{factory.e(r[0])}</td>'
f'<td class="table-cell">{r[1]:,}</td></tr>'
for r in rows
)
return (
'<table class="data-table"><thead><tr>'
'<th class="table-header">Domain</th>'
'<th class="table-header">Times Blocked</th>'
'</tr></thead><tbody>' + trs + '</tbody></table>'
)
except Exception:
return no_data
def client_activity_table():
no_data = '<p class="text-muted" style="margin:0.5rem 0 0">No query data collected yet.</p>'
try:
import sqlite3
if not os.path.exists(DNS_QUERIES_DB):
return no_data
con = sqlite3.connect(DNS_QUERIES_DB)
rows = con.execute(
'SELECT client_ip, COUNT(*) as total, SUM(blocked) as blocked '
'FROM dns_queries GROUP BY client_ip ORDER BY total DESC LIMIT 10'
).fetchall()
con.close()
if not rows:
return no_data
trs = []
for client_ip, total, blocked in rows:
pct = f'{blocked / total * 100:.0f}%' if total else '0%'
trs.append(
f'<tr><td class="table-cell">{factory.e(client_ip)}</td>'
f'<td class="table-cell">{total:,}</td>'
f'<td class="table-cell">{int(blocked):,} ({pct})</td></tr>'
)
return (
'<table class="data-table"><thead><tr>'
'<th class="table-header">Client</th>'
'<th class="table-header">Total Queries</th>'
'<th class="table-header">Blocked</th>'
'</tr></thead><tbody>' + ''.join(trs) + '</tbody></table>'
)
except Exception:
return no_data
def all_time_blocked_display():
try:
import sqlite3
if not os.path.exists(DNS_QUERIES_DB):
return '-'
con = sqlite3.connect(DNS_QUERIES_DB)
row = con.execute(
'SELECT SUM(blocked), COUNT(*) FROM dns_queries'
).fetchone()
con.close()
blocked, total = row
if not blocked:
return '-'
pct = f'{blocked / total * 100:.0f}' if total else '0'
return f'{int(blocked):,} blocked ({pct}%)'
except Exception:
return '-'
def count_blocked_domains():
@ -118,35 +201,31 @@ def bl_last_update():
def collect_tokens(cfg):
tokens = config_utils.collect_layout_tokens(cfg)
vlans = cfg.get('vlans', [])
non_vpn_vlans = [v for v in vlans if not v.get('is_vpn')]
vlan_names = [v.get('name', '') for v in vlans]
net = cfg.get('network_interfaces', {})
dns = cfg.get('upstream_dns', {})
non_vpn_vlans = [v for v in cfg.get('vlans', []) if not v.get('is_vpn')]
dns = cfg.get('upstream_dns', {})
dns_stats = load_dns_metrics()
ddns = factory.load_ddns()
ip_str, domains_sub, last_obtained = public_ip_info(ddns)
ddns = factory.load_ddns()
ip_str, domains_sub, _ = public_ip_info(ddns)
tokens['GENERAL_WAN_INTERFACE'] = str(net.get('wan_interface', '-'))
tokens['OVERVIEW_VLAN_NAMES'] = ', '.join(vlan_names) or '-'
tokens['STAT_VLAN_COUNT'] = str(len(non_vpn_vlans))
tokens['STAT_LEASE_COUNT'] = str(len(live_dhcp_leases()))
tokens['STAT_BANNED_IP_COUNT'] = str(sum(1 for b in cfg.get('banned_ips', []) if b.get('enabled', True)))
tokens['STAT_BLOCKLIST_COUNT'] = str(len(cfg.get('dns_blocking', {}).get('blocklists', [])))
tokens['STAT_BLOCKED_TODAY'] = count_blocked_today()
tokens['STAT_BLOCKED_DOMAINS'] = count_blocked_domains()
tokens['STAT_BL_LAST_UPDATE'] = bl_last_update()
tokens['STAT_UPTIME'] = factory.run('uptime -p') or '-'
tokens['STAT_NFTABLES_STATUS'] = 'Active' if factory.run('nft list tables 2>/dev/null').strip() else 'Inactive'
tokens['STAT_PUBLIC_IP'] = ip_str
tokens['STAT_DDNS_HOSTNAME'] = domains_sub
tokens['DNS_CACHE_SIZE'] = str(dns.get('cache_size', '-'))
tokens['DNS_STAT_QUERIES'] = dns_stats['queries']
tokens['DNS_STAT_HITS'] = dns_stats['hits']
tokens['DNS_STAT_HIT_RATE'] = dns_stats['hit_rate']
tokens['DNS_STAT_FORWARDED'] = dns_stats['forwarded']
tokens['DNS_STAT_CACHE_EVICTIONS'] = dns_stats['cache_evictions']
tokens['DNS_METRICS_UPDATED'] = dns_stats['updated']
tokens['DNS_METRICS_SINCE'] = dns_stats['since']
tokens['DNS_PROVIDERS_TABLE'] = _dns_providers_table(dns_stats['servers'])
lease_count = len(live_dhcp_leases())
tokens['STAT_LEASE_COUNT'] = str(lease_count)
tokens['STAT_LEASES_LINK'] = f'<a href="/dhcpleases">{lease_count} active lease{"s" if lease_count != 1 else ""}</a>'
tokens['STAT_VLAN_COUNT'] = str(len(non_vpn_vlans))
tokens['STAT_PUBLIC_IP'] = ip_str
tokens['STAT_DDNS_HOSTNAME'] = domains_sub
tokens['DNS_CACHE_SIZE'] = str(dns.get('cache_size', '-'))
tokens['DNS_STAT_QUERIES'] = dns_stats['queries']
tokens['DNS_STAT_HITS'] = dns_stats['hits']
tokens['DNS_STAT_HIT_RATE'] = dns_stats['hit_rate']
tokens['DNS_STAT_FORWARDED'] = dns_stats['forwarded']
tokens['DNS_STAT_CACHE_EVICTIONS'] = dns_stats['cache_evictions']
tokens['DNS_METRICS_UPDATED'] = dns_stats['updated']
tokens['DNS_METRICS_SINCE'] = dns_stats['since']
tokens['DNS_PROVIDERS_TABLE'] = _dns_providers_table(dns_stats['servers'])
tokens['STAT_BLOCKED_ALLTIME'] = all_time_blocked_display()
tokens['HAS_QUERY_LOGGING'] = '1' if has_query_logging(cfg) else ''
tokens['BLOCKED_DOMAINS_TABLE'] = blocked_domains_table()
tokens['CLIENT_ACTIVITY_TABLE'] = client_activity_table()
return tokens