From c66c2e7a4d403481d6551c94ff5b2f842cce257f Mon Sep 17 00:00:00 2001 From: Matthew Grotke Date: Sun, 31 May 2026 01:19:50 -0400 Subject: [PATCH] Development --- docker/routlin-dash/app/factory.py | 5 +-- .../app/pages/networklayout/action.py | 36 +++++++++++++++++++ .../app/pages/networklayout/content.json | 12 +++++-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/docker/routlin-dash/app/factory.py b/docker/routlin-dash/app/factory.py index 068be29..423ec0a 100644 --- a/docker/routlin-dash/app/factory.py +++ b/docker/routlin-dash/app/factory.py @@ -775,18 +775,19 @@ def build_field(item, tokens): _vmask = parse_validation(validate_raw) if validate_raw else 0 validate_attr = f' data-validate="{_vmask}"' if _vmask else '' depends_attr = f' data-depends="{e(",".join(depends))}"' if depends else '' + extra_attrs = ''.join(f' {e(ak)}="{e(str(av))}"' for ak, av in item.get('attrs', {}).items()) if _vmask: return ( f'
' f'
' + f' placeholder="{placeholder}" class="form-input"{readonly}{validate_attr}{depends_attr}{extra_attrs}/>' f'
' f'{hint_html}
' ) return ( f'
' f'' + f' placeholder="{placeholder}" class="form-input"{readonly}{validate_attr}{depends_attr}{extra_attrs}/>' f'{hint_html}
' ) diff --git a/docker/routlin-dash/app/pages/networklayout/action.py b/docker/routlin-dash/app/pages/networklayout/action.py index 3e657b1..2f8267d 100644 --- a/docker/routlin-dash/app/pages/networklayout/action.py +++ b/docker/routlin-dash/app/pages/networklayout/action.py @@ -154,6 +154,36 @@ def addvlan_add(): flash(f"'{dhcp_domain_raw}' is not a valid domain name.", 'error') return redirect(f'/{_PAGE}') + dhcp_pool_start = sanitize.ip(request.form.get('dhcp_pool_start', '')) + dhcp_pool_end = sanitize.ip(request.form.get('dhcp_pool_end', '')) + if dhcp_pool_start and ipaddress.IPv4Address(dhcp_pool_start) not in _vlan_net: + flash(f"Pool start '{dhcp_pool_start}' is not in the VLAN subnet ({subnet}/{subnet_mask}).", 'error') + return redirect(f'/{_PAGE}') + if dhcp_pool_end and ipaddress.IPv4Address(dhcp_pool_end) not in _vlan_net: + flash(f"Pool end '{dhcp_pool_end}' is not in the VLAN subnet ({subnet}/{subnet_mask}).", 'error') + return redirect(f'/{_PAGE}') + if dhcp_pool_start and dhcp_pool_end: + if ipaddress.IPv4Address(dhcp_pool_start) > ipaddress.IPv4Address(dhcp_pool_end): + flash('Pool start must not be greater than pool end.', 'error') + return redirect(f'/{_PAGE}') + + _lease_time_raw = request.form.get('dhcp_lease_time', '').strip() + _lease_unit_raw = request.form.get('dhcp_lease_unit', '').strip() + dhcp_lease_time = None + if _lease_time_raw: + try: + _lt = int(_lease_time_raw) + if _lt < 1: + raise ValueError + except ValueError: + flash('Lease time must be a positive integer.', 'error') + return redirect(f'/{_PAGE}') + _unit_suffix = {'minutes': 'm', 'hours': 'h', 'days': 'd'}.get(_lease_unit_raw) + if not _unit_suffix: + flash('Lease time unit must be minutes, hours, or days.', 'error') + return redirect(f'/{_PAGE}') + dhcp_lease_time = f'{_lt}{_unit_suffix}' + cfg = load_config() vlans = cfg.setdefault('vlans', []) @@ -167,6 +197,12 @@ def addvlan_add(): dhcp_info = {} if dhcp_domain and dhcp_domain != 'local': dhcp_info['domain'] = dhcp_domain + if dhcp_pool_start: + dhcp_info['dynamic_pool_start'] = dhcp_pool_start + if dhcp_pool_end: + dhcp_info['dynamic_pool_end'] = dhcp_pool_end + if dhcp_lease_time: + dhcp_info['lease_time'] = dhcp_lease_time dhcp_overrides = {} if new_stored_gw: dhcp_overrides['gateway'] = new_stored_gw diff --git a/docker/routlin-dash/app/pages/networklayout/content.json b/docker/routlin-dash/app/pages/networklayout/content.json index 32364ec..0644c89 100644 --- a/docker/routlin-dash/app/pages/networklayout/content.json +++ b/docker/routlin-dash/app/pages/networklayout/content.json @@ -257,14 +257,22 @@ "label": "DHCP Dynamic Pool Start", "name": "dhcp_pool_start", "input_type": "text", - "placeholder": "e.g. 192.168.1.100" + "validate": "VALIDATION_ADDRESS", + "attrs": { + "data-dep-subnet": "[name='subnet']", + "data-dep-mask": ".subnet-prefix-input" + } }, { "type": "field", "label": "DHCP Dynamic Pool End", "name": "dhcp_pool_end", "input_type": "text", - "placeholder": "e.g. 192.168.1.200" + "validate": "VALIDATION_ADDRESS", + "attrs": { + "data-dep-subnet": "[name='subnet']", + "data-dep-mask": ".subnet-prefix-input" + } }, { "type": "field_row",