UI and security improvements
This commit is contained in:
parent
9a272ee959
commit
b8c4914a52
13 changed files with 136 additions and 80 deletions
|
|
@ -2,6 +2,7 @@ from flask import Blueprint, session, redirect, get_flashed_messages
|
|||
from markupsafe import Markup
|
||||
import json, re, subprocess, os, sys, html as html_mod
|
||||
import sanitize
|
||||
import validate
|
||||
from datetime import datetime, timezone
|
||||
from config_utils import core_hash
|
||||
|
||||
|
|
@ -211,12 +212,14 @@ def _config_datasource(name):
|
|||
return rows
|
||||
|
||||
if name == 'vlans':
|
||||
core = _load_core()
|
||||
bl_desc = {b['name']: b.get('description', b['name']) for b in core.get('blocklists', []) if 'name' in b}
|
||||
rows = []
|
||||
for v in sorted(vlans, key=lambda x: x.get('vlan_id', 0)):
|
||||
row = {k: v.get(k) for k in ('vlan_id', 'name', 'subnet', 'subnet_mask', 'radius_default', 'mdns_reflection', 'is_vpn')}
|
||||
row['interface'] = _resolve_iface(v, core)
|
||||
row['use_blocklists'] = json.dumps(v.get('use_blocklists', []))
|
||||
row['use_blocklists'] = json.dumps([
|
||||
{'n': bl, 'd': bl_desc.get(bl, bl)} for bl in v.get('use_blocklists', [])
|
||||
])
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
|
|
@ -475,16 +478,16 @@ def collect_tokens():
|
|||
tokens['EXISTING_VLAN_NAMES_JSON'] = json.dumps([v.get('name', '') for v in vlans])
|
||||
tokens['EXISTING_VLAN_INTERFACES_JSON'] = json.dumps([_resolve_iface(v, core) for v in vlans])
|
||||
tokens['STAT_BANNED_IP_COUNT'] = str(sum(1 for b in core.get('banned_ips', []) if b.get('enabled', True)))
|
||||
tokens['STAT_BLOCKLIST_COUNT'] = str(sum(1 for b in core.get('blocklists', []) if b.get('enabled', True)))
|
||||
tokens['STAT_BLOCKLIST_COUNT'] = str(len(core.get('blocklists', [])))
|
||||
|
||||
ddns = _load_ddns()
|
||||
tokens['DDNS_TIMER_INTERVAL'] = ddns.get('general', {}).get('timer_interval', '-')
|
||||
enabled_p = [p for p in ddns.get('providers', []) if p.get('enabled', True)]
|
||||
tokens['STAT_DDNS_PROVIDER_COUNT'] = str(len(enabled_p))
|
||||
_ddns_labels = {'noip': 'No-IP', 'cloudflare': 'Cloudflare', 'duckdns': 'DuckDNS'}
|
||||
tokens['DDNS_PROVIDER_OPTIONS'] = json.dumps([
|
||||
{'value': 'noip', 'label': 'No-IP'},
|
||||
{'value': 'cloudflare', 'label': 'Cloudflare'},
|
||||
{'value': 'duckdns', 'label': 'DuckDNS'},
|
||||
{'value': p, 'label': _ddns_labels.get(p, p.title())}
|
||||
for p in validate.VALID_DDNS_PROVIDERS
|
||||
])
|
||||
|
||||
wg_vlan = next((v for v in vlans if v.get('is_vpn')), {})
|
||||
|
|
@ -1027,7 +1030,21 @@ def _render_table_cell(value, render_fn, col_class='', field='', row_idx=None,
|
|||
items = json.loads(value) if value.startswith('[') else [s.strip() for s in value.split(',')]
|
||||
except Exception:
|
||||
items = [value]
|
||||
tags = ''.join(f'<span class="tag">{e(str(t))}</span>' for t in items if str(t).strip())
|
||||
def _tag(t):
|
||||
if isinstance(t, dict):
|
||||
s, tooltip = str(t.get('n', '')).strip(), str(t.get('d', t.get('n', ''))).strip()
|
||||
else:
|
||||
s = tooltip = str(t).strip()
|
||||
if not s:
|
||||
return ''
|
||||
short = s.split('-')[0]
|
||||
mini = s[0]
|
||||
return (f'<span class="tag" data-tooltip="{e(tooltip)}">'
|
||||
f'<span class="tl-full">{e(s)}</span>'
|
||||
f'<span class="tl-short">{e(short)}</span>'
|
||||
f'<span class="tl-min">{e(mini)}</span>'
|
||||
f'</span>')
|
||||
tags = ''.join(_tag(t) for t in items)
|
||||
return f'{td_open}<div class="tag-list">{tags}</div></td>'
|
||||
|
||||
if render_fn == 'interface_status':
|
||||
|
|
@ -1160,6 +1177,35 @@ function deriveVlanId(subnet, prefix) {
|
|||
return (id >= 0 && id <= 4094) ? id : null;
|
||||
}
|
||||
|
||||
function networkBitsMessage(octets, prefix) {
|
||||
var byteIdx = Math.floor((prefix - 1) / 8);
|
||||
var hostBitsInActive = (prefix % 8 === 0) ? 0 : (8 - (prefix % 8));
|
||||
var activeMask = hostBitsInActive === 0 ? 0xFF : ((0xFF << hostBitsInActive) & 0xFF);
|
||||
var ordinals = ['1st', '2nd', '3rd', '4th'];
|
||||
var parts = [];
|
||||
if (hostBitsInActive > 0 && (octets[byteIdx] & ~activeMask) !== 0) {
|
||||
var step = 1 << hostBitsInActive;
|
||||
var vals = [];
|
||||
for (var v = 0; v < 256; v += step) vals.push(String(v));
|
||||
var valStr = vals.length <= 8
|
||||
? vals.slice(0, -1).join(', ') + ' or ' + vals[vals.length - 1]
|
||||
: 'a multiple of ' + step;
|
||||
parts.push(ordinals[byteIdx] + ' quartet must be ' + valStr);
|
||||
}
|
||||
var badTrailing = [];
|
||||
for (var i = byteIdx + 1; i < 4; i++) {
|
||||
if (octets[i] !== 0) badTrailing.push(ordinals[i]);
|
||||
}
|
||||
if (badTrailing.length > 0) {
|
||||
var nameStr = badTrailing.length === 1
|
||||
? badTrailing[0]
|
||||
: badTrailing.slice(0, -1).join(', ') + ' and ' + badTrailing[badTrailing.length - 1];
|
||||
parts.push(nameStr + ' quartet' + (badTrailing.length > 1 ? 's' : '') + ' must be 0');
|
||||
}
|
||||
if (parts.length === 0) return null;
|
||||
return parts.join('; ') + ' for /' + prefix;
|
||||
}
|
||||
|
||||
function classifySubnet(s) {
|
||||
if (!s) return 'empty';
|
||||
if (/[^0-9.]/.test(s)) return 'invalid_char';
|
||||
|
|
@ -1230,8 +1276,12 @@ function updateAddVlanForm(form) {
|
|||
} else if (sClass === 'range') {
|
||||
subnetMsg = 'Quartet out of range'; subnetState = 'error';
|
||||
} else {
|
||||
if (id === 0) {
|
||||
subnetMsg = 'Reserved'; subnetState = 'warning';
|
||||
var octetsArr = subnet.split('.').map(Number);
|
||||
var hostMsg = networkBitsMessage(octetsArr, prefix);
|
||||
if (hostMsg) {
|
||||
subnetMsg = hostMsg; subnetState = 'error';
|
||||
} else if (id === 0) {
|
||||
subnetMsg = 'Would compute to VLAN ID 0 (reserved)'; subnetState = 'error';
|
||||
} else if (id === null || EXISTING_VLAN_IDS.indexOf(id) !== -1) {
|
||||
subnetMsg = id === null ? '' : 'Duplicate'; subnetState = id === null ? 'warning' : 'error';
|
||||
} else {
|
||||
|
|
@ -1242,7 +1292,7 @@ function updateAddVlanForm(form) {
|
|||
|
||||
// Interface duplicate/reserved sub-text
|
||||
if (ifacePrev) {
|
||||
if (id === 0) {
|
||||
if (id === 0 && !isVpn) {
|
||||
setFieldHint(ifacePrev, 'Reserved', 'error');
|
||||
} else {
|
||||
var ifaceDupe = ifaceVal.length > 0 && EXISTING_VLAN_INTERFACES.indexOf(ifaceVal) !== -1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue