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'
'
)
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",