diff --git a/docker/routlin-dash/app/action_dnsblocklists.py b/docker/routlin-dash/app/action_dnsblocklists.py index 792ddda..2eddef9 100644 --- a/docker/routlin-dash/app/action_dnsblocklists.py +++ b/docker/routlin-dash/app/action_dnsblocklists.py @@ -1,7 +1,6 @@ from flask import Blueprint, request, redirect, flash from auth import require_level from config_utils import load_core, save_core, verify_core_hash, queued_msg -import re import sanitize import validation as validate @@ -156,7 +155,11 @@ def dnsblocklists_cardaddblocklist_add(): @bp.route('/action/dnsblocklists_cardblocklistrefresh_save', methods=['POST']) @require_level('administrator') def dnsblocklists_cardblocklistrefresh_save(): - daily_execute_time = sanitize.time_24h(request.form.get('daily_execute_time_24hr_local', '')) + daily_execute_time = validate.time_24h(sanitize.time_24h(request.form.get('daily_execute_time_24hr_local', ''))) + + if not daily_execute_time: + flash('Daily Refresh Time must be a valid 24-hour time (e.g. 02:30).', 'error') + return redirect(VIEW) if not verify_core_hash(request.form.get('config_hash', '')): flash('Configuration was modified by another session. Please refresh and try again.', 'error') diff --git a/docker/routlin-dash/app/view_page.py b/docker/routlin-dash/app/view_page.py index e206712..af20097 100644 --- a/docker/routlin-dash/app/view_page.py +++ b/docker/routlin-dash/app/view_page.py @@ -1739,6 +1739,14 @@ function classifyNetworkname(s) { return 'complete'; } +function classifyTime24h(s) { + if (!s) return 'empty'; + if (/[^0-9:]/.test(s)) return 'invalid_char'; + if (s.length < 5) return 'incomplete'; + if (!/^([01]\d|2[0-3]):[0-5]\d$/.test(s)) return 'invalid_struct'; + return 'complete'; +} + function classifySubnet(s) { if (!s) return 'empty'; if (/[^0-9.]/.test(s)) return 'invalid_char'; @@ -2114,13 +2122,15 @@ var validateEl; domainname: { invalid_char: 'Letters, digits, hyphens and dots only', invalid_struct: 'Invalid domain format' }, networkname: { invalid_char: 'Letters, digits, hyphens and underscores only', - invalid_struct: 'No leading, trailing or consecutive special characters' } + 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)' } }; 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 }; + dashname: classifyDashname, domainname: classifyDomainname, networkname: classifyNetworkname, + time_24h: classifyTime24h }; validateEl = function(el) { var list = el.closest('.editable-list[data-validate]'); diff --git a/docker/routlin-dash/data/page_content.json b/docker/routlin-dash/data/page_content.json index dd82838..369677e 100644 --- a/docker/routlin-dash/data/page_content.json +++ b/docker/routlin-dash/data/page_content.json @@ -1255,9 +1255,10 @@ "items": [ { "type": "field", - "label": "Daily Task Time", + "label": "Daily Refresh Time", "name": "daily_execute_time_24hr_local", "input_type": "text", + "validate": "time_24h", "value": "%GENERAL_DAILY_EXECUTE_TIME%", "placeholder": "e.g. 02:30", "hint": "24-hour local time for the daily blocklist refresh." diff --git a/routlin/validation.py b/routlin/validation.py index c0bf581..f6da455 100644 --- a/routlin/validation.py +++ b/routlin/validation.py @@ -94,6 +94,20 @@ def port(value): return '' +# =================================================================== +# Time +# =================================================================== + +def time_24h(value): + """Return value if it is a valid 24-hour HH:MM time string, else ''.""" + if not value: + return '' + v = str(value).strip() + if re.fullmatch(r'([01]\d|2[0-3]):[0-5]\d', v): + return v + return '' + + # =================================================================== # Integer range # ===================================================================