Development
This commit is contained in:
parent
b38c199baf
commit
fd7cc5c11d
9 changed files with 55 additions and 24 deletions
|
|
@ -747,7 +747,7 @@ def config_datasource(name):
|
||||||
for v in sorted(vlans, key=lambda x: x.get('vlan_id') or 0):
|
for v in sorted(vlans, key=lambda x: x.get('vlan_id') or 0):
|
||||||
row = {k: v.get(k) for k in (
|
row = {k: v.get(k) for k in (
|
||||||
'name', 'subnet', 'subnet_mask', 'radius_default',
|
'name', 'subnet', 'subnet_mask', 'radius_default',
|
||||||
'mdns_reflection', 'is_vpn', 'dnsmasq_log_queries'
|
'mdns_reflection', 'is_vpn', 'dnsmasq_log_queries_days'
|
||||||
)}
|
)}
|
||||||
row['vlan_id'] = v.get('vlan_id')
|
row['vlan_id'] = v.get('vlan_id')
|
||||||
row['interface'] = resolve_iface(v, cfg)
|
row['interface'] = resolve_iface(v, cfg)
|
||||||
|
|
|
||||||
|
|
@ -936,12 +936,16 @@ def build_table_cell(value, render_fn, col_class='', field='', row_idx=None,
|
||||||
return f'{td_open}{inner}</td>'
|
return f'{td_open}{inner}</td>'
|
||||||
|
|
||||||
if render_fn == 'badge_yes_no':
|
if render_fn == 'badge_yes_no':
|
||||||
opts = render_options or {}
|
opts = render_options or {}
|
||||||
if str(value).lower() in ('true', '1', 'yes', 'enabled'):
|
sv = str(value)
|
||||||
tip = f' data-tooltip="{e(opts["title_true"])}"' if opts.get('title_true') else ''
|
is_true = sv.lower() in ('true', '1', 'yes', 'enabled') or (sv.lstrip('-').isdigit() and int(sv) > 0)
|
||||||
|
if is_true:
|
||||||
|
title = opts.get('title_true', '').replace('{value}', sv)
|
||||||
|
tip = f' data-tooltip="{e(title)}"' if title else ''
|
||||||
inner = f'<span class="badge badge-enabled"{tip}>Yes</span>'
|
inner = f'<span class="badge badge-enabled"{tip}>Yes</span>'
|
||||||
else:
|
else:
|
||||||
tip = f' data-tooltip="{e(opts["title_false"])}"' if opts.get('title_false') else ''
|
title = opts.get('title_false', '').replace('{value}', sv)
|
||||||
|
tip = f' data-tooltip="{e(title)}"' if title else ''
|
||||||
inner = f'<span class="badge badge-disabled"{tip}>No</span>'
|
inner = f'<span class="badge badge-disabled"{tip}>No</span>'
|
||||||
return f'{td_open}{inner}</td>'
|
return f'{td_open}{inner}</td>'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ def vlans_addedit():
|
||||||
subnet_mask = sanitize.subnet_mask(request.form.get('subnet_mask', ''))
|
subnet_mask = sanitize.subnet_mask(request.form.get('subnet_mask', ''))
|
||||||
radius_default = 'radius_default' in request.form
|
radius_default = 'radius_default' in request.form
|
||||||
mdns_reflection = 'mdns_reflection' in request.form
|
mdns_reflection = 'mdns_reflection' in request.form
|
||||||
dnsmasq_log_queries = 'dnsmasq_log_queries' in request.form
|
dnsmasq_log_queries_days = max(0, int(request.form.get('dnsmasq_log_queries_days', 0) or 0))
|
||||||
restricted_vlan_raw = request.form.get('restricted_vlan', '').strip()
|
restricted_vlan_raw = request.form.get('restricted_vlan', '').strip()
|
||||||
restricted_vlan = restricted_vlan_raw if restricted_vlan_raw in ('q', 'c') else ''
|
restricted_vlan = restricted_vlan_raw if restricted_vlan_raw in ('q', 'c') else ''
|
||||||
use_blocklists = sanitize.filterlist(
|
use_blocklists = sanitize.filterlist(
|
||||||
|
|
@ -264,7 +264,7 @@ def vlans_addedit():
|
||||||
'vlan_id': vlan_id,
|
'vlan_id': vlan_id,
|
||||||
'subnet': subnet,
|
'subnet': subnet,
|
||||||
'subnet_mask': subnet_mask,
|
'subnet_mask': subnet_mask,
|
||||||
'dnsmasq_log_queries': dnsmasq_log_queries,
|
'dnsmasq_log_queries_days': dnsmasq_log_queries_days,
|
||||||
'radius_default': radius_default,
|
'radius_default': radius_default,
|
||||||
'mdns_reflection': mdns_reflection,
|
'mdns_reflection': mdns_reflection,
|
||||||
'use_blocklists': use_blocklists,
|
'use_blocklists': use_blocklists,
|
||||||
|
|
@ -324,7 +324,7 @@ def vlans_addedit():
|
||||||
'is_vpn': is_vpn,
|
'is_vpn': is_vpn,
|
||||||
'subnet': subnet,
|
'subnet': subnet,
|
||||||
'subnet_mask': subnet_mask,
|
'subnet_mask': subnet_mask,
|
||||||
'dnsmasq_log_queries': dnsmasq_log_queries,
|
'dnsmasq_log_queries_days': dnsmasq_log_queries_days,
|
||||||
'use_blocklists': use_blocklists,
|
'use_blocklists': use_blocklists,
|
||||||
'radius_default': radius_default,
|
'radius_default': radius_default,
|
||||||
'mdns_reflection': mdns_reflection,
|
'mdns_reflection': mdns_reflection,
|
||||||
|
|
|
||||||
|
|
@ -84,12 +84,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Recorded",
|
"label": "Query Log",
|
||||||
"field": "dnsmasq_log_queries",
|
"field": "dnsmasq_log_queries_days",
|
||||||
"class": "col-narrow",
|
"class": "col-narrow",
|
||||||
"render": "badge_yes_no",
|
"render": "badge_yes_no",
|
||||||
"render_options": {
|
"render_options": {
|
||||||
"title_true": "DNS Queries Recorded",
|
"title_true": "DNS Queries Recorded for {value} Days",
|
||||||
"title_false": "DNS Queries Not Recorded"
|
"title_false": "DNS Queries Not Recorded"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -339,10 +339,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Record DNS Queries",
|
"label": "Record DNS Queries (Days)",
|
||||||
"name": "dnsmasq_log_queries",
|
"name": "dnsmasq_log_queries_days",
|
||||||
"input_type": "checkbox",
|
"input_type": "number",
|
||||||
"hint": "Log every DNS query. High volume - enable for debugging only."
|
"min": 0,
|
||||||
|
"hint": "Number of days to retain DNS query logs. 0 = recording disabled."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ def load_dns_metrics(period=0):
|
||||||
|
|
||||||
|
|
||||||
def has_query_logging(cfg):
|
def has_query_logging(cfg):
|
||||||
return any(v.get('dnsmasq_log_queries') for v in cfg.get('vlans', []))
|
return any(v.get('dnsmasq_log_queries_days', 0) > 0 for v in cfg.get('vlans', []))
|
||||||
|
|
||||||
|
|
||||||
def blocked_domains_table():
|
def blocked_domains_table():
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@
|
||||||
"subnet": "192.168.1.0",
|
"subnet": "192.168.1.0",
|
||||||
"subnet_mask": 24,
|
"subnet_mask": 24,
|
||||||
"is_vpn": false,
|
"is_vpn": false,
|
||||||
"dnsmasq_log_queries": true,
|
"dnsmasq_log_queries_days": 30,
|
||||||
"radius_default": false,
|
"radius_default": false,
|
||||||
"mdns_reflection": false,
|
"mdns_reflection": false,
|
||||||
"use_blocklists": [
|
"use_blocklists": [
|
||||||
|
|
@ -324,7 +324,7 @@
|
||||||
"subnet": "192.168.10.0",
|
"subnet": "192.168.10.0",
|
||||||
"subnet_mask": 24,
|
"subnet_mask": 24,
|
||||||
"is_vpn": false,
|
"is_vpn": false,
|
||||||
"dnsmasq_log_queries": true,
|
"dnsmasq_log_queries_days": 30,
|
||||||
"radius_default": false,
|
"radius_default": false,
|
||||||
"mdns_reflection": true,
|
"mdns_reflection": true,
|
||||||
"use_blocklists": [
|
"use_blocklists": [
|
||||||
|
|
@ -355,7 +355,7 @@
|
||||||
"subnet": "192.168.20.0",
|
"subnet": "192.168.20.0",
|
||||||
"subnet_mask": 24,
|
"subnet_mask": 24,
|
||||||
"is_vpn": false,
|
"is_vpn": false,
|
||||||
"dnsmasq_log_queries": true,
|
"dnsmasq_log_queries_days": 30,
|
||||||
"radius_default": true,
|
"radius_default": true,
|
||||||
"mdns_reflection": true,
|
"mdns_reflection": true,
|
||||||
"use_blocklists": [
|
"use_blocklists": [
|
||||||
|
|
@ -386,7 +386,7 @@
|
||||||
"subnet": "192.168.30.0",
|
"subnet": "192.168.30.0",
|
||||||
"subnet_mask": 24,
|
"subnet_mask": 24,
|
||||||
"is_vpn": false,
|
"is_vpn": false,
|
||||||
"dnsmasq_log_queries": true,
|
"dnsmasq_log_queries_days": 30,
|
||||||
"radius_default": false,
|
"radius_default": false,
|
||||||
"mdns_reflection": true,
|
"mdns_reflection": true,
|
||||||
"use_blocklists": [
|
"use_blocklists": [
|
||||||
|
|
@ -418,7 +418,7 @@
|
||||||
"subnet": "192.168.40.0",
|
"subnet": "192.168.40.0",
|
||||||
"subnet_mask": 24,
|
"subnet_mask": 24,
|
||||||
"is_vpn": true,
|
"is_vpn": true,
|
||||||
"dnsmasq_log_queries": true,
|
"dnsmasq_log_queries_days": 30,
|
||||||
"radius_default": false,
|
"radius_default": false,
|
||||||
"mdns_reflection": false,
|
"mdns_reflection": false,
|
||||||
"use_blocklists": [
|
"use_blocklists": [
|
||||||
|
|
|
||||||
|
|
@ -668,6 +668,9 @@ def main():
|
||||||
inserted = dns_queries.collect(full_cfg)
|
inserted = dns_queries.collect(full_cfg)
|
||||||
if inserted:
|
if inserted:
|
||||||
log.info(f"DNS query collector: inserted {inserted} new rows.")
|
log.info(f"DNS query collector: inserted {inserted} new rows.")
|
||||||
|
pruned = dns_queries.prune(full_cfg)
|
||||||
|
if pruned:
|
||||||
|
log.info(f"DNS query collector: pruned {pruned} old rows.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"DNS query collection failed: {e}")
|
log.warning(f"DNS query collection failed: {e}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ Called by:
|
||||||
- maintenance.py on each timer tick
|
- maintenance.py on each timer tick
|
||||||
- routlin-dash overview page on each page load (background thread)
|
- routlin-dash overview page on each page load (background thread)
|
||||||
|
|
||||||
Only VLANs with dnsmasq_log_queries=true are collected.
|
Only VLANs with dnsmasq_log_queries_days > 0 are collected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
@ -84,7 +84,7 @@ def collect(data):
|
||||||
"""
|
"""
|
||||||
unit_to_vlan = {}
|
unit_to_vlan = {}
|
||||||
for vlan in data.get('vlans', []):
|
for vlan in data.get('vlans', []):
|
||||||
if not vlan.get('dnsmasq_log_queries'):
|
if not vlan.get('dnsmasq_log_queries_days', 0):
|
||||||
continue
|
continue
|
||||||
iface = validation.derive_interface(vlan, data)
|
iface = validation.derive_interface(vlan, data)
|
||||||
svc = shared.vlan_service_name(vlan, iface)
|
svc = shared.vlan_service_name(vlan, iface)
|
||||||
|
|
@ -180,3 +180,26 @@ def collect(data):
|
||||||
shared.chown_to_script_dir_owner(DB_FILE)
|
shared.chown_to_script_dir_owner(DB_FILE)
|
||||||
con.close()
|
con.close()
|
||||||
return len(rows)
|
return len(rows)
|
||||||
|
|
||||||
|
|
||||||
|
def prune(data):
|
||||||
|
"""
|
||||||
|
Delete dns_queries rows older than the retention period configured per VLAN.
|
||||||
|
Uses the maximum retention days across all logging-enabled VLANs.
|
||||||
|
Returns the number of rows deleted.
|
||||||
|
"""
|
||||||
|
days = max(
|
||||||
|
(v.get('dnsmasq_log_queries_days', 0) for v in data.get('vlans', [])),
|
||||||
|
default=0
|
||||||
|
)
|
||||||
|
if not days:
|
||||||
|
return 0
|
||||||
|
if not DB_FILE.exists():
|
||||||
|
return 0
|
||||||
|
cutoff = int(__import__('time').time()) - days * 86400
|
||||||
|
con = open_db()
|
||||||
|
cur = con.execute('DELETE FROM dns_queries WHERE ts < ?', (cutoff,))
|
||||||
|
deleted = cur.rowcount
|
||||||
|
con.commit()
|
||||||
|
con.close()
|
||||||
|
return deleted
|
||||||
|
|
|
||||||
|
|
@ -478,7 +478,7 @@ def build_vlan_dnsmasq_conf(vlan, data, iface):
|
||||||
continue # skip IPv6 upstream -- WAN has no IPv6 address
|
continue # skip IPv6 upstream -- WAN has no IPv6 address
|
||||||
L.append(f"server={srv}")
|
L.append(f"server={srv}")
|
||||||
L.append(f"cache-size={dns_cfg.get('cache_size', 1000)}")
|
L.append(f"cache-size={dns_cfg.get('cache_size', 1000)}")
|
||||||
if vlan.get("dnsmasq_log_queries", False):
|
if vlan.get("dnsmasq_log_queries_days", 0):
|
||||||
L.append("log-queries")
|
L.append("log-queries")
|
||||||
L.append("")
|
L.append("")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue