mirror of
https://github.com/Ysurac/openmptcprouter-feeds.git
synced 2025-03-09 15:40:03 +00:00
Update to latest LuCi changes
This commit is contained in:
parent
976a467d5f
commit
f139a9c784
75 changed files with 22413 additions and 14077 deletions
File diff suppressed because it is too large
Load diff
560
luci-base/htdocs/luci-static/resources/firewall.js
Normal file
560
luci-base/htdocs/luci-static/resources/firewall.js
Normal file
|
@ -0,0 +1,560 @@
|
|||
'use strict';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require tools.prng as random';
|
||||
|
||||
|
||||
function initFirewallState() {
|
||||
return uci.load('firewall');
|
||||
}
|
||||
|
||||
function parseEnum(s, values) {
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
s = String(s).toUpperCase();
|
||||
|
||||
if (s == '')
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < values.length; i++)
|
||||
if (values[i].toUpperCase().indexOf(s) == 0)
|
||||
return values[i];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parsePolicy(s, defaultValue) {
|
||||
return parseEnum(s, ['DROP', 'REJECT', 'ACCEPT']) || (arguments.length < 2 ? null : defaultValue);
|
||||
}
|
||||
|
||||
|
||||
var Firewall, AbstractFirewallItem, Defaults, Zone, Forwarding, Redirect, Rule;
|
||||
|
||||
function lookupZone(name) {
|
||||
var z = uci.get('firewall', name);
|
||||
|
||||
if (z != null && z['.type'] == 'zone')
|
||||
return new Zone(z['.name']);
|
||||
|
||||
var sections = uci.sections('firewall', 'zone');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].name != name)
|
||||
continue;
|
||||
|
||||
return new Zone(sections[i]['.name']);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getColorForName(forName) {
|
||||
if (forName == null)
|
||||
return '#eeeeee';
|
||||
else if (forName == 'lan')
|
||||
return '#90f090';
|
||||
else if (forName == 'wan')
|
||||
return '#f09090';
|
||||
|
||||
random.seed(parseInt(sfh(forName), 16));
|
||||
|
||||
var r = random.get(128),
|
||||
g = random.get(128),
|
||||
min = 0,
|
||||
max = 128;
|
||||
|
||||
if ((r + g) < 128)
|
||||
min = 128 - r - g;
|
||||
else
|
||||
max = 255 - r - g;
|
||||
|
||||
var b = min + Math.floor(random.get() * (max - min));
|
||||
|
||||
return '#%02x%02x%02x'.format(0xff - r, 0xff - g, 0xff - b);
|
||||
}
|
||||
|
||||
|
||||
Firewall = L.Class.extend({
|
||||
getDefaults: function() {
|
||||
return initFirewallState().then(function() {
|
||||
return new Defaults();
|
||||
});
|
||||
},
|
||||
|
||||
newZone: function() {
|
||||
return initFirewallState().then(L.bind(function() {
|
||||
var name = 'newzone',
|
||||
count = 1;
|
||||
|
||||
while (this.getZone(name) != null)
|
||||
name = 'newzone%d'.format(++count);
|
||||
|
||||
return this.addZone(name);
|
||||
}, this));
|
||||
},
|
||||
|
||||
addZone: function(name) {
|
||||
return initFirewallState().then(L.bind(function() {
|
||||
if (name == null || !/^[a-zA-Z0-9_]+$/.test(name))
|
||||
return null;
|
||||
|
||||
if (this.getZone(name) != null)
|
||||
return null;
|
||||
|
||||
var d = new Defaults(),
|
||||
z = uci.add('firewall', 'zone');
|
||||
|
||||
uci.set('firewall', z, 'name', name);
|
||||
uci.set('firewall', z, 'network', ' ');
|
||||
uci.set('firewall', z, 'input', d.getInput() || 'DROP');
|
||||
uci.set('firewall', z, 'output', d.getOutput() || 'DROP');
|
||||
uci.set('firewall', z, 'forward', d.getForward() || 'DROP');
|
||||
|
||||
return new Zone(z);
|
||||
}, this));
|
||||
},
|
||||
|
||||
getZone: function(name) {
|
||||
return initFirewallState().then(function() {
|
||||
return lookupZone(name);
|
||||
});
|
||||
},
|
||||
|
||||
getZones: function() {
|
||||
return initFirewallState().then(function() {
|
||||
var sections = uci.sections('firewall', 'zone'),
|
||||
zones = [];
|
||||
|
||||
for (var i = 0; i < sections.length; i++)
|
||||
zones.push(new Zone(sections[i]['.name']));
|
||||
|
||||
zones.sort(function(a, b) { return a.getName() > b.getName() });
|
||||
|
||||
return zones;
|
||||
});
|
||||
},
|
||||
|
||||
getZoneByNetwork: function(network) {
|
||||
return initFirewallState().then(function() {
|
||||
var sections = uci.sections('firewall', 'zone');
|
||||
|
||||
for (var i = 0; i < sections.length; i++)
|
||||
if (L.toArray(sections[i].network || sections[i].name).indexOf(network) != -1)
|
||||
return new Zone(sections[i]['.name']);
|
||||
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
deleteZone: function(name) {
|
||||
return initFirewallState().then(function() {
|
||||
var section = uci.get('firewall', name),
|
||||
found = false;
|
||||
|
||||
if (section != null && section['.type'] == 'zone') {
|
||||
found = true;
|
||||
name = zone.name;
|
||||
uci.remove('firewall', zone['.name']);
|
||||
}
|
||||
else if (name != null) {
|
||||
var sections = uci.sections('firewall', 'zone');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].name != name)
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
uci.remove('firewall', sections[i]['.name']);
|
||||
}
|
||||
}
|
||||
|
||||
if (found == true) {
|
||||
sections = uci.sections('firewall');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i]['.type'] != 'rule' &&
|
||||
sections[i]['.type'] != 'redirect' &&
|
||||
sections[i]['.type'] != 'forwarding')
|
||||
continue;
|
||||
|
||||
if (sections[i].src == name || sections[i].dest == name)
|
||||
uci.remove('firewall', sections[i]['.name']);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
});
|
||||
},
|
||||
|
||||
renameZone: function(oldName, newName) {
|
||||
return initFirewallState().then(L.bind(function() {
|
||||
if (oldName == null || newName == null || !/^[a-zA-Z0-9_]+$/.test(newName))
|
||||
return false;
|
||||
|
||||
if (lookupZone(newName) != null)
|
||||
return false;
|
||||
|
||||
var sections = uci.sections('firewall', 'zone'),
|
||||
found = false;
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].name != oldName)
|
||||
continue;
|
||||
|
||||
if (L.toArray(sections[i].network).length == 0)
|
||||
uci.set('firewall', sections[i]['.name'], 'network', oldName);
|
||||
|
||||
uci.set('firewall', sections[i]['.name'], 'name', newName);
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found == true) {
|
||||
sections = uci.sections('firewall');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i]['.type'] != 'rule' &&
|
||||
sections[i]['.type'] != 'redirect' &&
|
||||
sections[i]['.type'] != 'forwarding')
|
||||
continue;
|
||||
|
||||
if (sections[i].src == oldName)
|
||||
uci.set('firewall', sections[i]['.name'], 'src', newName);
|
||||
|
||||
if (sections[i].dest == oldName)
|
||||
uci.set('firewall', sections[i]['.name'], 'dest', newName);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}, this));
|
||||
},
|
||||
|
||||
deleteNetwork: function(network) {
|
||||
return this.getZones().then(L.bind(function(zones) {
|
||||
var rv = false;
|
||||
|
||||
for (var i = 0; i < zones.length; i++)
|
||||
if (zones[i].deleteNetwork(network))
|
||||
rv = true;
|
||||
|
||||
return rv;
|
||||
}, this));
|
||||
},
|
||||
|
||||
getColorForName: getColorForName
|
||||
});
|
||||
|
||||
|
||||
AbstractFirewallItem = L.Class.extend({
|
||||
get: function(option) {
|
||||
return uci.get('firewall', this.sid, option);
|
||||
},
|
||||
|
||||
set: function(option, value) {
|
||||
return uci.set('firewall', this.sid, option, value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Defaults = AbstractFirewallItem.extend({
|
||||
__init__: function() {
|
||||
var sections = uci.sections('firewall', 'defaults');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
this.sid = sections[i]['.name'];
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.sid == null)
|
||||
this.sid = uci.add('firewall', 'defaults');
|
||||
},
|
||||
|
||||
isSynFlood: function() {
|
||||
return (this.get('syn_flood') == '1');
|
||||
},
|
||||
|
||||
isDropInvalid: function() {
|
||||
return (this.get('drop_invalid') == '1');
|
||||
},
|
||||
|
||||
getInput: function() {
|
||||
return parsePolicy(this.get('input'), 'DROP');
|
||||
},
|
||||
|
||||
getOutput: function() {
|
||||
return parsePolicy(this.get('output'), 'DROP');
|
||||
},
|
||||
|
||||
getForward: function() {
|
||||
return parsePolicy(this.get('forward'), 'DROP');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Zone = AbstractFirewallItem.extend({
|
||||
__init__: function(name) {
|
||||
var section = uci.get('firewall', name);
|
||||
|
||||
if (section != null && section['.type'] == 'zone') {
|
||||
this.sid = name;
|
||||
this.data = section;
|
||||
}
|
||||
else if (name != null) {
|
||||
var sections = uci.get('firewall', 'zone');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].name != name)
|
||||
continue;
|
||||
|
||||
this.sid = sections[i]['.name'];
|
||||
this.data = sections[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
isMasquerade: function() {
|
||||
return (this.get('masq') == '1');
|
||||
},
|
||||
|
||||
getName: function() {
|
||||
return this.get('name');
|
||||
},
|
||||
|
||||
getNetwork: function() {
|
||||
return this.get('network');
|
||||
},
|
||||
|
||||
getInput: function() {
|
||||
return parsePolicy(this.get('input'), (new Defaults()).getInput());
|
||||
},
|
||||
|
||||
getOutput: function() {
|
||||
return parsePolicy(this.get('output'), (new Defaults()).getOutput());
|
||||
},
|
||||
|
||||
getForward: function() {
|
||||
return parsePolicy(this.get('forward'), (new Defaults()).getForward());
|
||||
},
|
||||
|
||||
addNetwork: function(network) {
|
||||
var section = uci.get('network', network);
|
||||
|
||||
if (section == null || section['.type'] != 'interface')
|
||||
return false;
|
||||
|
||||
var newNetworks = this.getNetworks();
|
||||
|
||||
if (newNetworks.filter(function(net) { return net == network }).length)
|
||||
return false;
|
||||
|
||||
newNetworks.push(network);
|
||||
this.set('network', newNetworks.join(' '));
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
deleteNetwork: function(network) {
|
||||
var oldNetworks = this.getNetworks(),
|
||||
newNetworks = oldNetworks.filter(function(net) { return net != network });
|
||||
|
||||
if (newNetworks.length > 0)
|
||||
this.set('network', newNetworks.join(' '));
|
||||
else
|
||||
this.set('network', ' ');
|
||||
|
||||
return (newNetworks.length < oldNetworks.length);
|
||||
},
|
||||
|
||||
getNetworks: function() {
|
||||
return L.toArray(this.get('network') || this.get('name'));
|
||||
},
|
||||
|
||||
clearNetworks: function() {
|
||||
this.set('network', ' ');
|
||||
},
|
||||
|
||||
getForwardingsBy: function(what) {
|
||||
var sections = uci.sections('firewall', 'forwarding'),
|
||||
forwards = [];
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].src == null || sections[i].dest == null)
|
||||
continue;
|
||||
|
||||
if (sections[i][what] != this.getName())
|
||||
continue;
|
||||
|
||||
forwards.push(new Forwarding(sections[i]['.name']));
|
||||
}
|
||||
|
||||
return forwards;
|
||||
},
|
||||
|
||||
addForwardingTo: function(dest) {
|
||||
var forwards = this.getForwardingsBy('src'),
|
||||
zone = lookupZone(dest);
|
||||
|
||||
if (zone == null || zone.getName() == this.getName())
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < forwards.length; i++)
|
||||
if (forwards[i].getDestination() == zone.getName())
|
||||
return null;
|
||||
|
||||
var sid = uci.add('firewall', 'forwarding');
|
||||
|
||||
uci.set('firewall', sid, 'src', this.getName());
|
||||
uci.set('firewall', sid, 'dest', zone.getName());
|
||||
|
||||
return new Forwarding(sid);
|
||||
},
|
||||
|
||||
addForwardingFrom: function(src) {
|
||||
var forwards = this.getForwardingsBy('dest'),
|
||||
zone = lookupZone(src);
|
||||
|
||||
if (zone == null || zone.getName() == this.getName())
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < forwards.length; i++)
|
||||
if (forwards[i].getSource() == zone.getName())
|
||||
return null;
|
||||
|
||||
var sid = uci.add('firewall', 'forwarding');
|
||||
|
||||
uci.set('firewall', sid, 'src', zone.getName());
|
||||
uci.set('firewall', sid, 'dest', this.getName());
|
||||
|
||||
return new Forwarding(sid);
|
||||
},
|
||||
|
||||
deleteForwardingsBy: function(what) {
|
||||
var sections = uci.sections('firewall', 'forwarding'),
|
||||
found = false;
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].src == null || sections[i].dest == null)
|
||||
continue;
|
||||
|
||||
if (sections[i][what] != this.getName())
|
||||
continue;
|
||||
|
||||
uci.remove('firewall', sections[i]['.name']);
|
||||
found = true;
|
||||
}
|
||||
|
||||
return found;
|
||||
},
|
||||
|
||||
deleteForwarding: function(forwarding) {
|
||||
if (!(forwarding instanceof Forwarding))
|
||||
return false;
|
||||
|
||||
var section = uci.get('firewall', forwarding.sid);
|
||||
|
||||
if (!section || section['.type'] != 'forwarding')
|
||||
return false;
|
||||
|
||||
uci.remove('firewall', section['.name']);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
addRedirect: function(options) {
|
||||
var sid = uci.add('firewall', 'redirect');
|
||||
|
||||
if (options != null && typeof(options) == 'object')
|
||||
for (var key in options)
|
||||
if (options.hasOwnProperty(key))
|
||||
uci.set('firewall', sid, key, options[key]);
|
||||
|
||||
uci.set('firewall', sid, 'src', this.getName());
|
||||
|
||||
return new Redirect(sid);
|
||||
},
|
||||
|
||||
addRule: function(options) {
|
||||
var sid = uci.add('firewall', 'rule');
|
||||
|
||||
if (options != null && typeof(options) == 'object')
|
||||
for (var key in options)
|
||||
if (options.hasOwnProperty(key))
|
||||
uci.set('firewall', sid, key, options[key]);
|
||||
|
||||
uci.set('firewall', sid, 'src', this.getName());
|
||||
|
||||
return new Redirect(sid);
|
||||
},
|
||||
|
||||
getColor: function(forName) {
|
||||
var name = (arguments.length > 0 ? forName : this.getName());
|
||||
|
||||
return getColorForName(name);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Forwarding = AbstractFirewallItem.extend({
|
||||
__init__: function(sid) {
|
||||
this.sid = sid;
|
||||
},
|
||||
|
||||
getSource: function() {
|
||||
return this.get('src');
|
||||
},
|
||||
|
||||
getDestination: function() {
|
||||
return this.get('dest');
|
||||
},
|
||||
|
||||
getSourceZone: function() {
|
||||
return lookupZone(this.getSource());
|
||||
},
|
||||
|
||||
getDestinationZone: function() {
|
||||
return lookupZone(this.getDestination());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Rule = AbstractFirewallItem.extend({
|
||||
getSource: function() {
|
||||
return this.get('src');
|
||||
},
|
||||
|
||||
getDestination: function() {
|
||||
return this.get('dest');
|
||||
},
|
||||
|
||||
getSourceZone: function() {
|
||||
return lookupZone(this.getSource());
|
||||
},
|
||||
|
||||
getDestinationZone: function() {
|
||||
return lookupZone(this.getDestination());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Redirect = AbstractFirewallItem.extend({
|
||||
getSource: function() {
|
||||
return this.get('src');
|
||||
},
|
||||
|
||||
getDestination: function() {
|
||||
return this.get('dest');
|
||||
},
|
||||
|
||||
getSourceZone: function() {
|
||||
return lookupZone(this.getSource());
|
||||
},
|
||||
|
||||
getDestinationZone: function() {
|
||||
return lookupZone(this.getDestination());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return Firewall;
|
1669
luci-base/htdocs/luci-static/resources/form.js
Normal file
1669
luci-base/htdocs/luci-static/resources/form.js
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
2085
luci-base/htdocs/luci-static/resources/network.js
Normal file
2085
luci-base/htdocs/luci-static/resources/network.js
Normal file
File diff suppressed because it is too large
Load diff
5
luci-base/htdocs/luci-static/resources/promis.min.js
vendored
Normal file
5
luci-base/htdocs/luci-static/resources/promis.min.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/* Licensed under the BSD license. Copyright 2014 - Bram Stein. All rights reserved.
|
||||
* https://github.com/bramstein/promis */
|
||||
(function(){'use strict';var f,g=[];function l(a){g.push(a);1==g.length&&f()}function m(){for(;g.length;)g[0](),g.shift()}f=function(){setTimeout(m)};function n(a){this.a=p;this.b=void 0;this.f=[];var b=this;try{a(function(a){q(b,a)},function(a){r(b,a)})}catch(c){r(b,c)}}var p=2;function t(a){return new n(function(b,c){c(a)})}function u(a){return new n(function(b){b(a)})}function q(a,b){if(a.a==p){if(b==a)throw new TypeError;var c=!1;try{var d=b&&b.then;if(null!=b&&"object"==typeof b&&"function"==typeof d){d.call(b,function(b){c||q(a,b);c=!0},function(b){c||r(a,b);c=!0});return}}catch(e){c||r(a,e);return}a.a=0;a.b=b;v(a)}}
|
||||
function r(a,b){if(a.a==p){if(b==a)throw new TypeError;a.a=1;a.b=b;v(a)}}function v(a){l(function(){if(a.a!=p)for(;a.f.length;){var b=a.f.shift(),c=b[0],d=b[1],e=b[2],b=b[3];try{0==a.a?"function"==typeof c?e(c.call(void 0,a.b)):e(a.b):1==a.a&&("function"==typeof d?e(d.call(void 0,a.b)):b(a.b))}catch(h){b(h)}}})}n.prototype.g=function(a){return this.c(void 0,a)};n.prototype.c=function(a,b){var c=this;return new n(function(d,e){c.f.push([a,b,d,e]);v(c)})};
|
||||
function w(a){return new n(function(b,c){function d(c){return function(d){h[c]=d;e+=1;e==a.length&&b(h)}}var e=0,h=[];0==a.length&&b(h);for(var k=0;k<a.length;k+=1)u(a[k]).c(d(k),c)})}function x(a){return new n(function(b,c){for(var d=0;d<a.length;d+=1)u(a[d]).c(b,c)})};window.Promise||(window.Promise=n,window.Promise.resolve=u,window.Promise.reject=t,window.Promise.race=x,window.Promise.all=w,window.Promise.prototype.then=n.prototype.c,window.Promise.prototype["catch"]=n.prototype.g,window.Promise.prototype.finally=function(a){return this.c(a,a)});}());
|
160
luci-base/htdocs/luci-static/resources/rpc.js
Normal file
160
luci-base/htdocs/luci-static/resources/rpc.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
'use strict';
|
||||
|
||||
var rpcRequestID = 1,
|
||||
rpcSessionID = L.env.sessionid || '00000000000000000000000000000000',
|
||||
rpcBaseURL = L.url('admin/ubus');
|
||||
|
||||
return L.Class.extend({
|
||||
call: function(req, cb) {
|
||||
var q = '';
|
||||
|
||||
if (Array.isArray(req)) {
|
||||
if (req.length == 0)
|
||||
return Promise.resolve([]);
|
||||
|
||||
for (var i = 0; i < req.length; i++)
|
||||
q += '%s%s.%s'.format(
|
||||
q ? ';' : '/',
|
||||
req[i].params[1],
|
||||
req[i].params[2]
|
||||
);
|
||||
}
|
||||
else {
|
||||
q += '/%s.%s'.format(req.params[1], req.params[2]);
|
||||
}
|
||||
|
||||
return L.Request.post(rpcBaseURL + q, req, {
|
||||
timeout: (L.env.rpctimeout || 5) * 1000,
|
||||
credentials: true
|
||||
}).then(cb);
|
||||
},
|
||||
|
||||
handleListReply: function(req, msg) {
|
||||
var list = msg.result;
|
||||
|
||||
/* verify message frame */
|
||||
if (typeof(msg) != 'object' || msg.jsonrpc != '2.0' || !msg.id || !Array.isArray(list))
|
||||
list = [ ];
|
||||
|
||||
req.resolve(list);
|
||||
},
|
||||
|
||||
handleCallReply: function(req, res) {
|
||||
var type = Object.prototype.toString,
|
||||
msg = null;
|
||||
|
||||
if (!res.ok)
|
||||
L.error('RPCError', 'RPC call failed with HTTP error %d: %s',
|
||||
res.status, res.statusText || '?');
|
||||
|
||||
msg = res.json();
|
||||
|
||||
/* fetch response attribute and verify returned type */
|
||||
var ret = undefined;
|
||||
|
||||
/* verify message frame */
|
||||
if (typeof(msg) == 'object' && msg.jsonrpc == '2.0') {
|
||||
if (typeof(msg.error) == 'object' && msg.error.code && msg.error.message)
|
||||
req.reject(new Error('RPC call failed with error %d: %s'
|
||||
.format(msg.error.code, msg.error.message || '?')));
|
||||
else if (Array.isArray(msg.result) && msg.result[0] == 0)
|
||||
ret = (msg.result.length > 1) ? msg.result[1] : msg.result[0];
|
||||
}
|
||||
else {
|
||||
req.reject(new Error('Invalid message frame received'));
|
||||
}
|
||||
|
||||
if (req.expect) {
|
||||
for (var key in req.expect) {
|
||||
if (ret != null && key != '')
|
||||
ret = ret[key];
|
||||
|
||||
if (ret == null || type.call(ret) != type.call(req.expect[key]))
|
||||
ret = req.expect[key];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* apply filter */
|
||||
if (typeof(req.filter) == 'function') {
|
||||
req.priv[0] = ret;
|
||||
req.priv[1] = req.params;
|
||||
ret = req.filter.apply(this, req.priv);
|
||||
}
|
||||
|
||||
req.resolve(ret);
|
||||
},
|
||||
|
||||
list: function() {
|
||||
var msg = {
|
||||
jsonrpc: '2.0',
|
||||
id: rpcRequestID++,
|
||||
method: 'list',
|
||||
params: arguments.length ? this.varargs(arguments) : undefined
|
||||
};
|
||||
|
||||
return this.call(msg, this.handleListReply);
|
||||
},
|
||||
|
||||
declare: function(options) {
|
||||
return Function.prototype.bind.call(function(rpc, options) {
|
||||
var args = this.varargs(arguments, 2);
|
||||
return new Promise(function(resolveFn, rejectFn) {
|
||||
/* build parameter object */
|
||||
var p_off = 0;
|
||||
var params = { };
|
||||
if (Array.isArray(options.params))
|
||||
for (p_off = 0; p_off < options.params.length; p_off++)
|
||||
params[options.params[p_off]] = args[p_off];
|
||||
|
||||
/* all remaining arguments are private args */
|
||||
var priv = [ undefined, undefined ];
|
||||
for (; p_off < args.length; p_off++)
|
||||
priv.push(args[p_off]);
|
||||
|
||||
/* store request info */
|
||||
var req = {
|
||||
expect: options.expect,
|
||||
filter: options.filter,
|
||||
resolve: resolveFn,
|
||||
reject: rejectFn,
|
||||
params: params,
|
||||
priv: priv
|
||||
};
|
||||
|
||||
/* build message object */
|
||||
var msg = {
|
||||
jsonrpc: '2.0',
|
||||
id: rpcRequestID++,
|
||||
method: 'call',
|
||||
params: [
|
||||
rpcSessionID,
|
||||
options.object,
|
||||
options.method,
|
||||
params
|
||||
]
|
||||
};
|
||||
|
||||
/* call rpc */
|
||||
rpc.call(msg, rpc.handleCallReply.bind(rpc, req));
|
||||
});
|
||||
}, this, this, options);
|
||||
},
|
||||
|
||||
getSessionID: function() {
|
||||
return rpcSessionID;
|
||||
},
|
||||
|
||||
setSessionID: function(sid) {
|
||||
rpcSessionID = sid;
|
||||
},
|
||||
|
||||
getBaseURL: function() {
|
||||
return rpcBaseURL;
|
||||
},
|
||||
|
||||
setBaseURL: function(url) {
|
||||
rpcBaseURL = url;
|
||||
}
|
||||
});
|
93
luci-base/htdocs/luci-static/resources/tools/prng.js
Normal file
93
luci-base/htdocs/luci-static/resources/tools/prng.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
'use strict';
|
||||
|
||||
var s = [0x0000, 0x0000, 0x0000, 0x0000];
|
||||
|
||||
function mul(a, b) {
|
||||
var r = [0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000];
|
||||
|
||||
for (var j = 0; j < 4; j++) {
|
||||
var k = 0;
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var t = a[i] * b[j] + r[i+j] + k;
|
||||
r[i+j] = t & 0xffff;
|
||||
k = t >>> 16;
|
||||
}
|
||||
r[j+4] = k;
|
||||
}
|
||||
|
||||
r.length = 4;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
function add(a, n) {
|
||||
var r = [0x0000, 0x0000, 0x0000, 0x0000],
|
||||
k = n;
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var t = a[i] + k;
|
||||
r[i] = t & 0xffff;
|
||||
k = t >>> 16;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
function shr(a, n) {
|
||||
var r = [a[0], a[1], a[2], a[3], 0x0000],
|
||||
i = 4,
|
||||
k = 0;
|
||||
|
||||
for (; n > 16; n -= 16, i--)
|
||||
for (var j = 0; j < 4; j++)
|
||||
r[j] = r[j+1];
|
||||
|
||||
for (; i > 0; i--) {
|
||||
var s = r[i-1];
|
||||
r[i-1] = (s >>> n) | k;
|
||||
k = ((s & ((1 << n) - 1)) << (16 - n));
|
||||
}
|
||||
|
||||
r.length = 4;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
return L.Class.extend({
|
||||
seed: function(n) {
|
||||
n = (n - 1)|0;
|
||||
s[0] = n & 0xffff;
|
||||
s[1] = n >>> 16;
|
||||
s[2] = 0;
|
||||
s[3] = 0;
|
||||
},
|
||||
|
||||
int: function() {
|
||||
s = mul(s, [0x7f2d, 0x4c95, 0xf42d, 0x5851]);
|
||||
s = add(s, 1);
|
||||
|
||||
var r = shr(s, 33);
|
||||
return (r[1] << 16) | r[0];
|
||||
},
|
||||
|
||||
get: function() {
|
||||
var r = (this.int() % 0x7fffffff) / 0x7fffffff, l, u;
|
||||
|
||||
switch (arguments.length) {
|
||||
case 0:
|
||||
return r;
|
||||
|
||||
case 1:
|
||||
l = 1;
|
||||
u = arguments[0]|0;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
l = arguments[0]|0;
|
||||
u = arguments[1]|0;
|
||||
break;
|
||||
}
|
||||
|
||||
return Math.floor(r * (u - l + 1)) + l;
|
||||
}
|
||||
});
|
327
luci-base/htdocs/luci-static/resources/tools/widgets.js
Normal file
327
luci-base/htdocs/luci-static/resources/tools/widgets.js
Normal file
|
@ -0,0 +1,327 @@
|
|||
'use strict';
|
||||
'require ui';
|
||||
'require form';
|
||||
'require network';
|
||||
'require firewall';
|
||||
|
||||
var CBIZoneSelect = form.ListValue.extend({
|
||||
__name__: 'CBI.ZoneSelect',
|
||||
|
||||
load: function(section_id) {
|
||||
return Promise.all([ firewall.getZones(), network.getNetworks() ]).then(L.bind(function(zn) {
|
||||
this.zones = zn[0];
|
||||
this.networks = zn[1];
|
||||
|
||||
return this.super('load', section_id);
|
||||
}, this));
|
||||
},
|
||||
|
||||
filter: function(section_id, value) {
|
||||
return true;
|
||||
},
|
||||
|
||||
lookupZone: function(name) {
|
||||
return this.zones.filter(function(zone) { return zone.getName() == name })[0];
|
||||
},
|
||||
|
||||
lookupNetwork: function(name) {
|
||||
return this.networks.filter(function(network) { return network.getName() == name })[0];
|
||||
},
|
||||
|
||||
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||
var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
|
||||
choices = {};
|
||||
|
||||
if (this.allowlocal) {
|
||||
choices[''] = E('span', {
|
||||
'class': 'zonebadge',
|
||||
'style': 'background-color:' + firewall.getColorForName(null)
|
||||
}, [
|
||||
E('strong', _('Device')),
|
||||
(this.allowany || this.allowlocal)
|
||||
? ' (%s)'.format(this.alias != 'dest' ? _('output') : _('input')) : ''
|
||||
]);
|
||||
}
|
||||
else if (!this.multiple && (this.rmempty || this.optional)) {
|
||||
choices[''] = E('span', {
|
||||
'class': 'zonebadge',
|
||||
'style': 'background-color:' + firewall.getColorForName(null)
|
||||
}, E('em', _('unspecified')));
|
||||
}
|
||||
|
||||
if (this.allowany) {
|
||||
choices['*'] = E('span', {
|
||||
'class': 'zonebadge',
|
||||
'style': 'background-color:' + firewall.getColorForName(null)
|
||||
}, [
|
||||
E('strong', _('Any zone')),
|
||||
(this.allowany && this.allowlocal) ? ' (%s)'.format(_('forward')) : ''
|
||||
]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.zones.length; i++) {
|
||||
var zone = this.zones[i],
|
||||
name = zone.getName(),
|
||||
networks = zone.getNetworks(),
|
||||
ifaces = [];
|
||||
|
||||
if (!this.filter(section_id, name))
|
||||
continue;
|
||||
|
||||
for (var j = 0; j < networks.length; j++) {
|
||||
var network = this.lookupNetwork(networks[j]);
|
||||
|
||||
if (!network)
|
||||
continue;
|
||||
|
||||
var span = E('span', {
|
||||
'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
|
||||
}, network.getName() + ': ');
|
||||
|
||||
var devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
|
||||
|
||||
for (var k = 0; k < devices.length; k++) {
|
||||
span.appendChild(E('img', {
|
||||
'title': devices[k].getI18n(),
|
||||
'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
|
||||
}));
|
||||
}
|
||||
|
||||
if (!devices.length)
|
||||
span.appendChild(E('em', _('(empty)')));
|
||||
|
||||
ifaces.push(span);
|
||||
}
|
||||
|
||||
if (!ifaces.length)
|
||||
ifaces.push(E('em', _('(empty)')));
|
||||
|
||||
choices[name] = E('span', {
|
||||
'class': 'zonebadge',
|
||||
'style': 'background-color:' + zone.getColor()
|
||||
}, [ E('strong', name) ].concat(ifaces));
|
||||
}
|
||||
|
||||
var widget = new ui.Dropdown(values, choices, {
|
||||
id: this.cbid(section_id),
|
||||
sort: true,
|
||||
multiple: this.multiple,
|
||||
optional: this.optional || this.rmempty,
|
||||
select_placeholder: E('em', _('unspecified')),
|
||||
display_items: this.display_size || this.size || 3,
|
||||
dropdown_items: this.dropdown_size || this.size || 5,
|
||||
validate: L.bind(this.validate, this, section_id),
|
||||
create: !this.nocreate,
|
||||
create_markup: '' +
|
||||
'<li data-value="{{value}}">' +
|
||||
'<span class="zonebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)">' +
|
||||
'<strong>{{value}}:</strong> <em>('+_('create')+')</em>' +
|
||||
'</span>' +
|
||||
'</li>'
|
||||
});
|
||||
|
||||
return widget.render();
|
||||
},
|
||||
});
|
||||
|
||||
var CBIZoneForwards = form.DummyValue.extend({
|
||||
__name__: 'CBI.ZoneForwards',
|
||||
|
||||
load: function(section_id) {
|
||||
return Promise.all([ firewall.getDefaults(), firewall.getZones(), network.getNetworks() ]).then(L.bind(function(dzn) {
|
||||
this.defaults = dzn[0];
|
||||
this.zones = dzn[1];
|
||||
this.networks = dzn[2];
|
||||
|
||||
return this.super('load', section_id);
|
||||
}, this));
|
||||
},
|
||||
|
||||
renderZone: function(zone) {
|
||||
var name = zone.getName(),
|
||||
networks = zone.getNetworks(),
|
||||
ifaces = [];
|
||||
|
||||
for (var j = 0; j < networks.length; j++) {
|
||||
var network = this.networks.filter(function(net) { return net.getName() == networks[j] })[0];
|
||||
|
||||
if (!network)
|
||||
continue;
|
||||
|
||||
var span = E('span', {
|
||||
'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
|
||||
}, network.getName() + ': ');
|
||||
|
||||
var devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
|
||||
|
||||
for (var k = 0; k < devices.length && devices[k]; k++) {
|
||||
span.appendChild(E('img', {
|
||||
'title': devices[k].getI18n(),
|
||||
'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
|
||||
}));
|
||||
}
|
||||
|
||||
if (!devices.length)
|
||||
span.appendChild(E('em', _('(empty)')));
|
||||
|
||||
ifaces.push(span);
|
||||
}
|
||||
|
||||
if (!ifaces.length)
|
||||
ifaces.push(E('span', { 'class': 'ifacebadge' }, E('em', _('(empty)'))));
|
||||
|
||||
return E('label', {
|
||||
'class': 'zonebadge cbi-tooltip-container',
|
||||
'style': 'background-color:' + zone.getColor()
|
||||
}, [
|
||||
E('strong', name),
|
||||
E('div', { 'class': 'cbi-tooltip' }, ifaces)
|
||||
]);
|
||||
},
|
||||
|
||||
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||
var value = (cfgvalue != null) ? cfgvalue : this.default,
|
||||
zone = this.zones.filter(function(z) { return z.getName() == value })[0];
|
||||
|
||||
if (!zone)
|
||||
return E([]);
|
||||
|
||||
var forwards = zone.getForwardingsBy('src'),
|
||||
dzones = [];
|
||||
|
||||
for (var i = 0; i < forwards.length; i++) {
|
||||
var dzone = forwards[i].getDestinationZone();
|
||||
|
||||
if (!dzone)
|
||||
continue;
|
||||
|
||||
dzones.push(this.renderZone(dzone));
|
||||
}
|
||||
|
||||
if (!dzones.length)
|
||||
dzones.push(E('label', { 'class': 'zonebadge zonebadge-empty' },
|
||||
E('strong', this.defaults.getForward())));
|
||||
|
||||
return E('div', { 'class': 'zone-forwards' }, [
|
||||
E('div', { 'class': 'zone-src' }, this.renderZone(zone)),
|
||||
E('span', '⇒'),
|
||||
E('div', { 'class': 'zone-dest' }, dzones)
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
var CBINetworkSelect = form.ListValue.extend({
|
||||
__name__: 'CBI.NetworkSelect',
|
||||
|
||||
load: function(section_id) {
|
||||
return network.getNetworks().then(L.bind(function(networks) {
|
||||
this.networks = networks;
|
||||
|
||||
return this.super('load', section_id);
|
||||
}, this));
|
||||
},
|
||||
|
||||
filter: function(section_id, value) {
|
||||
return true;
|
||||
},
|
||||
|
||||
renderIfaceBadge: function(network) {
|
||||
var span = E('span', { 'class': 'ifacebadge' }, network.getName() + ': '),
|
||||
devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
|
||||
|
||||
for (var j = 0; j < devices.length && devices[j]; j++) {
|
||||
span.appendChild(E('img', {
|
||||
'title': devices[j].getI18n(),
|
||||
'src': L.resource('icons/%s%s.png'.format(devices[j].getType(), devices[j].isUp() ? '' : '_disabled'))
|
||||
}));
|
||||
}
|
||||
|
||||
if (!devices.length) {
|
||||
span.appendChild(E('em', { 'class': 'hide-close' }, _('(no interfaces attached)')));
|
||||
span.appendChild(E('em', { 'class': 'hide-open' }, '-'));
|
||||
}
|
||||
|
||||
return span;
|
||||
},
|
||||
|
||||
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||
var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
|
||||
choices = {},
|
||||
checked = {};
|
||||
|
||||
for (var i = 0; i < values.length; i++)
|
||||
checked[values[i]] = true;
|
||||
|
||||
values = [];
|
||||
|
||||
if (!this.multiple && (this.rmempty || this.optional))
|
||||
choices[''] = E('em', _('unspecified'));
|
||||
|
||||
for (var i = 0; i < this.networks.length; i++) {
|
||||
var network = this.networks[i],
|
||||
name = network.getName();
|
||||
|
||||
if (name == 'loopback' || !this.filter(section_id, name))
|
||||
continue;
|
||||
|
||||
if (this.novirtual && network.isVirtual())
|
||||
continue;
|
||||
|
||||
if (checked[name])
|
||||
values.push(name);
|
||||
|
||||
choices[name] = this.renderIfaceBadge(network);
|
||||
}
|
||||
|
||||
var widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
|
||||
id: this.cbid(section_id),
|
||||
sort: true,
|
||||
multiple: this.multiple,
|
||||
optional: this.optional || this.rmempty,
|
||||
select_placeholder: E('em', _('unspecified')),
|
||||
display_items: this.display_size || this.size || 3,
|
||||
dropdown_items: this.dropdown_size || this.size || 5,
|
||||
validate: L.bind(this.validate, this, section_id),
|
||||
create: !this.nocreate,
|
||||
create_markup: '' +
|
||||
'<li data-value="{{value}}">' +
|
||||
'<span class="ifacebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)">' +
|
||||
'{{value}}: <em>('+_('create')+')</em>' +
|
||||
'</span>' +
|
||||
'</li>'
|
||||
});
|
||||
|
||||
return widget.render();
|
||||
},
|
||||
|
||||
textvalue: function(section_id) {
|
||||
var cfgvalue = this.cfgvalue(section_id),
|
||||
values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
|
||||
rv = E([]);
|
||||
|
||||
for (var i = 0; i < (this.networks || []).length; i++) {
|
||||
var network = this.networks[i],
|
||||
name = network.getName();
|
||||
|
||||
if (values.indexOf(name) == -1)
|
||||
continue;
|
||||
|
||||
if (rv.length)
|
||||
L.dom.append(rv, ' ');
|
||||
|
||||
L.dom.append(rv, this.renderIfaceBadge(network));
|
||||
}
|
||||
|
||||
if (!rv.firstChild)
|
||||
rv.appendChild(E('em', _('unspecified')));
|
||||
|
||||
return rv;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
return L.Class.extend({
|
||||
ZoneSelect: CBIZoneSelect,
|
||||
ZoneForwards: CBIZoneForwards,
|
||||
NetworkSelect: CBINetworkSelect
|
||||
});
|
540
luci-base/htdocs/luci-static/resources/uci.js
Normal file
540
luci-base/htdocs/luci-static/resources/uci.js
Normal file
|
@ -0,0 +1,540 @@
|
|||
'use strict';
|
||||
'require rpc';
|
||||
|
||||
return L.Class.extend({
|
||||
__init__: function() {
|
||||
this.state = {
|
||||
newidx: 0,
|
||||
values: { },
|
||||
creates: { },
|
||||
changes: { },
|
||||
deletes: { },
|
||||
reorder: { }
|
||||
};
|
||||
|
||||
this.loaded = {};
|
||||
},
|
||||
|
||||
callLoad: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'get',
|
||||
params: [ 'config' ],
|
||||
expect: { values: { } }
|
||||
}),
|
||||
|
||||
callOrder: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'order',
|
||||
params: [ 'config', 'sections' ]
|
||||
}),
|
||||
|
||||
callAdd: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'add',
|
||||
params: [ 'config', 'type', 'name', 'values' ],
|
||||
expect: { section: '' }
|
||||
}),
|
||||
|
||||
callSet: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'set',
|
||||
params: [ 'config', 'section', 'values' ]
|
||||
}),
|
||||
|
||||
callDelete: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'delete',
|
||||
params: [ 'config', 'section', 'options' ]
|
||||
}),
|
||||
|
||||
callApply: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'apply',
|
||||
params: [ 'timeout', 'rollback' ]
|
||||
}),
|
||||
|
||||
callConfirm: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'confirm'
|
||||
}),
|
||||
|
||||
createSID: function(conf) {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
sid;
|
||||
|
||||
do {
|
||||
sid = "new%06x".format(Math.random() * 0xFFFFFF);
|
||||
} while ((n[conf] && n[conf][sid]) || (v[conf] && v[conf][sid]));
|
||||
|
||||
return sid;
|
||||
},
|
||||
|
||||
resolveSID: function(conf, sid) {
|
||||
if (typeof(sid) != 'string')
|
||||
return sid;
|
||||
|
||||
var m = /^@([a-zA-Z0-9_-]+)\[(-?[0-9]+)\]$/.exec(sid);
|
||||
|
||||
if (m) {
|
||||
var type = m[1],
|
||||
pos = +m[2],
|
||||
sections = this.sections(conf, type),
|
||||
section = sections[pos >= 0 ? pos : sections.length + pos];
|
||||
|
||||
return section ? section['.name'] : null;
|
||||
}
|
||||
|
||||
return sid;
|
||||
},
|
||||
|
||||
reorderSections: function() {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
r = this.state.reorder,
|
||||
tasks = [];
|
||||
|
||||
if (Object.keys(r).length === 0)
|
||||
return Promise.resolve();
|
||||
|
||||
/*
|
||||
gather all created and existing sections, sort them according
|
||||
to their index value and issue an uci order call
|
||||
*/
|
||||
for (var c in r) {
|
||||
var o = [ ];
|
||||
|
||||
if (n[c])
|
||||
for (var s in n[c])
|
||||
o.push(n[c][s]);
|
||||
|
||||
for (var s in v[c])
|
||||
o.push(v[c][s]);
|
||||
|
||||
if (o.length > 0) {
|
||||
o.sort(function(a, b) {
|
||||
return (a['.index'] - b['.index']);
|
||||
});
|
||||
|
||||
var sids = [ ];
|
||||
|
||||
for (var i = 0; i < o.length; i++)
|
||||
sids.push(o[i]['.name']);
|
||||
|
||||
tasks.push(this.callOrder(c, sids));
|
||||
}
|
||||
}
|
||||
|
||||
this.state.reorder = { };
|
||||
return Promise.all(tasks);
|
||||
},
|
||||
|
||||
loadPackage: function(packageName) {
|
||||
if (this.loaded[packageName] == null)
|
||||
return (this.loaded[packageName] = this.callLoad(packageName));
|
||||
|
||||
return Promise.resolve(this.loaded[packageName]);
|
||||
},
|
||||
|
||||
load: function(packages) {
|
||||
var self = this,
|
||||
pkgs = [ ],
|
||||
tasks = [];
|
||||
|
||||
if (!Array.isArray(packages))
|
||||
packages = [ packages ];
|
||||
|
||||
for (var i = 0; i < packages.length; i++)
|
||||
if (!self.state.values[packages[i]]) {
|
||||
pkgs.push(packages[i]);
|
||||
tasks.push(self.loadPackage(packages[i]));
|
||||
}
|
||||
|
||||
return Promise.all(tasks).then(function(responses) {
|
||||
for (var i = 0; i < responses.length; i++)
|
||||
self.state.values[pkgs[i]] = responses[i];
|
||||
|
||||
if (responses.length)
|
||||
document.dispatchEvent(new CustomEvent('uci-loaded'));
|
||||
|
||||
return pkgs;
|
||||
});
|
||||
},
|
||||
|
||||
unload: function(packages) {
|
||||
if (!Array.isArray(packages))
|
||||
packages = [ packages ];
|
||||
|
||||
for (var i = 0; i < packages.length; i++) {
|
||||
delete this.state.values[packages[i]];
|
||||
delete this.state.creates[packages[i]];
|
||||
delete this.state.changes[packages[i]];
|
||||
delete this.state.deletes[packages[i]];
|
||||
|
||||
delete this.loaded[packages[i]];
|
||||
}
|
||||
},
|
||||
|
||||
add: function(conf, type, name) {
|
||||
var n = this.state.creates,
|
||||
sid = name || this.createSID(conf);
|
||||
|
||||
if (!n[conf])
|
||||
n[conf] = { };
|
||||
|
||||
n[conf][sid] = {
|
||||
'.type': type,
|
||||
'.name': sid,
|
||||
'.create': name,
|
||||
'.anonymous': !name,
|
||||
'.index': 1000 + this.state.newidx++
|
||||
};
|
||||
|
||||
return sid;
|
||||
},
|
||||
|
||||
remove: function(conf, sid) {
|
||||
var n = this.state.creates,
|
||||
c = this.state.changes,
|
||||
d = this.state.deletes;
|
||||
|
||||
/* requested deletion of a just created section */
|
||||
if (n[conf] && n[conf][sid]) {
|
||||
delete n[conf][sid];
|
||||
}
|
||||
else {
|
||||
if (c[conf])
|
||||
delete c[conf][sid];
|
||||
|
||||
if (!d[conf])
|
||||
d[conf] = { };
|
||||
|
||||
d[conf][sid] = true;
|
||||
}
|
||||
},
|
||||
|
||||
sections: function(conf, type, cb) {
|
||||
var sa = [ ],
|
||||
v = this.state.values[conf],
|
||||
n = this.state.creates[conf],
|
||||
c = this.state.changes[conf],
|
||||
d = this.state.deletes[conf];
|
||||
|
||||
if (!v)
|
||||
return sa;
|
||||
|
||||
for (var s in v)
|
||||
if (!d || d[s] !== true)
|
||||
if (!type || v[s]['.type'] == type)
|
||||
sa.push(Object.assign({ }, v[s], c ? c[s] : undefined));
|
||||
|
||||
if (n)
|
||||
for (var s in n)
|
||||
if (!type || n[s]['.type'] == type)
|
||||
sa.push(Object.assign({ }, n[s]));
|
||||
|
||||
sa.sort(function(a, b) {
|
||||
return a['.index'] - b['.index'];
|
||||
});
|
||||
|
||||
for (var i = 0; i < sa.length; i++)
|
||||
sa[i]['.index'] = i;
|
||||
|
||||
if (typeof(cb) == 'function')
|
||||
for (var i = 0; i < sa.length; i++)
|
||||
cb.call(this, sa[i], sa[i]['.name']);
|
||||
|
||||
return sa;
|
||||
},
|
||||
|
||||
get: function(conf, sid, opt) {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
c = this.state.changes,
|
||||
d = this.state.deletes;
|
||||
|
||||
sid = this.resolveSID(conf, sid);
|
||||
|
||||
if (sid == null)
|
||||
return null;
|
||||
|
||||
/* requested option in a just created section */
|
||||
if (n[conf] && n[conf][sid]) {
|
||||
if (!n[conf])
|
||||
return undefined;
|
||||
|
||||
if (opt == null)
|
||||
return n[conf][sid];
|
||||
|
||||
return n[conf][sid][opt];
|
||||
}
|
||||
|
||||
/* requested an option value */
|
||||
if (opt != null) {
|
||||
/* check whether option was deleted */
|
||||
if (d[conf] && d[conf][sid]) {
|
||||
if (d[conf][sid] === true)
|
||||
return undefined;
|
||||
|
||||
for (var i = 0; i < d[conf][sid].length; i++)
|
||||
if (d[conf][sid][i] == opt)
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/* check whether option was changed */
|
||||
if (c[conf] && c[conf][sid] && c[conf][sid][opt] != null)
|
||||
return c[conf][sid][opt];
|
||||
|
||||
/* return base value */
|
||||
if (v[conf] && v[conf][sid])
|
||||
return v[conf][sid][opt];
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/* requested an entire section */
|
||||
if (v[conf])
|
||||
return v[conf][sid];
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
set: function(conf, sid, opt, val) {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
c = this.state.changes,
|
||||
d = this.state.deletes;
|
||||
|
||||
sid = this.resolveSID(conf, sid);
|
||||
|
||||
if (sid == null || opt == null || opt.charAt(0) == '.')
|
||||
return;
|
||||
|
||||
if (n[conf] && n[conf][sid]) {
|
||||
if (val != null)
|
||||
n[conf][sid][opt] = val;
|
||||
else
|
||||
delete n[conf][sid][opt];
|
||||
}
|
||||
else if (val != null && val !== '') {
|
||||
/* do not set within deleted section */
|
||||
if (d[conf] && d[conf][sid] === true)
|
||||
return;
|
||||
|
||||
/* only set in existing sections */
|
||||
if (!v[conf] || !v[conf][sid])
|
||||
return;
|
||||
|
||||
if (!c[conf])
|
||||
c[conf] = {};
|
||||
|
||||
if (!c[conf][sid])
|
||||
c[conf][sid] = {};
|
||||
|
||||
/* undelete option */
|
||||
if (d[conf] && d[conf][sid])
|
||||
d[conf][sid] = d[conf][sid].filter(function(o) { return o !== opt });
|
||||
|
||||
c[conf][sid][opt] = val;
|
||||
}
|
||||
else {
|
||||
/* only delete in existing sections */
|
||||
if (!(v[conf] && v[conf][sid] && v[conf][sid].hasOwnProperty(opt)) &&
|
||||
!(c[conf] && c[conf][sid] && c[conf][sid].hasOwnProperty(opt)))
|
||||
return;
|
||||
|
||||
if (!d[conf])
|
||||
d[conf] = { };
|
||||
|
||||
if (!d[conf][sid])
|
||||
d[conf][sid] = [ ];
|
||||
|
||||
if (d[conf][sid] !== true)
|
||||
d[conf][sid].push(opt);
|
||||
}
|
||||
},
|
||||
|
||||
unset: function(conf, sid, opt) {
|
||||
return this.set(conf, sid, opt, null);
|
||||
},
|
||||
|
||||
get_first: function(conf, type, opt) {
|
||||
var sid = null;
|
||||
|
||||
this.sections(conf, type, function(s) {
|
||||
if (sid == null)
|
||||
sid = s['.name'];
|
||||
});
|
||||
|
||||
return this.get(conf, sid, opt);
|
||||
},
|
||||
|
||||
set_first: function(conf, type, opt, val) {
|
||||
var sid = null;
|
||||
|
||||
this.sections(conf, type, function(s) {
|
||||
if (sid == null)
|
||||
sid = s['.name'];
|
||||
});
|
||||
|
||||
return this.set(conf, sid, opt, val);
|
||||
},
|
||||
|
||||
unset_first: function(conf, type, opt) {
|
||||
return this.set_first(conf, type, opt, null);
|
||||
},
|
||||
|
||||
move: function(conf, sid1, sid2, after) {
|
||||
var sa = this.sections(conf),
|
||||
s1 = null, s2 = null;
|
||||
|
||||
sid1 = this.resolveSID(conf, sid1);
|
||||
sid2 = this.resolveSID(conf, sid2);
|
||||
|
||||
for (var i = 0; i < sa.length; i++) {
|
||||
if (sa[i]['.name'] != sid1)
|
||||
continue;
|
||||
|
||||
s1 = sa[i];
|
||||
sa.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (s1 == null)
|
||||
return false;
|
||||
|
||||
if (sid2 == null) {
|
||||
sa.push(s1);
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < sa.length; i++) {
|
||||
if (sa[i]['.name'] != sid2)
|
||||
continue;
|
||||
|
||||
s2 = sa[i];
|
||||
sa.splice(i + !!after, 0, s1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (s2 == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < sa.length; i++)
|
||||
this.get(conf, sa[i]['.name'])['.index'] = i;
|
||||
|
||||
this.state.reorder[conf] = true;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
save: function() {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
c = this.state.changes,
|
||||
d = this.state.deletes,
|
||||
r = this.state.reorder,
|
||||
self = this,
|
||||
snew = [ ],
|
||||
pkgs = { },
|
||||
tasks = [];
|
||||
|
||||
if (n)
|
||||
for (var conf in n) {
|
||||
for (var sid in n[conf]) {
|
||||
var r = {
|
||||
config: conf,
|
||||
values: { }
|
||||
};
|
||||
|
||||
for (var k in n[conf][sid]) {
|
||||
if (k == '.type')
|
||||
r.type = n[conf][sid][k];
|
||||
else if (k == '.create')
|
||||
r.name = n[conf][sid][k];
|
||||
else if (k.charAt(0) != '.')
|
||||
r.values[k] = n[conf][sid][k];
|
||||
}
|
||||
|
||||
snew.push(n[conf][sid]);
|
||||
tasks.push(self.callAdd(r.config, r.type, r.name, r.values));
|
||||
}
|
||||
|
||||
pkgs[conf] = true;
|
||||
}
|
||||
|
||||
if (c)
|
||||
for (var conf in c) {
|
||||
for (var sid in c[conf])
|
||||
tasks.push(self.callSet(conf, sid, c[conf][sid]));
|
||||
|
||||
pkgs[conf] = true;
|
||||
}
|
||||
|
||||
if (d)
|
||||
for (var conf in d) {
|
||||
for (var sid in d[conf]) {
|
||||
var o = d[conf][sid];
|
||||
tasks.push(self.callDelete(conf, sid, (o === true) ? null : o));
|
||||
}
|
||||
|
||||
pkgs[conf] = true;
|
||||
}
|
||||
|
||||
if (r)
|
||||
for (var conf in r)
|
||||
pkgs[conf] = true;
|
||||
|
||||
return Promise.all(tasks).then(function(responses) {
|
||||
/*
|
||||
array "snew" holds references to the created uci sections,
|
||||
use it to assign the returned names of the new sections
|
||||
*/
|
||||
for (var i = 0; i < snew.length; i++)
|
||||
snew[i]['.name'] = responses[i];
|
||||
|
||||
return self.reorderSections();
|
||||
}).then(function() {
|
||||
pkgs = Object.keys(pkgs);
|
||||
|
||||
self.unload(pkgs);
|
||||
|
||||
return self.load(pkgs);
|
||||
});
|
||||
},
|
||||
|
||||
apply: function(timeout) {
|
||||
var self = this,
|
||||
date = new Date();
|
||||
|
||||
if (typeof(timeout) != 'number' || timeout < 1)
|
||||
timeout = 10;
|
||||
|
||||
return self.callApply(timeout, true).then(function(rv) {
|
||||
if (rv != 0)
|
||||
return Promise.reject(rv);
|
||||
|
||||
var try_deadline = date.getTime() + 1000 * timeout;
|
||||
var try_confirm = function() {
|
||||
return self.callConfirm().then(function(rv) {
|
||||
if (rv != 0) {
|
||||
if (date.getTime() < try_deadline)
|
||||
window.setTimeout(try_confirm, 250);
|
||||
else
|
||||
return Promise.reject(rv);
|
||||
}
|
||||
|
||||
return rv;
|
||||
});
|
||||
};
|
||||
|
||||
window.setTimeout(try_confirm, 1000);
|
||||
});
|
||||
},
|
||||
|
||||
changes: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'changes',
|
||||
expect: { changes: { } }
|
||||
})
|
||||
});
|
2054
luci-base/htdocs/luci-static/resources/ui.js
Normal file
2054
luci-base/htdocs/luci-static/resources/ui.js
Normal file
File diff suppressed because it is too large
Load diff
568
luci-base/htdocs/luci-static/resources/validation.js
Normal file
568
luci-base/htdocs/luci-static/resources/validation.js
Normal file
|
@ -0,0 +1,568 @@
|
|||
'use strict';
|
||||
|
||||
var Validator = L.Class.extend({
|
||||
__name__: 'Validation',
|
||||
|
||||
__init__: function(field, type, optional, vfunc, validatorFactory) {
|
||||
this.field = field;
|
||||
this.optional = optional;
|
||||
this.vfunc = vfunc;
|
||||
this.vstack = validatorFactory.compile(type);
|
||||
this.factory = validatorFactory;
|
||||
},
|
||||
|
||||
assert: function(condition, message) {
|
||||
if (!condition) {
|
||||
this.field.classList.add('cbi-input-invalid');
|
||||
this.error = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.field.classList.remove('cbi-input-invalid');
|
||||
this.error = null;
|
||||
return true;
|
||||
},
|
||||
|
||||
apply: function(name, value, args) {
|
||||
var func;
|
||||
|
||||
if (typeof(name) === 'function')
|
||||
func = name;
|
||||
else if (typeof(this.factory.types[name]) === 'function')
|
||||
func = this.factory.types[name];
|
||||
else
|
||||
return false;
|
||||
|
||||
if (value != null)
|
||||
this.value = value;
|
||||
|
||||
return func.apply(this, args);
|
||||
},
|
||||
|
||||
validate: function() {
|
||||
/* element is detached */
|
||||
if (!findParent(this.field, 'body') && !findParent(this.field, '[data-field]'))
|
||||
return true;
|
||||
|
||||
this.field.classList.remove('cbi-input-invalid');
|
||||
this.value = (this.field.value != null) ? this.field.value : '';
|
||||
this.error = null;
|
||||
|
||||
var valid;
|
||||
|
||||
if (this.value.length === 0)
|
||||
valid = this.assert(this.optional, _('non-empty value'));
|
||||
else
|
||||
valid = this.vstack[0].apply(this, this.vstack[1]);
|
||||
|
||||
if (valid !== true) {
|
||||
this.field.setAttribute('data-tooltip', _('Expecting: %s').format(this.error));
|
||||
this.field.setAttribute('data-tooltip-style', 'error');
|
||||
this.field.dispatchEvent(new CustomEvent('validation-failure', { bubbles: true }));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof(this.vfunc) == 'function')
|
||||
valid = this.vfunc(this.value);
|
||||
|
||||
if (valid !== true) {
|
||||
this.assert(false, valid);
|
||||
this.field.setAttribute('data-tooltip', valid);
|
||||
this.field.setAttribute('data-tooltip-style', 'error');
|
||||
this.field.dispatchEvent(new CustomEvent('validation-failure', { bubbles: true }));
|
||||
return false;
|
||||
}
|
||||
|
||||
this.field.removeAttribute('data-tooltip');
|
||||
this.field.removeAttribute('data-tooltip-style');
|
||||
this.field.dispatchEvent(new CustomEvent('validation-success', { bubbles: true }));
|
||||
return true;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var ValidatorFactory = L.Class.extend({
|
||||
__name__: 'ValidatorFactory',
|
||||
|
||||
create: function(field, type, optional, vfunc) {
|
||||
return new Validator(field, type, optional, vfunc, this);
|
||||
},
|
||||
|
||||
compile: function(code) {
|
||||
var pos = 0;
|
||||
var esc = false;
|
||||
var depth = 0;
|
||||
var stack = [ ];
|
||||
|
||||
code += ',';
|
||||
|
||||
for (var i = 0; i < code.length; i++) {
|
||||
if (esc) {
|
||||
esc = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (code.charCodeAt(i))
|
||||
{
|
||||
case 92:
|
||||
esc = true;
|
||||
break;
|
||||
|
||||
case 40:
|
||||
case 44:
|
||||
if (depth <= 0) {
|
||||
if (pos < i) {
|
||||
var label = code.substring(pos, i);
|
||||
label = label.replace(/\\(.)/g, '$1');
|
||||
label = label.replace(/^[ \t]+/g, '');
|
||||
label = label.replace(/[ \t]+$/g, '');
|
||||
|
||||
if (label && !isNaN(label)) {
|
||||
stack.push(parseFloat(label));
|
||||
}
|
||||
else if (label.match(/^(['"]).*\1$/)) {
|
||||
stack.push(label.replace(/^(['"])(.*)\1$/, '$2'));
|
||||
}
|
||||
else if (typeof this.types[label] == 'function') {
|
||||
stack.push(this.types[label]);
|
||||
stack.push(null);
|
||||
}
|
||||
else {
|
||||
L.raise('SyntaxError', 'Unhandled token "%s"', label);
|
||||
}
|
||||
}
|
||||
|
||||
pos = i+1;
|
||||
}
|
||||
|
||||
depth += (code.charCodeAt(i) == 40);
|
||||
break;
|
||||
|
||||
case 41:
|
||||
if (--depth <= 0) {
|
||||
if (typeof stack[stack.length-2] != 'function')
|
||||
L.raise('SyntaxError', 'Argument list follows non-function');
|
||||
|
||||
stack[stack.length-1] = this.compile(code.substring(pos, i));
|
||||
pos = i+1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
},
|
||||
|
||||
parseInteger: function(x) {
|
||||
return (/^-?\d+$/.test(x) ? +x : NaN);
|
||||
},
|
||||
|
||||
parseDecimal: function(x) {
|
||||
return (/^-?\d+(?:\.\d+)?$/.test(x) ? +x : NaN);
|
||||
},
|
||||
|
||||
parseIPv4: function(x) {
|
||||
if (!x.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))
|
||||
return null;
|
||||
|
||||
if (RegExp.$1 > 255 || RegExp.$2 > 255 || RegExp.$3 > 255 || RegExp.$4 > 255)
|
||||
return null;
|
||||
|
||||
return [ +RegExp.$1, +RegExp.$2, +RegExp.$3, +RegExp.$4 ];
|
||||
},
|
||||
|
||||
parseIPv6: function(x) {
|
||||
if (x.match(/^([a-fA-F0-9:]+):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/)) {
|
||||
var v6 = RegExp.$1, v4 = this.parseIPv4(RegExp.$2);
|
||||
|
||||
if (!v4)
|
||||
return null;
|
||||
|
||||
x = v6 + ':' + (v4[0] * 256 + v4[1]).toString(16)
|
||||
+ ':' + (v4[2] * 256 + v4[3]).toString(16);
|
||||
}
|
||||
|
||||
if (!x.match(/^[a-fA-F0-9:]+$/))
|
||||
return null;
|
||||
|
||||
var prefix_suffix = x.split(/::/);
|
||||
|
||||
if (prefix_suffix.length > 2)
|
||||
return null;
|
||||
|
||||
var prefix = (prefix_suffix[0] || '0').split(/:/);
|
||||
var suffix = prefix_suffix.length > 1 ? (prefix_suffix[1] || '0').split(/:/) : [];
|
||||
|
||||
if (suffix.length ? (prefix.length + suffix.length > 7)
|
||||
: ((prefix_suffix.length < 2 && prefix.length < 8) || prefix.length > 8))
|
||||
return null;
|
||||
|
||||
var i, word;
|
||||
var words = [];
|
||||
|
||||
for (i = 0, word = parseInt(prefix[0], 16); i < prefix.length; word = parseInt(prefix[++i], 16))
|
||||
if (prefix[i].length <= 4 && !isNaN(word) && word <= 0xFFFF)
|
||||
words.push(word);
|
||||
else
|
||||
return null;
|
||||
|
||||
for (i = 0; i < (8 - prefix.length - suffix.length); i++)
|
||||
words.push(0);
|
||||
|
||||
for (i = 0, word = parseInt(suffix[0], 16); i < suffix.length; word = parseInt(suffix[++i], 16))
|
||||
if (suffix[i].length <= 4 && !isNaN(word) && word <= 0xFFFF)
|
||||
words.push(word);
|
||||
else
|
||||
return null;
|
||||
|
||||
return words;
|
||||
},
|
||||
|
||||
types: {
|
||||
integer: function() {
|
||||
return this.assert(this.factory.parseInteger(this.value) !== NaN, _('valid integer value'));
|
||||
},
|
||||
|
||||
uinteger: function() {
|
||||
return this.assert(this.factory.parseInteger(this.value) >= 0, _('positive integer value'));
|
||||
},
|
||||
|
||||
float: function() {
|
||||
return this.assert(this.factory.parseDecimal(this.value) !== NaN, _('valid decimal value'));
|
||||
},
|
||||
|
||||
ufloat: function() {
|
||||
return this.assert(this.factory.parseDecimal(this.value) >= 0, _('positive decimal value'));
|
||||
},
|
||||
|
||||
ipaddr: function(nomask) {
|
||||
return this.assert(this.apply('ip4addr', null, [nomask]) || this.apply('ip6addr', null, [nomask]),
|
||||
nomask ? _('valid IP address') : _('valid IP address or prefix'));
|
||||
},
|
||||
|
||||
ip4addr: function(nomask) {
|
||||
var re = nomask ? /^(\d+\.\d+\.\d+\.\d+)$/ : /^(\d+\.\d+\.\d+\.\d+)(?:\/(\d+\.\d+\.\d+\.\d+)|\/(\d{1,2}))?$/,
|
||||
m = this.value.match(re);
|
||||
|
||||
return this.assert(m && this.factory.parseIPv4(m[1]) && (m[2] ? this.factory.parseIPv4(m[2]) : (m[3] ? this.apply('ip4prefix', m[3]) : true)),
|
||||
nomask ? _('valid IPv4 address') : _('valid IPv4 address or network'));
|
||||
},
|
||||
|
||||
ip6addr: function(nomask) {
|
||||
var re = nomask ? /^([0-9a-fA-F:.]+)$/ : /^([0-9a-fA-F:.]+)(?:\/(\d{1,3}))?$/,
|
||||
m = this.value.match(re);
|
||||
|
||||
return this.assert(m && this.factory.parseIPv6(m[1]) && (m[2] ? this.apply('ip6prefix', m[2]) : true),
|
||||
nomask ? _('valid IPv6 address') : _('valid IPv6 address or prefix'));
|
||||
},
|
||||
|
||||
ip4prefix: function() {
|
||||
return this.assert(!isNaN(this.value) && this.value >= 0 && this.value <= 32,
|
||||
_('valid IPv4 prefix value (0-32)'));
|
||||
},
|
||||
|
||||
ip6prefix: function() {
|
||||
return this.assert(!isNaN(this.value) && this.value >= 0 && this.value <= 128,
|
||||
_('valid IPv6 prefix value (0-128)'));
|
||||
},
|
||||
|
||||
cidr: function() {
|
||||
return this.assert(this.apply('cidr4') || this.apply('cidr6'), _('valid IPv4 or IPv6 CIDR'));
|
||||
},
|
||||
|
||||
cidr4: function() {
|
||||
var m = this.value.match(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})$/);
|
||||
return this.assert(m && this.factory.parseIPv4(m[1]) && this.apply('ip4prefix', m[2]), _('valid IPv4 CIDR'));
|
||||
},
|
||||
|
||||
cidr6: function() {
|
||||
var m = this.value.match(/^([0-9a-fA-F:.]+)\/(\d{1,3})$/);
|
||||
return this.assert(m && this.factory.parseIPv6(m[1]) && this.apply('ip6prefix', m[2]), _('valid IPv6 CIDR'));
|
||||
},
|
||||
|
||||
ipnet4: function() {
|
||||
var m = this.value.match(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
||||
return this.assert(m && this.factory.parseIPv4(m[1]) && this.factory.parseIPv4(m[2]), _('IPv4 network in address/netmask notation'));
|
||||
},
|
||||
|
||||
ipnet6: function() {
|
||||
var m = this.value.match(/^([0-9a-fA-F:.]+)\/([0-9a-fA-F:.]+)$/);
|
||||
return this.assert(m && this.factory.parseIPv6(m[1]) && this.factory.parseIPv6(m[2]), _('IPv6 network in address/netmask notation'));
|
||||
},
|
||||
|
||||
ip6hostid: function() {
|
||||
if (this.value == "eui64" || this.value == "random")
|
||||
return true;
|
||||
|
||||
var v6 = this.factory.parseIPv6(this.value);
|
||||
return this.assert(!(!v6 || v6[0] || v6[1] || v6[2] || v6[3]), _('valid IPv6 host id'));
|
||||
},
|
||||
|
||||
ipmask: function() {
|
||||
return this.assert(this.apply('ipmask4') || this.apply('ipmask6'),
|
||||
_('valid network in address/netmask notation'));
|
||||
},
|
||||
|
||||
ipmask4: function() {
|
||||
return this.assert(this.apply('cidr4') || this.apply('ipnet4') || this.apply('ip4addr'),
|
||||
_('valid IPv4 network'));
|
||||
},
|
||||
|
||||
ipmask6: function() {
|
||||
return this.assert(this.apply('cidr6') || this.apply('ipnet6') || this.apply('ip6addr'),
|
||||
_('valid IPv6 network'));
|
||||
},
|
||||
|
||||
port: function() {
|
||||
var p = this.factory.parseInteger(this.value);
|
||||
return this.assert(p >= 0 && p <= 65535, _('valid port value'));
|
||||
},
|
||||
|
||||
portrange: function() {
|
||||
if (this.value.match(/^(\d+)-(\d+)$/)) {
|
||||
var p1 = +RegExp.$1;
|
||||
var p2 = +RegExp.$2;
|
||||
return this.assert(p1 <= p2 && p2 <= 65535,
|
||||
_('valid port or port range (port1-port2)'));
|
||||
}
|
||||
|
||||
return this.assert(this.apply('port'), _('valid port or port range (port1-port2)'));
|
||||
},
|
||||
|
||||
macaddr: function() {
|
||||
return this.assert(this.value.match(/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/) != null,
|
||||
_('valid MAC address'));
|
||||
},
|
||||
|
||||
host: function(ipv4only) {
|
||||
return this.assert(this.apply('hostname') || this.apply(ipv4only == 1 ? 'ip4addr' : 'ipaddr'),
|
||||
_('valid hostname or IP address'));
|
||||
},
|
||||
|
||||
hostname: function(strict) {
|
||||
if (this.value.length <= 253)
|
||||
return this.assert(
|
||||
(this.value.match(/^[a-zA-Z0-9_]+$/) != null ||
|
||||
(this.value.match(/^[a-zA-Z0-9_][a-zA-Z0-9_\-.]*[a-zA-Z0-9]$/) &&
|
||||
this.value.match(/[^0-9.]/))) &&
|
||||
(!strict || !this.value.match(/^_/)),
|
||||
_('valid hostname'));
|
||||
|
||||
return this.assert(false, _('valid hostname'));
|
||||
},
|
||||
|
||||
network: function() {
|
||||
return this.assert(this.apply('uciname') || this.apply('host'),
|
||||
_('valid UCI identifier, hostname or IP address'));
|
||||
},
|
||||
|
||||
hostport: function(ipv4only) {
|
||||
var hp = this.value.split(/:/);
|
||||
return this.assert(hp.length == 2 && this.apply('host', hp[0], [ipv4only]) && this.apply('port', hp[1]),
|
||||
_('valid host:port'));
|
||||
},
|
||||
|
||||
ip4addrport: function() {
|
||||
var hp = this.value.split(/:/);
|
||||
return this.assert(hp.length == 2 && this.apply('ip4addr', hp[0], [true]) && this.apply('port', hp[1]),
|
||||
_('valid IPv4 address:port'));
|
||||
},
|
||||
|
||||
ipaddrport: function(bracket) {
|
||||
var m4 = this.value.match(/^([^\[\]:]+):(\d+)$/),
|
||||
m6 = this.value.match((bracket == 1) ? /^\[(.+)\]:(\d+)$/ : /^([^\[\]]+):(\d+)$/);
|
||||
|
||||
if (m4)
|
||||
return this.assert(this.apply('ip4addr', m4[1], [true]) && this.apply('port', m4[2]),
|
||||
_('valid address:port'));
|
||||
|
||||
return this.assert(m6 && this.apply('ip6addr', m6[1], [true]) && this.apply('port', m6[2]),
|
||||
_('valid address:port'));
|
||||
},
|
||||
|
||||
wpakey: function() {
|
||||
var v = this.value;
|
||||
|
||||
if (v.length == 64)
|
||||
return this.assert(v.match(/^[a-fA-F0-9]{64}$/), _('valid hexadecimal WPA key'));
|
||||
|
||||
return this.assert((v.length >= 8) && (v.length <= 63), _('key between 8 and 63 characters'));
|
||||
},
|
||||
|
||||
wepkey: function() {
|
||||
var v = this.value;
|
||||
|
||||
if (v.substr(0, 2) === 's:')
|
||||
v = v.substr(2);
|
||||
|
||||
if ((v.length == 10) || (v.length == 26))
|
||||
return this.assert(v.match(/^[a-fA-F0-9]{10,26}$/), _('valid hexadecimal WEP key'));
|
||||
|
||||
return this.assert((v.length === 5) || (v.length === 13), _('key with either 5 or 13 characters'));
|
||||
},
|
||||
|
||||
uciname: function() {
|
||||
return this.assert(this.value.match(/^[a-zA-Z0-9_]+$/), _('valid UCI identifier'));
|
||||
},
|
||||
|
||||
range: function(min, max) {
|
||||
var val = this.factory.parseDecimal(this.value);
|
||||
return this.assert(val >= +min && val <= +max, _('value between %f and %f').format(min, max));
|
||||
},
|
||||
|
||||
min: function(min) {
|
||||
return this.assert(this.factory.parseDecimal(this.value) >= +min, _('value greater or equal to %f').format(min));
|
||||
},
|
||||
|
||||
max: function(max) {
|
||||
return this.assert(this.factory.parseDecimal(this.value) <= +max, _('value smaller or equal to %f').format(max));
|
||||
},
|
||||
|
||||
rangelength: function(min, max) {
|
||||
var val = '' + this.value;
|
||||
return this.assert((val.length >= +min) && (val.length <= +max),
|
||||
_('value between %d and %d characters').format(min, max));
|
||||
},
|
||||
|
||||
minlength: function(min) {
|
||||
return this.assert((''+this.value).length >= +min,
|
||||
_('value with at least %d characters').format(min));
|
||||
},
|
||||
|
||||
maxlength: function(max) {
|
||||
return this.assert((''+this.value).length <= +max,
|
||||
_('value with at most %d characters').format(max));
|
||||
},
|
||||
|
||||
or: function() {
|
||||
var errors = [];
|
||||
|
||||
for (var i = 0; i < arguments.length; i += 2) {
|
||||
if (typeof arguments[i] != 'function') {
|
||||
if (arguments[i] == this.value)
|
||||
return this.assert(true);
|
||||
errors.push('"%s"'.format(arguments[i]));
|
||||
i--;
|
||||
}
|
||||
else if (arguments[i].apply(this, arguments[i+1])) {
|
||||
return this.assert(true);
|
||||
}
|
||||
else {
|
||||
errors.push(this.error);
|
||||
}
|
||||
}
|
||||
|
||||
var t = _('One of the following: %s');
|
||||
|
||||
return this.assert(false, t.format('\n - ' + errors.join('\n - ')));
|
||||
},
|
||||
|
||||
and: function() {
|
||||
for (var i = 0; i < arguments.length; i += 2) {
|
||||
if (typeof arguments[i] != 'function') {
|
||||
if (arguments[i] != this.value)
|
||||
return this.assert(false, '"%s"'.format(arguments[i]));
|
||||
i--;
|
||||
}
|
||||
else if (!arguments[i].apply(this, arguments[i+1])) {
|
||||
return this.assert(false, this.error);
|
||||
}
|
||||
}
|
||||
|
||||
return this.assert(true);
|
||||
},
|
||||
|
||||
neg: function() {
|
||||
this.value = this.value.replace(/^[ \t]*![ \t]*/, '');
|
||||
|
||||
if (arguments[0].apply(this, arguments[1]))
|
||||
return this.assert(true);
|
||||
|
||||
return this.assert(false, _('Potential negation of: %s').format(this.error));
|
||||
},
|
||||
|
||||
list: function(subvalidator, subargs) {
|
||||
this.field.setAttribute('data-is-list', 'true');
|
||||
|
||||
var tokens = this.value.match(/[^ \t]+/g);
|
||||
for (var i = 0; i < tokens.length; i++)
|
||||
if (!this.apply(subvalidator, tokens[i], subargs))
|
||||
return this.assert(false, this.error);
|
||||
|
||||
return this.assert(true);
|
||||
},
|
||||
|
||||
phonedigit: function() {
|
||||
return this.assert(this.value.match(/^[0-9\*#!\.]+$/),
|
||||
_('valid phone digit (0-9, "*", "#", "!" or ".")'));
|
||||
},
|
||||
|
||||
timehhmmss: function() {
|
||||
return this.assert(this.value.match(/^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$/),
|
||||
_('valid time (HH:MM:SS)'));
|
||||
},
|
||||
|
||||
dateyyyymmdd: function() {
|
||||
if (this.value.match(/^(\d\d\d\d)-(\d\d)-(\d\d)/)) {
|
||||
var year = +RegExp.$1,
|
||||
month = +RegExp.$2,
|
||||
day = +RegExp.$3,
|
||||
days_in_month = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
|
||||
|
||||
var is_leap_year = function(year) {
|
||||
return ((!(year % 4) && (year % 100)) || !(year % 400));
|
||||
}
|
||||
|
||||
var get_days_in_month = function(month, year) {
|
||||
return (month === 2 && is_leap_year(year)) ? 29 : days_in_month[month - 1];
|
||||
}
|
||||
|
||||
/* Firewall rules in the past don't make sense */
|
||||
return this.assert(year >= 2015 && month && month <= 12 && day && day <= get_days_in_month(month, year),
|
||||
_('valid date (YYYY-MM-DD)'));
|
||||
|
||||
}
|
||||
|
||||
return this.assert(false, _('valid date (YYYY-MM-DD)'));
|
||||
},
|
||||
|
||||
unique: function(subvalidator, subargs) {
|
||||
var ctx = this,
|
||||
option = findParent(ctx.field, '[data-type][data-name]'),
|
||||
section = findParent(option, '.cbi-section'),
|
||||
query = '[data-type="%s"][data-name="%s"]'.format(option.getAttribute('data-type'), option.getAttribute('data-name')),
|
||||
unique = true;
|
||||
|
||||
section.querySelectorAll(query).forEach(function(sibling) {
|
||||
if (sibling === option)
|
||||
return;
|
||||
|
||||
var input = sibling.querySelector('[data-type]'),
|
||||
values = input ? (input.getAttribute('data-is-list') ? input.value.match(/[^ \t]+/g) : [ input.value ]) : null;
|
||||
|
||||
if (values !== null && values.indexOf(ctx.value) !== -1)
|
||||
unique = false;
|
||||
});
|
||||
|
||||
if (!unique)
|
||||
return this.assert(false, _('unique value'));
|
||||
|
||||
if (typeof(subvalidator) === 'function')
|
||||
return this.apply(subvalidator, null, subargs);
|
||||
|
||||
return this.assert(true);
|
||||
},
|
||||
|
||||
hexstring: function() {
|
||||
return this.assert(this.value.match(/^([a-f0-9][a-f0-9]|[A-F0-9][A-F0-9])+$/),
|
||||
_('hexadecimal encoded value'));
|
||||
},
|
||||
|
||||
string: function() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ValidatorFactory;
|
|
@ -1,250 +1 @@
|
|||
/*
|
||||
* xhr.js - XMLHttpRequest helper class
|
||||
* (c) 2008-2018 Jo-Philipp Wich <jo@mein.io>
|
||||
*/
|
||||
|
||||
XHR.prototype = {
|
||||
_encode: function(obj) {
|
||||
obj = obj ? obj : { };
|
||||
obj['_'] = Math.random();
|
||||
|
||||
if (typeof obj == 'object') {
|
||||
var code = '';
|
||||
var self = this;
|
||||
|
||||
for (var k in obj)
|
||||
code += (code ? '&' : '') +
|
||||
k + '=' + encodeURIComponent(obj[k]);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
|
||||
_response: function(callback, ts) {
|
||||
if (this._xmlHttp.readyState !== 4)
|
||||
return;
|
||||
|
||||
var status = this._xmlHttp.status,
|
||||
login = this._xmlHttp.getResponseHeader("X-LuCI-Login-Required"),
|
||||
type = this._xmlHttp.getResponseHeader("Content-Type"),
|
||||
json = null;
|
||||
|
||||
if (status === 403 && login === 'yes') {
|
||||
XHR.halt();
|
||||
|
||||
showModal(_('Session expired'), [
|
||||
E('div', { class: 'alert-message warning' },
|
||||
_('A new login is required since the authentication session expired.')),
|
||||
E('div', { class: 'right' },
|
||||
E('div', {
|
||||
class: 'btn primary',
|
||||
click: function() {
|
||||
var loc = window.location;
|
||||
window.location = loc.protocol + '//' + loc.host + loc.pathname + loc.search;
|
||||
}
|
||||
}, _('To login…')))
|
||||
]);
|
||||
}
|
||||
else if (type && type.toLowerCase().match(/^application\/json\b/)) {
|
||||
try {
|
||||
json = JSON.parse(this._xmlHttp.responseText);
|
||||
}
|
||||
catch(e) {
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
|
||||
callback(this._xmlHttp, json, Date.now() - ts);
|
||||
},
|
||||
|
||||
busy: function() {
|
||||
if (!this._xmlHttp)
|
||||
return false;
|
||||
|
||||
switch (this._xmlHttp.readyState)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
abort: function() {
|
||||
if (this.busy())
|
||||
this._xmlHttp.abort();
|
||||
},
|
||||
|
||||
get: function(url, data, callback, timeout) {
|
||||
this._xmlHttp = new XMLHttpRequest();
|
||||
|
||||
var xhr = this._xmlHttp,
|
||||
code = this._encode(data);
|
||||
|
||||
url = location.protocol + '//' + location.host + url;
|
||||
|
||||
if (code)
|
||||
if (url.substr(url.length-1,1) == '&')
|
||||
url += code;
|
||||
else
|
||||
url += '?' + code;
|
||||
|
||||
xhr.open('GET', url, true);
|
||||
|
||||
if (!isNaN(timeout))
|
||||
xhr.timeout = timeout;
|
||||
|
||||
xhr.onreadystatechange = this._response.bind(this, callback, Date.now());
|
||||
xhr.send(null);
|
||||
},
|
||||
|
||||
post: function(url, data, callback, timeout) {
|
||||
this._xmlHttp = new XMLHttpRequest();
|
||||
|
||||
var xhr = this._xmlHttp,
|
||||
code = this._encode(data);
|
||||
|
||||
xhr.open('POST', url, true);
|
||||
|
||||
if (!isNaN(timeout))
|
||||
xhr.timeout = timeout;
|
||||
|
||||
xhr.onreadystatechange = this._response.bind(this, callback, Date.now());
|
||||
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
xhr.send(code);
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
this._xmlHttp.onreadystatechange = function() {};
|
||||
this._xmlHttp.abort();
|
||||
},
|
||||
|
||||
send_form: function(form, callback, extra_values) {
|
||||
var code = '';
|
||||
|
||||
for (var i = 0; i < form.elements.length; i++) {
|
||||
var e = form.elements[i];
|
||||
|
||||
if (e.options) {
|
||||
code += (code ? '&' : '') +
|
||||
form.elements[i].name + '=' + encodeURIComponent(
|
||||
e.options[e.selectedIndex].value
|
||||
);
|
||||
}
|
||||
else if (e.length) {
|
||||
for (var j = 0; j < e.length; j++)
|
||||
if (e[j].name) {
|
||||
code += (code ? '&' : '') +
|
||||
e[j].name + '=' + encodeURIComponent(e[j].value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
code += (code ? '&' : '') +
|
||||
e.name + '=' + encodeURIComponent(e.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof extra_values == 'object')
|
||||
for (var key in extra_values)
|
||||
code += (code ? '&' : '') +
|
||||
key + '=' + encodeURIComponent(extra_values[key]);
|
||||
|
||||
return (form.method == 'get'
|
||||
? this.get(form.getAttribute('action'), code, callback)
|
||||
: this.post(form.getAttribute('action'), code, callback));
|
||||
}
|
||||
}
|
||||
|
||||
XHR.get = function(url, data, callback) {
|
||||
(new XHR()).get(url, data, callback);
|
||||
}
|
||||
|
||||
XHR.post = function(url, data, callback) {
|
||||
(new XHR()).post(url, data, callback);
|
||||
}
|
||||
|
||||
XHR.poll = function(interval, url, data, callback, post) {
|
||||
if (isNaN(interval) || interval <= 0)
|
||||
interval = L.env.pollinterval;
|
||||
|
||||
if (!XHR._q) {
|
||||
XHR._t = 0;
|
||||
XHR._q = [ ];
|
||||
XHR._r = function() {
|
||||
for (var i = 0, e = XHR._q[0]; i < XHR._q.length; e = XHR._q[++i])
|
||||
{
|
||||
if (!(XHR._t % e.interval) && !e.xhr.busy())
|
||||
e.xhr[post ? 'post' : 'get'](e.url, e.data, e.callback, e.interval * 1000 * 5 - 5);
|
||||
}
|
||||
|
||||
XHR._t++;
|
||||
};
|
||||
}
|
||||
|
||||
var e = {
|
||||
interval: interval,
|
||||
callback: callback,
|
||||
url: url,
|
||||
data: data,
|
||||
xhr: new XHR()
|
||||
};
|
||||
|
||||
XHR._q.push(e);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
XHR.stop = function(e) {
|
||||
for (var i = 0; XHR._q && XHR._q[i]; i++) {
|
||||
if (XHR._q[i] === e) {
|
||||
e.xhr.cancel();
|
||||
XHR._q.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
XHR.halt = function() {
|
||||
if (XHR._i) {
|
||||
/* show & set poll indicator */
|
||||
try {
|
||||
document.getElementById('xhr_poll_status').style.display = '';
|
||||
document.getElementById('xhr_poll_status_on').style.display = 'none';
|
||||
document.getElementById('xhr_poll_status_off').style.display = '';
|
||||
} catch(e) { }
|
||||
|
||||
window.clearInterval(XHR._i);
|
||||
XHR._i = null;
|
||||
}
|
||||
}
|
||||
|
||||
XHR.run = function() {
|
||||
if (XHR._r && !XHR._i) {
|
||||
/* show & set poll indicator */
|
||||
try {
|
||||
document.getElementById('xhr_poll_status').style.display = '';
|
||||
document.getElementById('xhr_poll_status_on').style.display = '';
|
||||
document.getElementById('xhr_poll_status_off').style.display = 'none';
|
||||
} catch(e) { }
|
||||
|
||||
/* kick first round manually to prevent one second lag when setting up
|
||||
* the poll interval */
|
||||
XHR._r();
|
||||
XHR._i = window.setInterval(XHR._r, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
XHR.running = function() {
|
||||
return !!(XHR._r && XHR._i);
|
||||
}
|
||||
|
||||
function XHR() {}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', XHR.run);
|
||||
/* replaced by luci.js */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue