return n;
};
+ this.toColor = function(str)
+ {
+ if (typeof(str) != 'string' || str.length == 0)
+ return '#CCCCCC';
+
+ if (str == 'wan')
+ return '#F09090';
+ else if (str == 'lan')
+ return '#90F090';
+
+ var i = 0, hash = 0;
+
+ while (i < str.length)
+ hash = str.charCodeAt(i++) + ((hash << 5) - hash);
+
+ var r = (hash & 0xFF) % 128;
+ var g = ((hash >> 8) & 0xFF) % 128;
+
+ var min = 0;
+ var max = 128;
+
+ if ((r + g) < 128)
+ min = 128 - r - g;
+ else
+ max = 255 - r - g;
+
+ var b = min + (((hash >> 16) & 0xFF) % (max - min));
+
+ return '#%02X%02X%02X'.format(0xFF - r, 0xFF - g, 0xFF - b);
+ };
+
+ this.parseIPv4 = function(str)
+ {
+ if ((typeof(str) != 'string' && !(str instanceof String)) ||
+ !str.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/))
+ return undefined;
+
+ var num = [ ];
+ var parts = str.split(/\./);
+
+ for (var i = 0; i < parts.length; i++)
+ {
+ var n = parseInt(parts[i], 10);
+ if (isNaN(n) || n > 255)
+ return undefined;
+
+ num.push(n);
+ }
+
+ return num;
+ };
+
+ this.parseIPv6 = function(str)
+ {
+ if ((typeof(str) != 'string' && !(str instanceof String)) ||
+ !str.match(/^[a-fA-F0-9:]+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/))
+ return undefined;
+
+ var parts = str.split(/::/);
+ if (parts.length == 0 || parts.length > 2)
+ return undefined;
+
+ var lnum = [ ];
+ if (parts[0].length > 0)
+ {
+ var left = parts[0].split(/:/);
+ for (var i = 0; i < left.length; i++)
+ {
+ var n = parseInt(left[i], 16);
+ if (isNaN(n))
+ return undefined;
+
+ lnum.push((n / 256) >> 0);
+ lnum.push(n % 256);
+ }
+ }
+
+ var rnum = [ ];
+ if (parts.length > 1 && parts[1].length > 0)
+ {
+ var right = parts[1].split(/:/);
+
+ for (var i = 0; i < right.length; i++)
+ {
+ if (right[i].indexOf('.') > 0)
+ {
+ var addr = L.parseIPv4(right[i]);
+ if (!addr)
+ return undefined;
+
+ rnum.push.apply(rnum, addr);
+ continue;
+ }
+
+ var n = parseInt(right[i], 16);
+ if (isNaN(n))
+ return undefined;
+
+ rnum.push((n / 256) >> 0);
+ rnum.push(n % 256);
+ }
+ }
+
+ if (rnum.length > 0 && (lnum.length + rnum.length) > 15)
+ return undefined;
+
+ var num = [ ];
+
+ num.push.apply(num, lnum);
+
+ for (var i = 0; i < (16 - lnum.length - rnum.length); i++)
+ num.push(0);
+
+ num.push.apply(num, rnum);
+
+ if (num.length > 16)
+ return undefined;
+
+ return num;
+ };
+
+ this.isNetmask = function(addr)
+ {
+ if (!$.isArray(addr))
+ return false;
+
+ var c;
+
+ for (c = 0; (c < addr.length) && (addr[c] == 255); c++);
+
+ if (c == addr.length)
+ return true;
+
+ if ((addr[c] == 254) || (addr[c] == 252) || (addr[c] == 248) ||
+ (addr[c] == 240) || (addr[c] == 224) || (addr[c] == 192) ||
+ (addr[c] == 128) || (addr[c] == 0))
+ {
+ for (c++; (c < addr.length) && (addr[c] == 0); c++);
+
+ if (c == addr.length)
+ return true;
+ }
+
+ return false;
+ };
+
this.globals = {
timeout: 15000,
resource: '/luci2',
init: function()
{
this.state = {
- newid: 0,
+ newidx: 0,
values: { },
creates: { },
changes: { },
params: [ 'config', 'section', 'options' ]
}),
+ _newid: function(conf)
+ {
+ var v = this.state.values;
+ var n = this.state.creates;
+ var sid;
+
+ do {
+ sid = "new%06x".format(Math.random() * 0xFFFFFF);
+ } while ((n[conf] && n[conf][sid]) || (v[conf] && v[conf][sid]));
+
+ return sid;
+ },
+
load: function(packages)
{
var self = this;
L.rpc.batch();
for (var i = 0; i < packages.length; i++)
- if (!seen[packages[i]])
+ if (!seen[packages[i]] && !self.state.values[packages[i]])
{
pkgs.push(packages[i]);
seen[packages[i]] = true;
add: function(conf, type, name)
{
- var c = this.state.creates;
- var s = '.new.%d'.format(this.state.newid++);
+ var n = this.state.creates;
+ var sid = this._newid(conf);
- if (!c[conf])
- c[conf] = { };
+ if (!n[conf])
+ n[conf] = { };
- c[conf][s] = {
+ n[conf][sid] = {
'.type': type,
- '.name': s,
+ '.name': sid,
'.create': name,
'.anonymous': !name,
- '.index': 1000 + this.state.newid
+ '.index': 1000 + this.state.newidx++
};
- return s;
+ return sid;
},
remove: function(conf, sid)
var d = this.state.deletes;
/* requested deletion of a just created section */
- if (sid.indexOf('.new.') == 0)
+ if (n[conf] && n[conf][sid])
{
- if (n[conf])
- delete n[conf][sid];
+ delete n[conf][sid];
}
else
{
return undefined;
/* requested option in a just created section */
- if (sid.indexOf('.new.') == 0)
+ if (n[conf] && n[conf][sid])
{
if (!n[conf])
return undefined;
set: function(conf, sid, opt, val)
{
+ var v = this.state.values;
var n = this.state.creates;
var c = this.state.changes;
var d = this.state.deletes;
opt.charAt(0) == '.')
return;
- if (sid.indexOf('.new.') == 0)
+ if (n[conf] && n[conf][sid])
{
- if (n[conf] && n[conf][sid])
- {
- if (typeof(val) != 'undefined')
- n[conf][sid][opt] = val;
- else
- delete n[conf][sid][opt];
- }
+ if (typeof(val) != 'undefined')
+ n[conf][sid][opt] = val;
+ else
+ delete n[conf][sid][opt];
}
else if (typeof(val) != 'undefined')
{
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] = { };
}
else
{
+ /* only delete in existing sections */
+ if (!v[conf] || !v[conf][sid])
+ return;
+
if (!d[conf])
d[conf] = { };
return this.set(conf, sid, opt, undefined);
},
+ get_first: function(conf, type, opt)
+ {
+ var sid = undefined;
+
+ L.uci.sections(conf, type, function(s) {
+ if (typeof(sid) != 'string')
+ sid = s['.name'];
+ });
+
+ return this.get(conf, sid, opt);
+ },
+
+ set_first: function(conf, type, opt, val)
+ {
+ var sid = undefined;
+
+ L.uci.sections(conf, type, function(s) {
+ if (typeof(sid) != 'string')
+ sid = s['.name'];
+ });
+
+ return this.set(conf, sid, opt, val);
+ },
+
+ unset_first: function(conf, type, opt)
+ {
+ return this.set_first(conf, type, opt, undefined);
+ },
+
_reload: function()
{
var pkgs = [ ];
{
var o = [ ];
- if (n && n[c])
+ if (n[c])
for (var s in n[c])
o.push(n[c][s]);
{
L.rpc.batch();
+ var v = this.state.values;
+ var n = this.state.creates;
+ var c = this.state.changes;
+ var d = this.state.deletes;
+
var self = this;
var snew = [ ];
+ var pkgs = { };
- if (self.state.creates)
- for (var c in self.state.creates)
- for (var s in self.state.creates[c])
+ if (n)
+ for (var conf in n)
+ {
+ for (var sid in n[conf])
{
var r = {
- config: c,
+ config: conf,
values: { }
};
- for (var k in self.state.creates[c][s])
+ for (var k in n[conf][sid])
{
if (k == '.type')
- r.type = self.state.creates[c][s][k];
+ r.type = n[conf][sid][k];
else if (k == '.create')
- r.name = self.state.creates[c][s][k];
+ r.name = n[conf][sid][k];
else if (k.charAt(0) != '.')
- r.values[k] = self.state.creates[c][s][k];
+ r.values[k] = n[conf][sid][k];
}
- snew.push(self.state.creates[c][s]);
+ snew.push(n[conf][sid]);
self._add(r.config, r.type, r.name, r.values);
}
- if (self.state.changes)
- for (var c in self.state.changes)
- for (var s in self.state.changes[c])
- self._set(c, s, self.state.changes[c][s]);
+ pkgs[conf] = true;
+ }
+
+ if (c)
+ for (var conf in c)
+ {
+ for (var sid in c[conf])
+ self._set(conf, sid, c[conf][sid]);
+
+ pkgs[conf] = true;
+ }
- if (self.state.deletes)
- for (var c in self.state.deletes)
- for (var s in self.state.deletes[c])
+ if (d)
+ for (var conf in d)
+ {
+ for (var sid in d[conf])
{
- var o = self.state.deletes[c][s];
- self._delete(c, s, (o === true) ? undefined : o);
+ var o = d[conf][sid];
+ self._delete(conf, sid, (o === true) ? undefined : o);
}
+ pkgs[conf] = true;
+ }
+
return L.rpc.flush().then(function(responses) {
/*
array "snew" holds references to the created uci sections,
snew[i]['.name'] = responses[i];
return self._reorder();
+ }).then(function() {
+ pkgs = L.toArray(pkgs);
+
+ self.unload(pkgs);
+
+ return self.load(pkgs);
});
},
_fetch_protocols: function()
{
var self = L.NetworkModel;
- var deferreds = [ ];
+ var deferreds = [
+ self._fetch_protocol('none')
+ ];
for (var proto in self._cache.protolist)
deferreds.push(self._fetch_protocol(proto));
return dev.getTrafficHistory();
},
+ renderBadge: function()
+ {
+ var badge = $('<span />')
+ .addClass('badge')
+ .text('%s: '.format(this.name()));
+
+ var dev = this.getDevice();
+ var subdevs = this.getSubdevices();
+
+ if (subdevs.length)
+ for (var j = 0; j < subdevs.length; j++)
+ badge.append($('<img />')
+ .attr('src', subdevs[j].icon())
+ .attr('title', '%s (%s)'.format(subdevs[j].description(), subdevs[j].name() || '?')));
+ else if (dev)
+ badge.append($('<img />')
+ .attr('src', dev.icon())
+ .attr('title', '%s (%s)'.format(dev.description(), dev.name() || '?')));
+ else
+ badge.append($('<em />').text(L.tr('(No devices attached)')));
+
+ return badge;
+ },
+
setDevices: function(devs)
{
var dev = this.getPhysdev();
appendTo: function(id) {
return $(id).append(this.render());
+ },
+
+ on: function(evname, evfunc)
+ {
+ var evnames = L.toArray(evname);
+
+ if (!this.events)
+ this.events = { };
+
+ for (var i = 0; i < evnames.length; i++)
+ this.events[evnames[i]] = evfunc;
+
+ return this;
+ },
+
+ trigger: function(evname, evdata)
+ {
+ if (this.events)
+ {
+ var evnames = L.toArray(evname);
+
+ for (var i = 0; i < evnames.length; i++)
+ if (this.events[evnames[i]])
+ this.events[evnames[i]].call(this, evdata);
+ }
+
+ return this;
}
});
'ipaddr': function()
{
- if (validation.types['ip4addr'].apply(this) ||
- validation.types['ip6addr'].apply(this))
+ if (L.parseIPv4(this) || L.parseIPv6(this))
return true;
validation.i18n('Must be a valid IP address');
'ip4addr': function()
{
- if (this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\S+))?$/))
- {
- if ((RegExp.$1 >= 0) && (RegExp.$1 <= 255) &&
- (RegExp.$2 >= 0) && (RegExp.$2 <= 255) &&
- (RegExp.$3 >= 0) && (RegExp.$3 <= 255) &&
- (RegExp.$4 >= 0) && (RegExp.$4 <= 255) &&
- ((RegExp.$6.indexOf('.') < 0)
- ? ((RegExp.$6 >= 0) && (RegExp.$6 <= 32))
- : (validation.types['ip4addr'].apply(RegExp.$6))))
- return true;
- }
+ if (L.parseIPv4(this))
+ return true;
validation.i18n('Must be a valid IPv4 address');
return false;
'ip6addr': function()
{
- if (this.match(/^([a-fA-F0-9:.]+)(\/(\d+))?$/))
- {
- if (!RegExp.$2 || ((RegExp.$3 >= 0) && (RegExp.$3 <= 128)))
- {
- var addr = RegExp.$1;
+ if (L.parseIPv6(this))
+ return true;
- if (addr == '::')
- {
- return true;
- }
+ validation.i18n('Must be a valid IPv6 address');
+ return false;
+ },
- if (addr.indexOf('.') > 0)
- {
- var off = addr.lastIndexOf(':');
+ 'netmask4': function()
+ {
+ if (L.isNetmask(L.parseIPv4(this)))
+ return true;
- if (!(off && validation.types['ip4addr'].apply(addr.substr(off+1))))
- {
- validation.i18n('Must be a valid IPv6 address');
- return false;
- }
+ validation.i18n('Must be a valid IPv4 netmask');
+ return false;
+ },
- addr = addr.substr(0, off) + ':0:0';
- }
+ 'netmask6': function()
+ {
+ if (L.isNetmask(L.parseIPv6(this)))
+ return true;
- if (addr.indexOf('::') >= 0)
- {
- var colons = 0;
- var fill = '0';
+ validation.i18n('Must be a valid IPv6 netmask6');
+ return false;
+ },
- for (var i = 1; i < (addr.length-1); i++)
- if (addr.charAt(i) == ':')
- colons++;
+ 'cidr4': function()
+ {
+ if (this.match(/^([0-9.]+)\/(\d{1,2})$/))
+ if (RegExp.$2 <= 32 && L.parseIPv4(RegExp.$1))
+ return true;
- if (colons > 7)
- {
- validation.i18n('Must be a valid IPv6 address');
- return false;
- }
+ validation.i18n('Must be a valid IPv4 prefix');
+ return false;
+ },
- for (var i = 0; i < (7 - colons); i++)
- fill += ':0';
+ 'cidr6': function()
+ {
+ if (this.match(/^([a-fA-F0-9:.]+)\/(\d{1,3})$/))
+ if (RegExp.$2 <= 128 && L.parseIPv6(RegExp.$1))
+ return true;
- if (addr.match(/^(.*?)::(.*?)$/))
- addr = (RegExp.$1 ? RegExp.$1 + ':' : '') + fill +
- (RegExp.$2 ? ':' + RegExp.$2 : '');
- }
+ validation.i18n('Must be a valid IPv6 prefix');
+ return false;
+ },
- if (addr.match(/^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$/) != null)
- return true;
+ 'ipmask4': function()
+ {
+ if (this.match(/^([0-9.]+)\/([0-9.]+)$/))
+ {
+ var addr = RegExp.$1, mask = RegExp.$2;
+ if (L.parseIPv4(addr) && L.isNetmask(L.parseIPv4(mask)))
+ return true;
+ }
- validation.i18n('Must be a valid IPv6 address');
- return false;
- }
+ validation.i18n('Must be a valid IPv4 address/netmask pair');
+ return false;
+ },
+
+ 'ipmask6': function()
+ {
+ if (this.match(/^([a-fA-F0-9:.]+)\/([a-fA-F0-9:.]+)$/))
+ {
+ var addr = RegExp.$1, mask = RegExp.$2;
+ if (L.parseIPv6(addr) && L.isNetmask(L.parseIPv6(mask)))
+ return true;
}
- validation.i18n('Must be a valid IPv6 address');
+ validation.i18n('Must be a valid IPv6 address/netmask pair');
return false;
},
this.instance = { };
this.dependencies = [ ];
this.rdependency = { };
- this.events = { };
this.options = L.defaults(options, {
placeholder: '',
opt: this.options.optional
};
- for (var evname in this.events)
- elem.on(evname, evdata, this.events[evname]);
+ if (this.events)
+ for (var evname in this.events)
+ elem.on(evname, evdata, this.events[evname]);
if (typeof(this.options.datatype) == 'undefined' && $.isEmptyObject(this.rdependency))
return elem;
}
return false;
- },
-
- on: function(evname, evfunc)
- {
- this.events[evname] = evfunc;
- return this;
}
});
for (var i = 0; i < interfaces.length; i++)
{
var iface = interfaces[i];
- var badge = $('<span />')
- .addClass('badge')
- .text('%s: '.format(iface.name()));
-
- var dev = iface.getDevice();
- var subdevs = iface.getSubdevices();
-
- if (subdevs.length)
- for (var j = 0; j < subdevs.length; j++)
- badge.append(this._device_icon(subdevs[j]));
- else if (dev)
- badge.append(this._device_icon(dev));
- else
- badge.append($('<em />').text(L.tr('(No devices attached)')));
$('<li />')
.append($('<label />')
.addClass(itype + ' inline')
- .append($('<input />')
+ .append(this.validator(sid, $('<input />')
.attr('name', itype + id)
.attr('type', itype)
.attr('value', iface.name())
- .prop('checked', !!check[iface.name()]))
- .append(badge))
+ .prop('checked', !!check[iface.name()]), true))
+ .append(iface.renderBadge()))
.appendTo(ul);
}
add: function(name)
{
- this.map.add(this.map.uci_package, this.uci_type, name);
+ return this.map.add(this.map.uci_package, this.uci_type, name);
},
remove: function(sid)
{
- this.map.remove(this.map.uci_package, sid);
+ return this.map.remove(this.map.uci_package, sid);
},
_ev_add: function(ev)
self.active_panel = -1;
self.map.save();
- self.add(name);
+
+ ev.data.sid = self.add(name);
+ ev.data.type = self.uci_type;
+ ev.data.name = name;
+
+ self.trigger('add', ev);
+
self.map.redraw();
L.ui.restoreScrollTop();
L.ui.saveScrollTop();
+ self.trigger('remove', ev);
+
self.map.save();
self.remove(sid);
self.map.redraw();
self.active_tab = parseInt(ev.target.getAttribute('data-luci2-tab-index'));
},
+ _ev_apply: function(ev)
+ {
+ var self = ev.data.self;
+
+ self.trigger('apply', ev);
+ },
+
+ _ev_save: function(ev)
+ {
+ var self = ev.data.self;
+
+ self.send().then(function() {
+ self.trigger('save', ev);
+ });
+ },
+
+ _ev_reset: function(ev)
+ {
+ var self = ev.data.self;
+
+ self.trigger('reset', ev);
+ self.reset();
+ },
+
_render_tab_head: function(tab_index)
{
var section = this.sections[tab_index];
_render_footer: function()
{
+ var evdata = {
+ self: this
+ };
+
return $('<div />')
.addClass('panel panel-default panel-body text-right')
.append($('<div />')
.addClass('btn-group')
.append(L.ui.button(L.tr('Save & Apply'), 'primary')
- .click({ self: this }, function(ev) { }))
+ .click(evdata, this._ev_apply))
.append(L.ui.button(L.tr('Save'), 'default')
- .click({ self: this }, function(ev) { ev.data.self.send(); }))
+ .click(evdata, this._ev_save))
.append(L.ui.button(L.tr('Reset'), 'default')
- .click({ self: this }, function(ev) { ev.data.self.insertInto(ev.data.self.target); })));
+ .click(evdata, this._ev_reset)));
},
render: function()
});
},
+ revert: function()
+ {
+ var packages = { };
+
+ for (var i = 0; i < this.sections.length; i++)
+ this.sections[i].ucipackages(packages);
+
+ packages[this.uci_package] = true;
+
+ L.uci.unload(L.toArray(packages));
+ },
+
+ reset: function()
+ {
+ var self = this;
+
+ self.revert();
+
+ return self.insertInto(self.target);
+ },
+
insertInto: function(id)
{
var self = this;
});
this.cbi.Modal = this.cbi.Map.extend({
+ _ev_apply: function(ev)
+ {
+ var self = ev.data.self;
+
+ self.trigger('apply', ev);
+ },
+
+ _ev_save: function(ev)
+ {
+ var self = ev.data.self;
+
+ self.send().then(function() {
+ self.trigger('save', ev);
+ self.close();
+ });
+ },
+
+ _ev_reset: function(ev)
+ {
+ var self = ev.data.self;
+
+ self.trigger('close', ev);
+ self.revert();
+ self.close();
+ },
+
_render_footer: function()
{
+ var evdata = {
+ self: this
+ };
+
return $('<div />')
.addClass('btn-group')
.append(L.ui.button(L.tr('Save & Apply'), 'primary')
- .click({ self: this }, function(ev) { }))
+ .click(evdata, this._ev_apply))
.append(L.ui.button(L.tr('Save'), 'default')
- .click({ self: this }, function(ev) { ev.data.self.send(); }))
+ .click(evdata, this._ev_save))
.append(L.ui.button(L.tr('Cancel'), 'default')
- .click({ self: this }, function(ev) { L.ui.dialog(false); }));
+ .click(evdata, this._ev_reset));
},
render: function()
L.ui.loading(false);
});
+ },
+
+ close: function()
+ {
+ L.ui.dialog(false);
}
});
};