Development
This commit is contained in:
parent
0c0589a0b1
commit
a55d44f480
3 changed files with 131 additions and 59 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import copy
|
import copy
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import json
|
||||||
|
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
from auth import require_level
|
from auth import require_level
|
||||||
|
|
@ -62,6 +63,39 @@ def addvlan_add():
|
||||||
if not _hash_ok():
|
if not _hash_ok():
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
server_identities_raw = request.form.get('server_identities', '[]')
|
||||||
|
try:
|
||||||
|
raw_identities = json.loads(server_identities_raw)
|
||||||
|
if not isinstance(raw_identities, list):
|
||||||
|
raise ValueError
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
flash('Invalid identity data.', 'error')
|
||||||
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
new_identities = []
|
||||||
|
if raw_identities:
|
||||||
|
_vlan_net = ipaddress.IPv4Network(f'{subnet}/{subnet_mask}', strict=False)
|
||||||
|
for raw in raw_identities:
|
||||||
|
ip_clean = sanitize.ip(str(raw.get('ip', '')))
|
||||||
|
if not ip_clean:
|
||||||
|
flash('Invalid IP address in identity.', 'error')
|
||||||
|
return redirect(f'/{_PAGE}')
|
||||||
|
if ipaddress.IPv4Address(ip_clean) not in _vlan_net:
|
||||||
|
flash(f"Identity IP '{ip_clean}' is not in the VLAN subnet ({subnet}/{subnet_mask}).", 'error')
|
||||||
|
return redirect(f'/{_PAGE}')
|
||||||
|
ident = {'ip': ip_clean}
|
||||||
|
desc = str(raw.get('description', '')).strip()
|
||||||
|
if desc:
|
||||||
|
ident['description'] = desc
|
||||||
|
hostname_raw = str(raw.get('hostname', '')).strip()
|
||||||
|
if hostname_raw:
|
||||||
|
clean_hostname = sanitize.hostname(hostname_raw)
|
||||||
|
if clean_hostname is None:
|
||||||
|
flash(f"'{hostname_raw}' is not a valid hostname.", 'error')
|
||||||
|
return redirect(f'/{_PAGE}')
|
||||||
|
ident['hostname'] = clean_hostname
|
||||||
|
new_identities.append(ident)
|
||||||
|
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
vlans = cfg.setdefault('vlans', [])
|
vlans = cfg.setdefault('vlans', [])
|
||||||
|
|
||||||
|
|
@ -82,6 +116,7 @@ def addvlan_add():
|
||||||
'use_blocklists': use_blocklists,
|
'use_blocklists': use_blocklists,
|
||||||
'radius_default': radius_default,
|
'radius_default': radius_default,
|
||||||
'mdns_reflection': mdns_reflection,
|
'mdns_reflection': mdns_reflection,
|
||||||
|
'server_identities': new_identities,
|
||||||
}
|
}
|
||||||
if is_vpn:
|
if is_vpn:
|
||||||
entry['peers'] = []
|
entry['peers'] = []
|
||||||
|
|
|
||||||
|
|
@ -217,8 +217,34 @@
|
||||||
"type": "hr"
|
"type": "hr"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "identity_builder",
|
"type": "record_editor",
|
||||||
"label": "Router Identities on this VLAN:"
|
"label": "Router Identities on this VLAN:",
|
||||||
|
"name": "server_identities",
|
||||||
|
"empty_message": "No identities added.",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"label": "IP Address",
|
||||||
|
"name": "ip",
|
||||||
|
"valtype": "address",
|
||||||
|
"attrs": {
|
||||||
|
"data-dep-subnet": "[name='subnet']",
|
||||||
|
"data-dep-mask": ".subnet-prefix-input"
|
||||||
|
},
|
||||||
|
"placeholder": "x.x.x.x",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Description",
|
||||||
|
"name": "description",
|
||||||
|
"placeholder": "Optional label"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Hostname",
|
||||||
|
"name": "hostname",
|
||||||
|
"validate": "networkname",
|
||||||
|
"placeholder": "Optional"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "hr"
|
"type": "hr"
|
||||||
|
|
|
||||||
|
|
@ -1205,65 +1205,76 @@ def _render_item(item, tokens, inherited_req=None):
|
||||||
value = e(apply_tokens(item.get('value', ''), tokens))
|
value = e(apply_tokens(item.get('value', ''), tokens))
|
||||||
return f'<input type="hidden" name="{name}" value="{value}"/>'
|
return f'<input type="hidden" name="{name}" value="{value}"/>'
|
||||||
|
|
||||||
if t == 'identity_builder':
|
if t == 'record_editor':
|
||||||
label = e(item.get('label', 'Self Ident(s):'))
|
label = e(item.get('label', ''))
|
||||||
|
name = e(item.get('name', ''))
|
||||||
|
empty = e(item.get('empty_message', 'No records added.'))
|
||||||
|
fields = item.get('fields', [])
|
||||||
|
col_count = len(fields) + 1
|
||||||
|
|
||||||
|
ths = ''.join(f'<th>{e(f.get("label",""))}</th>' for f in fields) + '<th></th>'
|
||||||
|
|
||||||
|
form_rows = ''
|
||||||
|
for f in fields:
|
||||||
|
f_label = e(f.get('label', ''))
|
||||||
|
f_name = e(f.get('name', ''))
|
||||||
|
f_placeholder = e(f.get('placeholder', ''))
|
||||||
|
f_required = 'true' if f.get('required') else 'false'
|
||||||
|
f_validate = f.get('validate', '')
|
||||||
|
f_valtype = f.get('valtype', '')
|
||||||
|
f_attrs = f.get('attrs', {})
|
||||||
|
|
||||||
|
attr_str = f' data-field="{f_name}" data-required="{f_required}"'
|
||||||
|
if f_validate:
|
||||||
|
attr_str += f' data-validate="{e(f_validate)}"'
|
||||||
|
if f_valtype:
|
||||||
|
attr_str += f' data-valtype="{e(f_valtype)}"'
|
||||||
|
for ak, av in f_attrs.items():
|
||||||
|
attr_str += f' {e(ak)}="{e(str(av))}"'
|
||||||
|
|
||||||
|
inp = f'<input type="text" class="form-input"{attr_str} placeholder="{f_placeholder}"/>'
|
||||||
|
if f_validate or f_valtype:
|
||||||
|
field_inner = (
|
||||||
|
'<div class="field-wrap">'
|
||||||
|
+ inp +
|
||||||
|
'<p class="form-hint field-dyn-hint hidden"></p>'
|
||||||
|
'</div>'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
field_inner = inp
|
||||||
|
|
||||||
|
form_rows += (
|
||||||
|
f'<div class="form-group">'
|
||||||
|
f'<label class="form-label">{f_label}</label>'
|
||||||
|
f'{field_inner}'
|
||||||
|
f'</div>'
|
||||||
|
)
|
||||||
|
|
||||||
|
n = len(fields)
|
||||||
|
grid_class = f'form-row-{n}' if n in (2, 3, 4) else 'form-row-3'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
'<div class="form-group identity-builder">'
|
f'<div class="form-group record-editor" data-name="{name}" data-empty-message="{empty}">'
|
||||||
f'<label class="form-label">{label}</label>'
|
f'<label class="form-label">{label}</label>'
|
||||||
'<div class="identity-builder-body">'
|
f'<div class="record-editor-body">'
|
||||||
'<div class="identity-builder-list">'
|
f'<table class="data-table record-editor-table">'
|
||||||
'<table class="data-table identity-list-table">'
|
f'<thead><tr>{ths}</tr></thead>'
|
||||||
'<thead><tr><th>IP Address</th><th>Description</th><th>Hostname</th><th></th></tr></thead>'
|
f'<tbody class="record-editor-rows">'
|
||||||
'<tbody class="identity-builder-rows">'
|
f'<tr class="record-editor-empty-row">'
|
||||||
'<tr class="identity-empty-row">'
|
f'<td colspan="{col_count}" class="table-empty">{empty}</td>'
|
||||||
'<td colspan="4" class="table-empty">No identities added.</td>'
|
f'</tr>'
|
||||||
'</tr>'
|
f'</tbody>'
|
||||||
'</tbody>'
|
f'</table>'
|
||||||
'</table>'
|
f'<div class="record-editor-form">'
|
||||||
'</div>'
|
f'<div class="{grid_class}">{form_rows}</div>'
|
||||||
'<div class="identity-builder-form">'
|
f'<div style="margin-top:0.5rem">'
|
||||||
'<table class="inline-edit-labeled-table">'
|
f'<button type="button" class="btn btn-secondary btn-sm record-editor-add-btn">Add</button>'
|
||||||
'<thead><tr><th></th><th></th></tr></thead>'
|
f'<button type="button" class="btn btn-ghost btn-sm record-editor-cancel-btn hidden" style="margin-left:0.5rem">Cancel</button>'
|
||||||
'<tbody>'
|
f'</div>'
|
||||||
'<tr>'
|
f'</div>'
|
||||||
'<td class="identity-input-label">IP Address:</td>'
|
f'</div>'
|
||||||
'<td>'
|
f'<input type="hidden" name="{name}" class="record-editor-hidden" value="[]"/>'
|
||||||
'<div class="field-wrap">'
|
f'</div>'
|
||||||
'<input type="text" class="form-input inline-edit-input identity-ip-input"'
|
|
||||||
' placeholder="x.x.x.x"'
|
|
||||||
' data-valtype="address"'
|
|
||||||
' data-dep-subnet="[name=\'subnet\']"'
|
|
||||||
' data-dep-mask=".subnet-prefix-input"/>'
|
|
||||||
'<p class="form-hint field-dyn-hint hidden"></p>'
|
|
||||||
'</div>'
|
|
||||||
'</td>'
|
|
||||||
'</tr>'
|
|
||||||
'<tr>'
|
|
||||||
'<td class="identity-input-label">Description (Opt):</td>'
|
|
||||||
'<td><input type="text" class="form-input inline-edit-input identity-desc-input"/></td>'
|
|
||||||
'</tr>'
|
|
||||||
'<tr>'
|
|
||||||
'<td class="identity-input-label">Hostname (Opt):</td>'
|
|
||||||
'<td>'
|
|
||||||
'<div class="form-group field-wrap" style="margin:0">'
|
|
||||||
'<input type="text" class="form-input inline-edit-input identity-host-input"'
|
|
||||||
' data-validate="networkname"/>'
|
|
||||||
'<p class="form-hint field-dyn-hint hidden"></p>'
|
|
||||||
'</div>'
|
|
||||||
'</td>'
|
|
||||||
'</tr>'
|
|
||||||
'<tr>'
|
|
||||||
'<td></td>'
|
|
||||||
'<td><button type="button" class="btn btn-secondary btn-sm identity-add-btn">Add</button></td>'
|
|
||||||
'</tr>'
|
|
||||||
'</tbody>'
|
|
||||||
'</table>'
|
|
||||||
'</div>'
|
|
||||||
'</div>'
|
|
||||||
'<textarea name="server_identity_ips" class="hidden"></textarea>'
|
|
||||||
'<textarea name="server_identity_descriptions" class="hidden"></textarea>'
|
|
||||||
'<textarea name="server_identity_hostnames" class="hidden"></textarea>'
|
|
||||||
'</div>'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if t == 'field':
|
if t == 'field':
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue