restrictions: add a Family Time Controls Wizard - #147
Merged
Conversation
Compresses picking a device group, setting a block schedule, and saving into one guided flow. Writes the restriction_rule section directly rather than driving the ~40-field general-purpose Add Rule form: the wizard's scope (group + schedule + always-full-block) never touches the IP-table/port/ protocol/URL-matching fields that make up most of that form. Supports selecting multiple groups in one rule (make_nftables_rules.c already splits local_addr on commas and emits one @setname match per GROUP: token). The "give more time" override needs no new UI: createReenableCell already attaches to every restriction_rule row saveChanges()/resetData() render.
ispyisail
changed the base branch from
refactor/dedup-wg-client-config
to
master
July 30, 2026 08:17
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #145 (base branch is
refactor/dedup-wg-client-config, notmaster) for a clean stack, though this feature itself doesn't depend on the WireGuard dedup work at all, it's independent of #146 too. Fine to retarget tomasterdirectly if preferred once #144/#145 land; opened against the same base as the other two wizard PRs to keep the merge order simple.What this adds
The Restrictions page today is an accurate, general-purpose firewall rule builder (IP/port/protocol/URL matching, ingress vs egress, address types). Nothing about it says "parental controls" to a parent who just wants to block a kid's tablet at bedtime.
This adds a "Quick Setup: Family Time Controls" callout that walks through: pick one or more device groups, pick a time range and days, done. No firewall jargon anywhere in it.
A different reuse shape than #146, and why
#146 (the WireGuard wizard) drove the real add-client modal programmatically, because that form is small (~15 fields). The Restrictions page's own Add Rule form is much bigger (~40 fields: IP tables, ports, protocols, transport type, URL matching), none of which this wizard's scope (group + schedule + always-full-block) ever touches.
So this wizard writes the
restriction_ruleUCI section directly viauci.set(), a small, explicit subset of that schema, then calls the real, unmodifiedsaveChanges()for persistence. That function updatesuciOriginaland callsresetData()itself (this page has no reload, unlike the WireGuard page), so the newly-created rule shows up in the normal table, fully rendered, with no extra work.The override control needed zero new UI
createReenableCell()(the "30 min / 1 hr / 4 hr / Until Tomorrow" picker) is already unconditionally attached to everyrestriction_rulerow byresetData(). A rule this wizard creates gets it automatically the momentsaveChanges()re-renders the table, i.e. as soon as the wizard's own "Save Changes" button is clicked. Verified directly: after running the wizard end to end, the resulting row's reenable cell has realreenable_btn_30m/reenable_btn_tomorrowbuttons, not stand-ins.Multi-group, not one-rule-per-group
Per the RFC's own finding:
make_nftables_rules.c(:832,:842) already splitslocal_addron commas and emits one@setnamematch perGROUP:token, with an explicit source comment that named sets must never be combined with{}syntax. Solocal_addr = "GROUP:Kids,GROUP:Teens"was already the backend's preferred shape; the frontend just never exposed selecting more than one. This wizard writes exactly that comma-joined form for a multi-group selection, one rule, not N rules, verified against the same parsing logic (no whitespace, no braces, each token a bareGROUP:entry).A harness fix this exposed, not worked around
tests/restrictions/harness.jshad no DOM fixture at all before this (it only ever needed to exercise the reenable-cell widget in isolation). Calling the wizard's ownsaveChanges()end to end meant, for the first time, exercisingresetData()'s full render path, which unconditionally depends on every field of the general-purpose Add Rule form existing (same as the real page: those fields are always in the DOM, just not visible). Extended the harness with the full field set (mirrored acrossrule_/exception_prefixes, confirmed identical via diff ofrestriction_templatevswhitelist_template) rather than stubbing the real functions away. Also convertedrestStr/UIto a Proxy fallback (same pattern already used in the WireGuard harness) so an i18n key the wizard doesn't touch reads back as a harmless placeholder instead of crashing table-header rendering withundefined.Testing
14 new tests:
wizard-rule-shape.test.js(6): the created section is a genuinerestriction_rule, field-for-field matches a hand-built full-block/group-targeted/scheduled rule, a specific day selection is written (not defaulted to every day), a same-day time range stays a single span, an overnight range correctly splits into two comma-joined ranges, persists correctly through the realsaveChanges().wizard-multigroup.test.js(4): single vs multi-group shape, no whitespace/braces, group order preserved.wizard-override.test.js(4): drives the realreenableSetPreset/reenableSetTomorrow/saveChanges()on a wizard-created rule specifically, both override shapes (fixed duration and next-local-midnight), plus re-enabling before save correctly cancels the pending timer.Full suite: 36/36 (22 pre-existing + 14 new).
page_lint.test.sh: 11 pass, 3 fail. Confirmed pre-existing by running the identical check againstmaster: byte-for-byte the same 3 failures. This page'srestriction.sh/restrictions.jsfilename split (a known, pre-existing convention, not something this PR touches) trips the lint tool's naming assumption.Full node lane, run on this branch specifically: initially showed 15 additional failures, all in
tests/wireguard/wizard-*.test.js, testing #146'sopenRemoteAccessWizard(), which this branch never had (it's a sibling of #146, not stacked on it, so this checkout'swireguard.jsdoesn't include that code). Confirmed as a branch-topology artifact, not a regression, by excluding those files: clean, only the same 2 unrelated pre-existing failures (access-dropbear-preserve,vlan/switchinfo-emit), reconfirmed pre-existing on this branch by stashing this entire diff and re-running.node --checkclean, tabs verified, no em dashes, i18n BOM intact.Not verified
The override's real-world timing:
restriction_reenable.sh's cron sweep actually flipping a rule back on at the computed time. Only the UCI-value math (reenable_atlands within the expected window, or at the exact computed next-local-midnight value) is tested here, not the shell script's execution on a real router.Out of scope
Category-based content filtering (a genuinely different, URL/domain-list-driven feature tracked in
docs/dns-threat-blocking-plan.md). Per-app blocking, bandwidth limits, reporting/dashboards. Any new blocking mechanism, if you're expecting nftables rule changes here, there are none.