Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions package/gargoyle/files/www/js/restrictions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1266,3 +1266,163 @@ function editRestrictionModal(isRestriction, triggerEl)
modalPrepare(ruleType + '_modal', isRestriction ? restStr.ERSect : restStr.EESect, modalElements, modalButtons);
openModalWindow(ruleType + '_modal');
}

// ---- Family Time Controls Wizard ----
//
// A plain-language front end over the same restriction_rule schema the full
// Add New Rule form writes -- see docs/wizards/02-family-time.md
// (gargoyle-tools repo) for the design rationale.
//
// Deliberately NOT built by driving the general-purpose modal/setUciFromDocument
// the way spec 01's wizard reused addAc(): that form has ~40 fields (IP tables,
// ports, protocols, URL matching) the wizard's own scope (device/group +
// schedule + always-full-block) never touches at all. Writing the section
// directly via uci.set() is a small, narrow subset of that schema, not a
// duplicate of the general form's much larger logic.
//
// The override/"give more time" control (createReenableCell) is NOT
// reimplemented either: it's already attached automatically to every
// restriction_rule row resetData() renders, so a rule created here gets it for
// free the moment the real saveChanges() (also unmodified) re-renders the
// table -- no separate wizard step needed for it.

var familyTimeWizardSelectedGroups = [];

function openFamilyTimeWizard()
{
if(knownDeviceGroups.length === 0)
{
showFamilyTimeWizardPanel("noGroups");
modalPrepare('family_time_wizard_modal', restStr.FTWTitle, [], ["defaultDismiss"]);
openModalWindow('family_time_wizard_modal');
return;
}

var listEl = document.getElementById("family_time_wizard_group_list");
while(listEl.firstChild) { listEl.removeChild(listEl.firstChild); }
var gi;
for(gi = 0; gi < knownDeviceGroups.length; gi++)
{
var group = knownDeviceGroups[gi];
var wrap = document.createElement("div");
var cb = createInput("checkbox");
cb.id = "family_time_wizard_group_" + gi;
cb.value = group;
var lbl = document.createElement("label");
lbl.setAttribute("for", cb.id);
lbl.textContent = " " + group;
wrap.appendChild(cb);
wrap.appendChild(lbl);
listEl.appendChild(wrap);
}

showFamilyTimeWizardPanel("step1");
modalPrepare('family_time_wizard_modal', restStr.FTWTitle, [],
[
{"title" : restStr.FTWNext, "classes" : "btn btn-primary", "function" : familyTimeWizardStep1Next},
"defaultDismiss"
]);
openModalWindow('family_time_wizard_modal');
}

function showFamilyTimeWizardPanel(name)
{
["noGroups", "step1", "step2", "done"].forEach(function(panel)
{
document.getElementById("family_time_wizard_" + panel).style.display = panel == name ? "block" : "none";
});
}

function familyTimeWizardStep1Next()
{
var listEl = document.getElementById("family_time_wizard_group_list");
var checked = Array.prototype.slice.call(listEl.querySelectorAll("input[type=checkbox]")).filter(function(cb){ return cb.checked; });
if(checked.length === 0)
{
alert(restStr.FTWNoGroupSelected);
return;
}
familyTimeWizardSelectedGroups = checked.map(function(cb){ return cb.value; });

showFamilyTimeWizardPanel("step2");
modalPrepare('family_time_wizard_modal', restStr.FTWTitle, [],
[
{"title" : restStr.FTWCreate, "classes" : "btn btn-primary", "function" : familyTimeWizardCreate},
"defaultDismiss"
]);
}

// "HH:MM" only (unlike validateHours, which also accepts a bare hour) -- the
// wizard's two plain text fields are simple enough to ask for the full form.
function familyTimeWizardValidTime(str)
{
return /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/.test(str);
}

// Builds the active_hours value for a from/to pair, splitting into two
// comma-joined ranges when the range crosses midnight (e.g. 21:00 to 07:00),
// since a single HH:MM-HH:MM range can't express that within one calendar
// day. validateHours (common.js) accepts either shape.
function familyTimeWizardHoursRange(fromStr, toStr)
{
if(fromStr < toStr)
{
return fromStr + "-" + toStr;
}
return fromStr + "-23:59,00:00-" + toStr;
}

function familyTimeWizardCreate()
{
var fromStr = document.getElementById("family_time_wizard_from").value;
var toStr = document.getElementById("family_time_wizard_to").value;
if(!familyTimeWizardValidTime(fromStr) || !familyTimeWizardValidTime(toStr) || fromStr == toStr)
{
alert(restStr.FTWBadTimes);
return;
}

var dayIds = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
var checkedDays = dayIds.filter(function(d){ return document.getElementById("family_time_wizard_" + d).checked; });
if(checkedDays.length === 0)
{
alert(restStr.FTWNoDaySelected);
return;
}

var newIndex = 1;
var newId = "rule_" + newIndex;
while(uci.get(pkg, newId, "") != "")
{
newIndex++;
newId = "rule_" + newIndex;
}

uci.set(pkg, newId, "", "restriction_rule");
uci.set(pkg, newId, "is_ingress", "0");
uci.set(pkg, newId, "enabled", "1");
uci.set(pkg, newId, "family", "any");
uci.set(pkg, newId, "description", restStr.FTWTitle + ": " + familyTimeWizardSelectedGroups.join(", "));
// One rule targeting every selected group, not one rule per group:
// make_nftables_rules.c splits local_addr on commas and emits one @setname
// match per GROUP: token, so this is already the backend's own preferred
// shape, not a workaround.
uci.set(pkg, newId, "local_addr", familyTimeWizardSelectedGroups.map(function(g){ return "GROUP:" + g; }).join(","));
if(checkedDays.length < 7)
{
uci.set(pkg, newId, "active_weekdays", checkedDays.join(","));
}
uci.set(pkg, newId, "active_hours", familyTimeWizardHoursRange(fromStr, toStr));
// url_type, remote_port_type, local_port_type, transport_protocol,
// app_protocol_type are all deliberately left unset here: on this schema,
// unset reads back as "all" (see the all_access checkbox's own load-time
// derivation above), which is exactly "block everything" -- the whole of
// this wizard's v1 scope. There is no separate field to set for it.

showFamilyTimeWizardPanel("done");
modalPrepare('family_time_wizard_modal', restStr.FTWTitle, [],
[
{"title" : UI.SaveChanges, "classes" : "btn btn-primary",
"function" : function(){ closeModalWindow('family_time_wizard_modal'); saveChanges(); }},
]);
}
61 changes: 61 additions & 0 deletions package/gargoyle/files/www/restriction.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@
</script>

<h1 class="page-header"><%~ restrictions.mRestrict %></h1>

<div class="row">
<div id="restriction_wizard_callout" class="col-lg-6">
<div class="panel panel-info">
<div class="panel-body">
<p><%~ restrictions.FTWIntro %></p>
<button id="restriction_wizard_open" class="btn btn-primary" onclick="openFamilyTimeWizard()"><%~ restrictions.FTWBtn %></button>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-lg-6">
<div class="panel panel-default">
Expand Down Expand Up @@ -85,6 +97,55 @@
<button id="reset_button" class="btn btn-warning btn-lg" onclick="resetData()"><%~ Reset %></button>
</div>

<div class="modal fade" tabindex="-1" role="dialog" id="family_time_wizard_modal" aria-hidden="true" aria-labelledby="family_time_wizard_modal_title">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h3 id="family_time_wizard_modal_title" class="panel-title"><%~ restrictions.FTWTitle %></h3>
</div>
<div class="modal-body">
<div id="family_time_wizard_noGroups" style="display:none">
<p><%~ restrictions.FTWNoGroups %></p>
</div>

