Latest parental control implementation

This commit is contained in:
Martin Schröder 2015-06-18 17:59:23 +02:00
parent 2f575f92fa
commit 01df45a0eb
18 changed files with 4063 additions and 52 deletions

10
juci-dev-upload Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
ROUTER="$1"
set -e
make clean && make DEFAULT_THEME=y
scp ~/.ssh/id_rsa.pub "root@$ROUTER:/etc/dropbear/authorized_keys"
scp -r htdocs/* root@$ROUTER:/www/
scp -r menu.d/* root@$ROUTER:/usr/share/rpcd/menu.d/

View File

@ -54,14 +54,15 @@ var rpc_calls = {
};
var spawn = require('child_process').spawn;
/*
setTimeout(function recompile(){
spawn('make', ["debug", "DEFAULT_THEME=y"], { customFds: [0,1,2] })
.on("exit", function(code){
console.log("Recompiled gui, code: "+code);
setTimeout(recompile, 5000);
});
}, 0);
}, 0); */
/**
* Real keep-alive HTTP agent

View File

@ -26,5 +26,10 @@
"modes": [ "expert" ],
"acls": [ "hostnames" ],
"index": 50
},
"internet/parental_control": {
"title": "Parental Control",
"acls": [ "firewall" ],
"index": 60
}
}

View File

@ -122,9 +122,11 @@ UCI.firewall.$registerSectionType("dmz", {
"host": { dvalue: "", type: String } // TODO: change to ip address
});
UCI.firewall.$registerSectionType("rule", {
"type": { dvalue: "generic", type: String },
"name": { dvalue: "", type: String },
"src": { dvalue: "lan", type: String },
"src_ip": { dvalue: "", type: String }, // needs to be extended type of ip address/mask
"src_mac": { dvalue: [], type: Array },
"src_port": { dvalue: 0, type: Number },
"proto": { dvalue: "tcp", type: String },
"dest": { dvalue: "*", type: String },
@ -135,9 +137,19 @@ UCI.firewall.$registerSectionType("rule", {
"icmp_type": { dvalue: [], type: Array },
"enabled": { dvalue: true, type: Boolean },
"hidden": { dvalue: true, type: Boolean },
"limit": { dvalue: "", type: String }
"limit": { dvalue: "", type: String },
// scheduling
"weekdays": { dvalue: "", type: String },
"start_time": { dvalue: "", type: String },
"stop_time": { dvalue: "", type: String },
});
UCI.firewall.$registerSectionType("settings", {
"disabled": { dvalue: false, type: Boolean },
"ping_wan": { dvalue: false, type: Boolean }
});
UCI.firewall.$registerSectionType("urlblock", {
"enabled": { dvalue: false, type: Boolean },
"url": { dvalue: [], type: Array },
"src_mac": { dvalue: [], type: Array },
});

View File

@ -0,0 +1,80 @@
<juci-layout-with-sidebar>
<div ng-controller="InternetParentalControlPage">
<h2 translate>Parental Control</h2>
<p translate>internet.parental.control.info</p>
<juci-config-section>
<h2 translate>URL Blocking Function</h2>
<juci-config-lines>
<juci-config-line title="{{'URL Blocking'|translate}}">
<switch ng-model="firewall.urlblock.enabled.value" class="green"></switch>
</juci-config-line>
</juci-config-lines>
<div class="row">
<div class="col-sm-4">
<div class="row" ng-repeat="r in urlList track by $index">
<div class="col-sm-10">
<input type="text" class="form-control" ng-model="r.url" placeholder="{{'URL'|translate}}"/>
</div>
<div class="col-sm-2">
<button class="btn btn-default" ng-click="onDeleteURL(r.url)"><i class="fa fa-trash-o"></i></button>
</div>
</div>
<div class="row">
<div class="col-sm-10"/>
<div class="col-sm-2">
<button class="btn btn-default" ng-click="onAddURL()"><i class="fa fa-plus"></i>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="row" ng-repeat="r in macList track by $index">
<div class="col-sm-10">
<input type="text" class="form-control" ng-model="r.mac" placeholder="{{'URL'|translate}}"/>
</div>
<div class="col-sm-2">
<button class="btn btn-default" ng-click="onDeleteMAC(r.mac)"><i class="fa fa-trash-o"></i></button>
</div>
</div>
<div class="row">
<div class="col-sm-10">
<juci-select ng-model="selectedMAC" ng-items="connectedHosts" placeholder="{{'Select Existing Host'|translate}}" on-change="onSelectExistingMAC($value)"/>
</div>
<div class="col-sm-2">
<button class="btn btn-default" ng-click="onAddMAC()"><i class="fa fa-plus"></i>
</div>
</div>
</div>
</div>
</juci-config-section>
<juci-config-section>
<h2 translate>Internet Access Scheduling</h2>
<table class="table">
<thead>
<th translate>Weekdays</th>
<th translate>Start Time</th>
<th translate>Stop Time</th>
<th translate>MAC Addresses</th>
<th></th>
</thead>
<tbody >
<tr ng-repeat="r in accessRules">
<td>{{r.weekdays.value}}</td>
<td>{{r.start_time.value}}</td>
<td>{{r.stop_time.value}}</td>
<td><ul><li ng-repeat="mac in r.src_mac.value">{{mac}}</li></ul></td>
<td style="width: 1%"><button class="btn btn-default" ng-click="onDeleteAccessRule(r)"><i class="fa fa-trash-o"></i></button></td>
</tr>
<tr>
<td colspan="3"></td>
<td style="width: 1%"><button class="btn btn-default" ng-click="onAddAccessRule()"><i class="fa fa-plus"></i></button></td>
</tr>
</tbody>
</table>
</juci-config-section>
<juci-config-apply></juci-config-apply>
<modal title="Add / Edit MAC Filter Scheduling" ng-show="rule" on-accept="onAcceptEdit()" on-dismiss="onCancelEdit()" dismiss-label="Cancel" accept-label="Save">
<uci-firewall-nat-rule-edit ng-model="rule"></uci-firewall-nat-rule-edit>
<div class="alert alert-danger" ng-show="errors && errors.length"><ul><li ng-repeat="e in errors track by $index">{{e|translate}}</li></ul></div>
</modal>
</div>
</juci-layout-with-sidebar>

View File

@ -0,0 +1,111 @@
JUCI.app
.controller("InternetParentalControlPage", function($scope, $uci, $rpc){
function reload(){
$uci.sync("firewall").done(function(){
$scope.rules = $uci.firewall["@rule"];
$scope.$apply();
});
} reload();
$scope.urlList = [];
$scope.macList = [];
$scope.connectedHosts = [];
$rpc.router.clients().done(function(clients){
$scope.connectedHosts = Object.keys(clients).filter(function(x){
// use only connected hosts
return clients[x].connected;
}).map(function(k){
return { label: clients[k].hostname+" ("+clients[k].ipaddr+")", value: clients[k].macaddr };
});
$scope.$apply();
});
$uci.sync("firewall").done(function(){
$scope.urlblock = $uci.firewall.urlblock;
$scope.urlblock.url.value.map(function(x){ $scope.urlList.push({url: x}); });
$scope.urlblock.src_mac.value.map(function(x){ $scope.macList.push({mac: x}); });
$scope.accessRules = $uci.firewall["@rule"].filter(function(rule){
return rule.type.value == "internet_access";
});
$scope.onAddURL = function(){
$scope.urlList.push({url: ""});
}
$scope.onDeleteURL = function(url){
$scope.urlList = $scope.urlList.filter(function(x){
return x.url != url;
});
}
$scope.onAddMAC = function(){
$scope.macList.push({mac: ""});
}
$scope.onDeleteMAC = function(mac){
$scope.macList = $scope.macList.filter(function(x){
return x.mac != mac;
});
}
$scope.$watch("urlList", function(){
$scope.urlblock.url.value = $scope.urlList.map(function(k){
return k.url;
});
}, true);
$scope.$watch("macList", function(){
$scope.urlblock.src_mac.value = $scope.macList.map(function(k){
return k.mac;
});
}, true);
$scope.onSelectExistingMAC = function(value){
$scope.macList.push({mac: value});
$scope.selectedMAC = "";
}
$scope.$apply();
});
/*
$scope.onAddRule = function(net){
$uci.firewall.create({
".type": "rule",
"src": "wan",
"dest": "lan",
"target": "DNAT"
}).done(function(section){
$scope.rule = section;
$scope.rule[".new"] = true;
//$scope.rule[".edit"] = true;
$scope.$apply();
});
};
$scope.onEditRule = function(rule){
$scope.rule = rule;
//$scope.rule[".edit"] = true;
console.log($scope.rule[".name"]);
console.log(Object.keys($scope.redirects).map(function(k) { return $scope.redirects[k][".name"]; }));
};
$scope.onDeleteRule = function(rule){
rule.$delete().done(function(){
});
};
$scope.onAcceptEdit = function(){
$scope.errors = $scope.rule.$getErrors();
if($scope.errors.length) return;
$scope.rule = null;
};
$scope.onCancelEdit = function(){
if($scope.rule[".new"]){
$scope.rule.$delete().done(function(){
$scope.rule = null;
$scope.$apply();
});
} else {
$scope.rule = null;
}
}*/
});

