From 7825b9cd1a4fe929eb9ef5a0f8b99ad1e889286f Mon Sep 17 00:00:00 2001 From: Matthew Grotke Date: Mon, 25 May 2026 02:51:38 -0400 Subject: [PATCH] Development --- docker/routlin-dash/app/view_page.py | 47 +++++++++++++++++----- docker/routlin-dash/data/page_content.json | 33 ++++++++++----- 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/docker/routlin-dash/app/view_page.py b/docker/routlin-dash/app/view_page.py index bca5e68..434c0f5 100644 --- a/docker/routlin-dash/app/view_page.py +++ b/docker/routlin-dash/app/view_page.py @@ -1128,15 +1128,17 @@ def _render_field(item, tokens): if input_type == 'number': min_attr = f' min="{item["min"]}"' if 'min' in item else '' max_attr = f' max="{item["max"]}"' if 'max' in item else '' + dyn_hint_html = '' inp = (f'') + f' class="form-input{extra_cls}"{readonly}' + f' data-validate="positive_int" />') if item.get('layout') == 'inline': return (f'
' f'' - f'
{inp}
' + f'
{inp}{dyn_hint_html}
' f'{hint_html}
') return (f'
' - f'{inp}{hint_html}
') + f'{inp}{dyn_hint_html}{hint_html}') if input_type == 'textarea': rows = item.get('rows', 4) @@ -1878,6 +1880,17 @@ function classifyTime24h(s) { return 'complete'; } +function classifyPositiveInt(s, el) { + if (el && el.validity && el.validity.badInput) return 'invalid_char'; + if (!s && s !== '0') return 'empty'; + if (/[^0-9]/.test(s)) return 'invalid_char'; + var n = parseInt(s, 10); + var min = (el && el.min !== '') ? parseInt(el.min, 10) : 0; + var max = (el && el.max !== '') ? parseInt(el.max, 10) : null; + if (n < min || (max !== null && n > max)) return 'out_of_range'; + return 'complete'; +} + function classifySubnet(s) { if (!s) return 'empty'; if (/[^0-9.]/.test(s)) return 'invalid_char'; @@ -2078,8 +2091,10 @@ document.addEventListener('click', function(e) { if (inputType === 'checkbox') { var checked = (val === true || val === 'true' || val === 1 || val === '1'); - td.innerHTML = ''; + var cbLabel = fDef.checkbox_label ? ' ' + esc(fDef.checkbox_label) + '' : ''; + td.innerHTML = ''; } else if (inputType === 'checkbox_multi') { var opts = fDef.options || []; var checked = []; @@ -2106,7 +2121,9 @@ document.addEventListener('click', function(e) { var minAttr = fDef.min !== undefined ? ' min="' + esc(String(fDef.min)) + '"' : ''; var maxAttr = fDef.max !== undefined ? ' max="' + esc(String(fDef.max)) + '"' : ''; td.innerHTML = ''; + '"' + minAttr + maxAttr + ' class="form-input inline-edit-input" data-validate="positive_int"/>' + + ''; + if (typeof validateEl === 'function') validateEl(td.querySelector('input')); } else if (inputType === 'textarea') { var textVal; try { var arr = JSON.parse(val); textVal = Array.isArray(arr) ? arr.join('\n') : String(val||''); } @@ -2254,21 +2271,30 @@ var validateEl; invalid_struct: 'Invalid domain format' }, networkname: { invalid_char: 'Letters, digits, hyphens and underscores only', invalid_struct: 'No leading, trailing or consecutive special characters' }, - time_24h: { invalid_char: 'Digits and colon only', invalid_struct: 'Must be HH:MM in 24-hour format (e.g. 02:30)' } + time_24h: { invalid_char: 'Digits and colon only', invalid_struct: 'Must be HH:MM in 24-hour format (e.g. 02:30)' }, + positive_int: { invalid_char: 'Digits only', + out_of_range: function(el) { + var mn = (el && el.min !== '') ? el.min : null; + var mx = (el && el.max !== '') ? el.max : null; + if (mn !== null && mx !== null) return 'Must be between ' + mn + ' and ' + mx; + if (mn !== null) return 'Must be ≥ ' + mn; + if (mx !== null) return 'Must be ≤ ' + mx; + return 'Out of range'; + }} }; var _classifiers = { ip: classifyIp, ipv4: classifyIpv4, ipv6: classifyIpv6, mac: classifyMac, subnet: classifySubnet, url: classifyUrl, port: classifyPort, ipv4cidr: classifyIpv4Cidr, endpoint: classifyEndpoint, dashname: classifyDashname, domainname: classifyDomainname, networkname: classifyNetworkname, - time_24h: classifyTime24h }; + time_24h: classifyTime24h, positive_int: classifyPositiveInt }; validateEl = function(el) { var list = el.closest('.editable-list[data-validate]'); var vtype = el.dataset.validate || (list ? list.dataset.validate : ''); var classify = _classifiers[vtype]; if (!classify) return; - var cls = classify(el.value); + var cls = classify(el.value, el); if (list) { el.classList.remove('field-invalid', 'field-warning'); if (cls === 'incomplete') el.classList.add('field-warning'); @@ -2280,7 +2306,8 @@ var validateEl; } else if (cls === 'incomplete') { setFieldHint(el, el._postValidate ? el._postValidate(cls) : '', 'warning'); } else { - setFieldHint(el, msgs[cls] || 'Invalid', 'error'); + var msgVal = msgs[cls]; + setFieldHint(el, typeof msgVal === 'function' ? msgVal(el) : (msgVal || 'Invalid'), 'error'); } } }; diff --git a/docker/routlin-dash/data/page_content.json b/docker/routlin-dash/data/page_content.json index b839d4c..a052da1 100644 --- a/docker/routlin-dash/data/page_content.json +++ b/docker/routlin-dash/data/page_content.json @@ -974,7 +974,8 @@ }, { "col": "enabled", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" } ] }, @@ -1100,7 +1101,8 @@ }, { "col": "enabled", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" } ] }, @@ -1519,15 +1521,18 @@ }, { "col": "radius_default", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" }, { "col": "mdns_reflection", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" }, { "col": "dnsmasq_log_queries", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Record" }, { "col": "use_blocklists", @@ -1754,7 +1759,8 @@ }, { "col": "enabled", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" } ] }, @@ -1920,7 +1926,8 @@ }, { "col": "enabled", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" } ] }, @@ -2128,11 +2135,13 @@ }, { "col": "radius_client", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" }, { "col": "enabled", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" } ] }, @@ -2326,11 +2335,13 @@ }, { "col": "split_tunnel", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" }, { "col": "enabled", - "input_type": "checkbox" + "input_type": "checkbox", + "checkbox_label": "Enabled" } ] },