<div id="family_time_wizard_step1" style="display:none">
<p><%~ restrictions.FTWStep1Desc %></p>
<div id="family_time_wizard_group_list"></div>
</div>

<div id="family_time_wizard_step2" style="display:none">
<p><%~ restrictions.FTWStep2Desc %></p>
<div class="row form-group">
<label class="col-xs-3" for='family_time_wizard_from'><%~ restrictions.FTWFrom %>:</label>
<span class="col-xs-3"><input type='text' class="form-control" id='family_time_wizard_from' size='6' placeholder='21:00' /></span>
<label class="col-xs-3" for='family_time_wizard_to'><%~ restrictions.FTWTo %>:</label>
<span class="col-xs-3"><input type='text' class="form-control" id='family_time_wizard_to' size='6' placeholder='07:00' /></span>
</div>
<div class="row form-group">
<label class="col-xs-5"><%~ restrictions.FTWDays %>:</label>
<span class="col-xs-7">
<input type='checkbox' id='family_time_wizard_sun' checked /><label for="family_time_wizard_sun"><%~ Sun %></label>
<input type='checkbox' id='family_time_wizard_mon' checked /><label for="family_time_wizard_mon"><%~ Mon %></label>
<input type='checkbox' id='family_time_wizard_tue' checked /><label for="family_time_wizard_tue"><%~ Tue %></label>
<input type='checkbox' id='family_time_wizard_wed' checked /><label for="family_time_wizard_wed"><%~ Wed %></label>
<input type='checkbox' id='family_time_wizard_thu' checked /><label for="family_time_wizard_thu"><%~ Thu %></label>
<input type='checkbox' id='family_time_wizard_fri' checked /><label for="family_time_wizard_fri"><%~ Fri %></label>
<input type='checkbox' id='family_time_wizard_sat' checked /><label for="family_time_wizard_sat"><%~ Sat %></label>
</span>
</div>
</div>

<div id="family_time_wizard_done" style="display:none">
<p><%~ restrictions.FTWDone %></p>
<p><em><%~ restrictions.FTWOverrideHint %></em></p>
</div>
</div>
<div class="modal-footer" id="family_time_wizard_modal_button_container">
</div>
</div>
</div>
</div>

<div class="modal fade" tabindex="-1" role="dialog" id="restriction_rule_modal" aria-hidden="true" aria-labelledby="restriction_rule_modal_title">
<div class="modal-dialog" role="document">
<div class="modal-content">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,20 @@ restStr.RePendRelTom="Will re-enable tomorrow at midnight";
restStr.RePendRelCust="Will re-enable after Save";
restStr.RePendAbsPfx="Disabled until";
restStr.ReDurErr="ERROR: custom duration must be a whole number of minutes or hours, greater than zero";

restStr.FTWBtn="Quick Setup: Family Time Controls";
restStr.FTWIntro="Set a schedule that blocks internet access for a device or group, without needing to understand firewall rules yourself.";
restStr.FTWTitle="Family Time Controls";
restStr.FTWNoGroups="No device groups exist yet. Go to Known Devices and assign a group to at least one device, then come back here.";
restStr.FTWStep1Desc="Who is this for? Pick one or more device groups.";
restStr.FTWNoGroupSelected="Pick at least one device group.";
restStr.FTWNext="Next";
restStr.FTWStep2Desc="When should internet access be blocked? This blocks all internet access during the times you pick.";
restStr.FTWFrom="From";
restStr.FTWTo="To";
restStr.FTWDays="On these days";
restStr.FTWBadTimes="Enter a valid from and to time.";
restStr.FTWNoDaySelected="Pick at least one day.";
restStr.FTWCreate="Create";
restStr.FTWDone="Done. The schedule is saved and active.";
restStr.FTWOverrideHint="Need to give more time right now? Find this rule in the Current Restrictions list below and use its 30 min / 1 hr / 4 hr / Until Tomorrow buttons.";
Loading