View File

@ -0,0 +1,42 @@
<div class="form-horizontal" >
<div class="form-group">
<label for="device" class="control-label col-xs-4" translate>Device</label>
<div class="col-xs-8" id="device">
<juci-select ng-model="ngModel.dest_ip.value" ng-items="deviceChoices" placeholder="Select connected device"></juci-select>
</div>
</div>
<div class="form-group">
<label for="ipAddress" class="control-label col-xs-4" translate>Local IP Address</label>
<div class="col-xs-8">
<input id="ipAddress" type="text" class="form-control" ng-model="ngModel.dest_ip.value" />
</div>
</div>
<div class="form-group">
<label for="protocol" class="control-label col-xs-4" translate>Protocol</label>
<div class="col-xs-8" id="protocol">
<juci-select ng-model="ngModel.proto.value" ng-items="protocolChoices" placeholder="Select protocol"></juci-select>
</div>
</div>
<div class="form-group">
<label for="range" class="control-label col-xs-4">Type</label>
<div class="col-xs-8" id="range">
<div class="btn-group">
<button ng-repeat="item in [[false,'Port'], [true, 'Port range']]" class="btn btn-default" ng-model="portIsRange" btn-radio="item[0]" ng-click="onPortRangeClick(item[0])">{{item[1]}}</button>
</div>
</div>
</div>
<div class="form-group" >
<label for="range" class="control-label col-xs-4" ng-show="!portIsRange" translate>Public port</label>
<label for="range" class="control-label col-xs-4" ng-show="portIsRange" translate>Public port range</label>
<div class="col-xs-8" ng-class="{'field-error': ngModel.src_dport.error}" >
<juci-input-port ng-model="ngModel.src_dport.value" port-range="portIsRange"/>
</div>
</div>
<div class="form-group" >
<label for="range" class="control-label col-xs-4" ng-show="!portIsRange" translate>Private port</label>
<label for="range" class="control-label col-xs-4" ng-show="portIsRange" translate>Private port range</label>
<div class="col-xs-8" ng-class="{'field-error': ngModel.dest_port.error}" >
<juci-input-port ng-model="ngModel.dest_port.value" port-range="portIsRange"/>
</div>
</div>
</div>

View File

@ -0,0 +1,43 @@
JUCI.app
.directive("uciFirewallRuleEdit", function($compile, $parse){
return {
templateUrl: plugin_root+"/widgets/uci.firewall.rule.edit.html",
scope: {
ngModel: "=ngModel"
},
controller: "uciFirewallRuleEdit",
replace: true
};
}).controller("uciFirewallRuleEdit", function($scope, $uci, $rpc, $log){
$scope.$watch("ngModel", function(value){
if(!value) return;
var ngModel = value;
if(ngModel && ngModel.src_dport && ngModel.dest_port && ngModel.src_dport.value && ngModel.dest_port.value){
$scope.portIsRange = (ngModel.src_dport.value.indexOf("-") != -1) || (ngModel.dest_port.value.indexOf("-") != -1);
}
});
$scope.protocolChoices = [
{ label: "UDP", value: "udp"},
{ label: "TCP", value: "tcp"},
{ label: "TCP + UDP", value: "tcpudp" }
];
$scope.deviceChoices = [];
$rpc.router.clients().done(function(clients){
var choices = [];
Object.keys(clients).map(function(x) {
var c = clients[x];
if(c.connected){
choices.push({
label: (c.hostname && c.hostname.length)?c.hostname:c.ipaddr,
value: c.ipaddr
});
}
});
$scope.deviceChoices = choices;
$scope.$apply();
});
$scope.onPortRangeClick = function(value){
$scope.portIsRange = value;
}
});

View File

@ -47,8 +47,8 @@
<juci-config-line title="{{'Pick configuration backup to upload'|translate}}">
<input type="file" class="btn btn-default btn-file" name="filedata" />
</juci-config-line>
<juci-config-line title="{{'Backup file password (if encrypted)'|translate}}">
<input type="password" class="form-control" name="password" ng-model="restore.password" />
<juci-config-line title="{{'Backup file password'|translate}}">
<input type="password" class="form-control" name="password" ng-model="restore.password" placeholder="{{'Password (if encrypted)'|translate}}"/>
</juci-config-line>
<!--<juci-config-line title="{{'Start upgrade'|translate}}">
<input type="submit" class="btn btn-lg btn-default" value="{{'Upgrade'|translate}}"/>

View File

@ -49,6 +49,9 @@
</juci-config-line>
</juci-config-lines>
</juci-config-section>
<modal title="Do you want to keep your configuration?" ng-show="showConfirm" on-accept="onConfirmKeep()" on-dismiss="onConfirmWipe()" accept-label="Yes" dismiss-label="No">
<p translate>If you answer yes then your confiruation will be saved before the upgrade and restored after the upgrade has completed. If you choose 'no' then all your current confiration will be reset to defaults.</p>
</modal>
<modal title="Firmware Upgrade" ng-show="showUpgradeStatus">
<center>
<p style="text-align: center;" ng-show="!error">{{message}}</p>

View File

@ -117,7 +117,25 @@ JUCI.app
$scope.config = $config;
function upgradeStart(path){
function confirmKeep(){
var deferred = $.Deferred();
$scope.onConfirmKeep = function(){
$scope.showConfirm = 0;
deferred.resolve(true);
}
$scope.onConfirmWipe = function(){
$scope.showConfirm = 0;
deferred.resolve(false);
}
$scope.showConfirm = 1;
setTimeout(function(){ $scope.$apply(); }, 0);
return deferred.promise();
}
function upgradeStart(path, keep_configs){
$scope.showUpgradeStatus = 1;
$scope.error = null;
$scope.message = gettext("Verifying firmware image")+"...";
@ -125,8 +143,8 @@ JUCI.app
setTimeout(function(){ $scope.$apply(); }, 0);
console.log("Trying to upgrade from "+path);
$rpc.juci.system.upgrade_start({"path": path}).done(function(result){
$rpc.juci.system.upgrade_start({"path": path, "keep": ((keep_configs)?1:0)}).done(function(result){
// this will actually never succeed because server will be killed
console.error("upgrade_start returned success, which means that it actually probably failed but did not return an error");
$scope.error = (result.stdout||"") + (result.stderr||"");
@ -172,7 +190,9 @@ JUCI.app
});
}
$scope.onUpgradeOnline = function(){
upgradeStart($scope.onlineUpgrade);
confirmKeep().done(function(keep){
upgradeStart($scope.onlineUpgrade, keep);
});
}
$scope.onCheckUSB = function(){
@ -191,7 +211,9 @@ JUCI.app
});
}
$scope.onUpgradeUSB = function(){
upgradeStart($scope.usbUpgrade);
confirmKeep().done(function(keep){
upgradeStart($scope.usbUpgrade, keep);
});
}
$scope.onCheckUSB();
@ -200,8 +222,8 @@ JUCI.app
$scope.onUploadComplete = function(result){
console.log("Upload completed: "+JSON.stringify(result));
}
$scope.onUploadUpgrade = function(){
$scope.showUpgradeStatus = 1;
$scope.onUploadUpgrade = function(keep_configs){
//$scope.showUpgradeStatus = 1;
$scope.message = "Uploading...";
$scope.progress = 'uploading';
$("#postiframe").bind("load", function(){
@ -216,8 +238,14 @@ JUCI.app
//return;
}
upgradeStart($scope.uploadFilename);
$scope.showUpgradeStatus = 0;
$scope.$apply();
confirmKeep().done(function(keep){
$scope.showUpgradeStatus = 1;
//$scope.$apply();
upgradeStart($scope.uploadFilename, keep);
});
$(this).unbind("load");
});
$("form[name='uploadForm']").submit();

View File

@ -9,7 +9,7 @@
<juci-config-line title="Outgoing Number Blocking">
<switch ng-model="filter.block_outgoing.value"></switch>
</juci-config-line>
<juci-config-lines>
</juci-config-lines>
<juci-config-lines ng-show="filter.block_outgoing.value">
<hr/>
<juci-config-line title="Do not allow connections to these numbers">
@ -35,7 +35,7 @@
<juci-config-line title="Block connections to all special rate numbers">
<switch ng-model="filter.block_special_rate.value"></switch>
</juci-config-line>
<juci-config-lines>
</juci-config-lines>
</juci-config-section>
<juci-config-section>
<h2 translate>Incoming Calls</h2>
@ -43,7 +43,7 @@
<juci-config-line title="Incoming Number Blocking">
<switch ng-model="filter.block_incoming.value"></switch>
</juci-config-line>
<juci-config-lines>
</juci-config-lines>
<juci-config-lines ng-show="filter.block_incoming.value">
<juci-config-line title="Do not allow connections from these numbers">
<div class="row" ng-repeat="rule in incomingRules">
@ -58,7 +58,7 @@
</div>
</div>
</juci-config-line>
<juci-config-lines>
</juci-config-lines>
</juci-config-section>
<juci-config-apply></juci-config-apply>
</div>

View File

@ -26,8 +26,7 @@ div.logo {
}
.overview-banner {
background: url("../img/family_web.jpg") repeat scroll center center !important;
width: 100%;
background: url("../img/overview_header.jpg") repeat scroll top center !important;
height: 400px;
margin-bottom: 30px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
<h3 class="modal-title">{{title|translate}}</h3>
</div>
<div class="modal-body" ng-transclude></div>
<div class="modal-footer">
<div class="modal-footer" ng-hide="noFooter">
<div class="btn-toolbar">
<button class="btn btn-primary btn-lg" ng-show="acceptLabel" ng-click="onAccept()">{{acceptLabel}}</button>
<button class="btn btn-default btn-lg" ng-show="dismissLabel" ng-click="onDismiss()">{{dismissLabel}}</button>

View File

@ -14,6 +14,7 @@ JUCI.app
ngShow: "=",
onAccept: "&",
onDismiss: "&",
noFooter: "@",
title: "@",
hideCloseBtn : "@"
},