}
};
- this.uci = {
+ this.UCIContext = Class.extend({
- writable: function()
+ init: function()
{
- return _luci2.session.access('ubus', 'uci', 'commit');
+ this.state = {
+ newid: 0,
+ values: { },
+ creates: { },
+ changes: { },
+ deletes: { },
+ reorder: { }
+ };
},
- add: _luci2.rpc.declare({
+ _load: _luci2.rpc.declare({
+ object: 'uci',
+ method: 'get',
+ params: [ 'config' ],
+ expect: { values: { } }
+ }),
+
+ _order: _luci2.rpc.declare({
+ object: 'uci',
+ method: 'order',
+ params: [ 'config', 'sections' ]
+ }),
+
+ _add: _luci2.rpc.declare({
object: 'uci',
method: 'add',
params: [ 'config', 'type', 'name', 'values' ],
expect: { section: '' }
}),
- apply: function()
- {
-
- },
-
- configs: _luci2.rpc.declare({
+ _set: _luci2.rpc.declare({
object: 'uci',
- method: 'configs',
- expect: { configs: [ ] }
+ method: 'set',
+ params: [ 'config', 'section', 'values' ]
}),
- _changes: _luci2.rpc.declare({
+ _delete: _luci2.rpc.declare({
object: 'uci',
- method: 'changes',
- params: [ 'config' ],
- expect: { changes: [ ] }
+ method: 'delete',
+ params: [ 'config', 'section', 'options' ]
}),
- changes: function(config)
+ load: function(packages)
{
- if (typeof(config) == 'string')
- return this._changes(config);
+ var self = this;
+ var seen = { };
+ var pkgs = [ ];
- var configlist;
- return this.configs().then(function(configs) {
- _luci2.rpc.batch();
- configlist = configs;
+ if (!$.isArray(packages))
+ packages = [ packages ];
- for (var i = 0; i < configs.length; i++)
- _luci2.uci._changes(configs[i]);
+ _luci2.rpc.batch();
- return _luci2.rpc.flush();
- }).then(function(changes) {
- var rv = { };
+ for (var i = 0; i < packages.length; i++)
+ if (!seen[packages[i]])
+ {
+ pkgs.push(packages[i]);
+ seen[packages[i]] = true;
+ self._load(packages[i]);
+ }
- for (var i = 0; i < configlist.length; i++)
- if (changes[i].length)
- rv[configlist[i]] = changes[i];
+ return _luci2.rpc.flush().then(function(responses) {
+ for (var i = 0; i < responses.length; i++)
+ self.state.values[pkgs[i]] = responses[i];
- return rv;
+ return pkgs;
});
},
- commit: _luci2.rpc.declare({
- object: 'uci',
- method: 'commit',
- params: [ 'config' ]
- }),
-
- _delete_one: _luci2.rpc.declare({
- object: 'uci',
- method: 'delete',
- params: [ 'config', 'section', 'option' ]
- }),
+ unload: function(packages)
+ {
+ if (!$.isArray(packages))
+ packages = [ packages ];
- _delete_multiple: _luci2.rpc.declare({
- object: 'uci',
- method: 'delete',
- params: [ 'config', 'section', 'options' ]
- }),
+ 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': function(config, section, option)
+ add: function(conf, type, name)
{
- if ($.isArray(option))
- return this._delete_multiple(config, section, option);
- else
- return this._delete_one(config, section, option);
- },
+ var c = this.state.creates;
+ var s = '.new.%d'.format(this.state.newid++);
- delete_all: _luci2.rpc.declare({
- object: 'uci',
- method: 'delete',
- params: [ 'config', 'type', 'match' ]
- }),
+ if (!c[conf])
+ c[conf] = { };
- _foreach: _luci2.rpc.declare({
- object: 'uci',
- method: 'get',
- params: [ 'config', 'type' ],
- expect: { values: { } }
- }),
+ c[conf][s] = {
+ '.type': type,
+ '.name': s,
+ '.create': name,
+ '.anonymous': !name,
+ '.index': 1000 + this.state.newid
+ };
- foreach: function(config, type, cb)
- {
- return this._foreach(config, type).then(function(sections) {
- for (var s in sections)
- cb(sections[s]);
- });
+ return s;
},
- get: _luci2.rpc.declare({
- object: 'uci',
- method: 'get',
- params: [ 'config', 'section', 'option' ],
- expect: { '': { } },
- filter: function(data, params) {
- if (typeof(params.option) == 'undefined')
- return data.values ? data.values['.type'] : undefined;
- else
- return data.value;
+ remove: function(conf, sid)
+ {
+ var n = this.state.creates;
+ var c = this.state.changes;
+ var d = this.state.deletes;
+
+ /* requested deletion of a just created section */
+ if (sid.indexOf('.new.') == 0)
+ {
+ if (n[conf])
+ delete n[conf][sid];
}
- }),
+ else
+ {
+ if (c[conf])
+ delete c[conf][sid];
- get_all: _luci2.rpc.declare({
- object: 'uci',
- method: 'get',
- params: [ 'config', 'section' ],
- expect: { values: { } },
- filter: function(data, params) {
- if (typeof(params.section) == 'string')
- data['.section'] = params.section;
- else if (typeof(params.config) == 'string')
- data['.package'] = params.config;
- return data;
+ if (!d[conf])
+ d[conf] = { };
+
+ d[conf][sid] = true;
}
- }),
+ },
- get_first: function(config, type, option)
+ sections: function(conf, type, cb)
{
- return this._foreach(config, type).then(function(sections) {
- for (var s in sections)
- {
- var val = (typeof(option) == 'string') ? sections[s][option] : sections[s]['.name'];
+ var sa = [ ];
+ var v = this.state.values[conf];
+ var n = this.state.creates[conf];
+ var c = this.state.changes[conf];
+ var d = this.state.deletes[conf];
- if (typeof(val) != 'undefined')
- return val;
- }
+ if (!v)
+ return sa;
- return undefined;
- });
- },
+ for (var s in v)
+ if (!d || d[s] !== true)
+ if (!type || v[s]['.type'] == type)
+ sa.push($.extend({ }, v[s], c ? c[s] : undefined));
- section: _luci2.rpc.declare({
- object: 'uci',
- method: 'add',
- params: [ 'config', 'type', 'name', 'values' ],
- expect: { section: '' }
- }),
+ if (n)
+ for (var s in n)
+ if (!type || n[s]['.type'] == type)
+ sa.push(n[s]);
- _set: _luci2.rpc.declare({
- object: 'uci',
- method: 'set',
- params: [ 'config', 'section', 'values' ]
- }),
+ sa.sort(function(a, b) {
+ return a['.index'] - b['.index'];
+ });
- set: function(config, section, option, value)
- {
- if (typeof(value) == 'undefined' && typeof(option) == 'string')
- return this.section(config, section, option); /* option -> type */
- else if ($.isPlainObject(option))
- return this._set(config, section, option); /* option -> values */
+ for (var i = 0; i < sa.length; i++)
+ sa[i]['.index'] = i;
- var values = { };
- values[option] = value;
+ if (typeof(cb) == 'function')
+ for (var i = 0; i < sa.length; i++)
+ cb.call(this, sa[i], sa[i]['.name']);
- return this._set(config, section, values);
+ return sa;
},
- order: _luci2.rpc.declare({
- object: 'uci',
- method: 'order',
- params: [ 'config', 'sections' ]
- })
- };
-
- this.network = {
- listNetworkNames: function() {
- return _luci2.rpc.list('network.interface.*').then(function(list) {
- var names = [ ];
- for (var name in list)
- if (name != 'network.interface.loopback')
- names.push(name.substring(18));
- names.sort();
- return names;
- });
- },
+ get: function(conf, sid, opt)
+ {
+ var v = this.state.values;
+ var n = this.state.creates;
+ var c = this.state.changes;
+ var d = this.state.deletes;
- listDeviceNames: _luci2.rpc.declare({
- object: 'network.device',
- method: 'status',
- expect: { '': { } },
- filter: function(data) {
- var names = [ ];
- for (var name in data)
- if (name != 'lo')
- names.push(name);
- names.sort();
- return names;
- }
- }),
+ if (typeof(sid) == 'undefined')
+ return undefined;
- getNetworkStatus: function()
- {
- var nets = [ ];
- var devs = { };
+ /* requested option in a just created section */
+ if (sid.indexOf('.new.') == 0)
+ {
+ if (!n[conf])
+ return undefined;
- return this.listNetworkNames().then(function(names) {
- _luci2.rpc.batch();
+ if (typeof(opt) == 'undefined')
+ return n[conf][sid];
- for (var i = 0; i < names.length; i++)
- _luci2.network.getInterfaceStatus(names[i]);
+ return n[conf][sid][opt];
+ }
- return _luci2.rpc.flush();
- }).then(function(networks) {
- for (var i = 0; i < networks.length; i++)
+ /* requested an option value */
+ if (typeof(opt) != 'undefined')
+ {
+ /* check whether option was deleted */
+ if (d[conf] && d[conf][sid])
{
- var net = nets[i] = networks[i];
- var dev = net.l3_device || net.l2_device;
- if (dev)
- net.device = devs[dev] || (devs[dev] = { });
- }
+ if (d[conf][sid] === true)
+ return undefined;
- _luci2.rpc.batch();
+ for (var i = 0; i < d[conf][sid].length; i++)
+ if (d[conf][sid][i] == opt)
+ return undefined;
+ }
- for (var dev in devs)
- _luci2.network.getDeviceStatus(dev);
+ /* check whether option was changed */
+ if (c[conf] && c[conf][sid] && typeof(c[conf][sid][opt]) != 'undefined')
+ return c[conf][sid][opt];
- return _luci2.rpc.flush();
- }).then(function(devices) {
- _luci2.rpc.batch();
+ /* return base value */
+ if (v[conf] && v[conf][sid])
+ return v[conf][sid][opt];
- for (var i = 0; i < devices.length; i++)
- {
- var brm = devices[i]['bridge-members'];
- delete devices[i]['bridge-members'];
+ return undefined;
+ }
- $.extend(devs[devices[i]['device']], devices[i]);
+ /* requested an entire section */
+ if (v[conf])
+ return v[conf][sid];
- if (!brm)
- continue;
+ return undefined;
+ },
- devs[devices[i]['device']].subdevices = [ ];
+ set: function(conf, sid, opt, val)
+ {
+ var n = this.state.creates;
+ var c = this.state.changes;
+ var d = this.state.deletes;
- for (var j = 0; j < brm.length; j++)
- {
- if (!devs[brm[j]])
- {
- devs[brm[j]] = { };
- _luci2.network.getDeviceStatus(brm[j]);
- }
+ if (typeof(sid) == 'undefined' ||
+ typeof(opt) == 'undefined' ||
+ opt.charAt(0) == '.')
+ return;
- devs[devices[i]['device']].subdevices[j] = devs[brm[j]];
- }
+ if (sid.indexOf('.new.') == 0)
+ {
+ if (n[conf] && n[conf][sid])
+ {
+ if (typeof(val) != 'undefined')
+ n[conf][sid][opt] = val;
+ else
+ delete n[conf][sid][opt];
}
+ }
+ else if (typeof(val) != 'undefined')
+ {
+ /* do not set within deleted section */
+ if (d[conf] && d[conf][sid] === true)
+ return;
- return _luci2.rpc.flush();
- }).then(function(subdevices) {
- for (var i = 0; i < subdevices.length; i++)
- $.extend(devs[subdevices[i]['device']], subdevices[i]);
+ if (!c[conf])
+ c[conf] = { };
- _luci2.rpc.batch();
+ if (!c[conf][sid])
+ c[conf][sid] = { };
- for (var dev in devs)
- _luci2.wireless.getDeviceStatus(dev);
+ /* undelete option */
+ if (d[conf] && d[conf][sid])
+ d[conf][sid] = _luci2.filterArray(d[conf][sid], opt);
- return _luci2.rpc.flush();
- }).then(function(wifidevices) {
- for (var i = 0; i < wifidevices.length; i++)
- if (wifidevices[i])
- devs[wifidevices[i]['device']].wireless = wifidevices[i];
+ c[conf][sid][opt] = val;
+ }
+ else
+ {
+ if (!d[conf])
+ d[conf] = { };
- nets.sort(function(a, b) {
- if (a['interface'] < b['interface'])
- return -1;
- else if (a['interface'] > b['interface'])
- return 1;
- else
- return 0;
- });
+ if (!d[conf][sid])
+ d[conf][sid] = [ ];
- return nets;
- });
+ if (d[conf][sid] !== true)
+ d[conf][sid].push(opt);
+ }
},
- findWanInterfaces: function(cb)
+ unset: function(conf, sid, opt)
{
- return this.listNetworkNames().then(function(names) {
- _luci2.rpc.batch();
-
- for (var i = 0; i < names.length; i++)
- _luci2.network.getInterfaceStatus(names[i]);
-
- return _luci2.rpc.flush();
- }).then(function(interfaces) {
- var rv = [ undefined, undefined ];
-
- for (var i = 0; i < interfaces.length; i++)
- {
- if (!interfaces[i].route)
- continue;
+ return this.set(conf, sid, opt, undefined);
+ },
- for (var j = 0; j < interfaces[i].route.length; j++)
- {
- var rt = interfaces[i].route[j];
+ _reload: function()
+ {
+ var pkgs = [ ];
- if (typeof(rt.table) != 'undefined')
- continue;
+ for (var pkg in this.state.values)
+ pkgs.push(pkg);
- if (rt.target == '0.0.0.0' && rt.mask == 0)
- rv[0] = interfaces[i];
- else if (rt.target == '::' && rt.mask == 0)
- rv[1] = interfaces[i];
- }
- }
+ this.init();
- return rv;
- });
+ return this.load(pkgs);
},
- getDHCPLeases: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'dhcp_leases',
- expect: { leases: [ ] }
- }),
+ _reorder: function()
+ {
+ var v = this.state.values;
+ var n = this.state.creates;
+ var r = this.state.reorder;
- getDHCPv6Leases: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'dhcp6_leases',
- expect: { leases: [ ] }
- }),
+ if ($.isEmptyObject(r))
+ return _luci2.deferrable();
- getRoutes: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'routes',
- expect: { routes: [ ] }
- }),
+ _luci2.rpc.batch();
- getIPv6Routes: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'routes',
- expect: { routes: [ ] }
- }),
+ /*
+ 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 = [ ];
- getARPTable: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'arp_table',
- expect: { entries: [ ] }
- }),
+ if (n && n[c])
+ for (var s in n[c])
+ o.push(n[c][s]);
- getInterfaceStatus: _luci2.rpc.declare({
- object: 'network.interface',
- method: 'status',
- params: [ 'interface' ],
- expect: { '': { } },
- filter: function(data, params) {
- data['interface'] = params['interface'];
- data['l2_device'] = data['device'];
- delete data['device'];
- return data;
- }
- }),
+ for (var s in v[c])
+ o.push(v[c][s]);
- getDeviceStatus: _luci2.rpc.declare({
- object: 'network.device',
- method: 'status',
- params: [ 'name' ],
- expect: { '': { } },
- filter: function(data, params) {
- data['device'] = params['name'];
- return data;
+ 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']);
+
+ this._order(c, sids);
+ }
}
- }),
- getConntrackCount: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'conntrack_count',
- expect: { '': { count: 0, limit: 0 } }
- }),
+ this.state.reorder = { };
+ return _luci2.rpc.flush();
+ },
- listSwitchNames: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'switch_list',
- expect: { switches: [ ] }
- }),
+ swap: function(conf, sid1, sid2)
+ {
+ var s1 = this.get(conf, sid1);
+ var s2 = this.get(conf, sid2);
+ var n1 = s1 ? s1['.index'] : NaN;
+ var n2 = s2 ? s2['.index'] : NaN;
- getSwitchInfo: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'switch_info',
- params: [ 'switch' ],
- expect: { info: { } },
- filter: function(data, params) {
- data['attrs'] = data['switch'];
- data['vlan_attrs'] = data['vlan'];
- data['port_attrs'] = data['port'];
- data['switch'] = params['switch'];
+ if (isNaN(n1) || isNaN(n2))
+ return false;
- delete data.vlan;
- delete data.port;
+ s1['.index'] = n2;
+ s2['.index'] = n1;
- return data;
- }
- }),
+ this.state.reorder[conf] = true;
- getSwitchStatus: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'switch_status',
- params: [ 'switch' ],
- expect: { ports: [ ] }
- }),
+ return true;
+ },
+ save: function()
+ {
+ _luci2.rpc.batch();
- runPing: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'ping',
- params: [ 'data' ],
- expect: { '': { code: -1 } }
- }),
+ var self = this;
+ var snew = [ ];
- runPing6: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'ping6',
- params: [ 'data' ],
- expect: { '': { code: -1 } }
- }),
+ if (self.state.creates)
+ for (var c in self.state.creates)
+ for (var s in self.state.creates[c])
+ {
+ var r = {
+ config: c,
+ values: { }
+ };
- runTraceroute: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'traceroute',
- params: [ 'data' ],
- expect: { '': { code: -1 } }
- }),
+ for (var k in self.state.creates[c][s])
+ {
+ if (k == '.type')
+ r.type = self.state.creates[c][s][k];
+ else if (k == '.create')
+ r.name = self.state.creates[c][s][k];
+ else if (k.charAt(0) != '.')
+ r.values[k] = self.state.creates[c][s][k];
+ }
- runTraceroute6: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'traceroute6',
- params: [ 'data' ],
- expect: { '': { code: -1 } }
+ snew.push(self.state.creates[c][s]);
+
+ 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]);
+
+ if (self.state.deletes)
+ for (var c in self.state.deletes)
+ for (var s in self.state.deletes[c])
+ {
+ var o = self.state.deletes[c][s];
+ self._delete(c, s, (o === true) ? undefined : o);
+ }
+
+ return _luci2.rpc.flush().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._reorder();
+ });
+ },
+
+ _apply: _luci2.rpc.declare({
+ object: 'uci',
+ method: 'apply',
+ params: [ 'timeout', 'rollback' ]
}),
- runNslookup: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'nslookup',
- params: [ 'data' ],
- expect: { '': { code: -1 } }
+ _confirm: _luci2.rpc.declare({
+ object: 'uci',
+ method: 'confirm'
}),
+ apply: function(timeout)
+ {
+ var self = this;
+ var date = new Date();
+ var deferred = $.Deferred();
- setUp: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'ifup',
- params: [ 'data' ],
- expect: { '': { code: -1 } }
+ if (typeof(timeout) != 'number' || timeout < 1)
+ timeout = 10;
+
+ self._apply(timeout, true).then(function(rv) {
+ if (rv != 0)
+ {
+ deferred.rejectWith(self, [ rv ]);
+ return;
+ }
+
+ var try_deadline = date.getTime() + 1000 * timeout;
+ var try_confirm = function()
+ {
+ return self._confirm().then(function(rv) {
+ if (rv != 0)
+ {
+ if (date.getTime() < try_deadline)
+ window.setTimeout(try_confirm, 250);
+ else
+ deferred.rejectWith(self, [ rv ]);
+
+ return;
+ }
+
+ deferred.resolveWith(self, [ rv ]);
+ });
+ };
+
+ window.setTimeout(try_confirm, 1000);
+ });
+
+ return deferred;
+ },
+
+ changes: _luci2.rpc.declare({
+ object: 'uci',
+ method: 'changes',
+ expect: { changes: { } }
}),
- setDown: _luci2.rpc.declare({
- object: 'luci2.network',
- method: 'ifdown',
- params: [ 'data' ],
- expect: { '': { code: -1 } }
- })
- };
+ readable: function(conf)
+ {
+ return _luci2.session.hasACL('uci', conf, 'read');
+ },
+
+ writable: function(conf)
+ {
+ return _luci2.session.hasACL('uci', conf, 'write');
+ }
+ });
+
+ this.uci = new this.UCIContext();
this.wireless = {
listDeviceNames: _luci2.rpc.declare({
for (var i = 0; i < data.length; i++)
data[i]['device'] = params['device'];
- data.sort(function(a, b) {
- if (a.bssid < b.bssid)
- return -1;
- else if (a.bssid > b.bssid)
- return 1;
- else
- return 0;
- });
+ data.sort(function(a, b) {
+ if (a.bssid < b.bssid)
+ return -1;
+ else if (a.bssid > b.bssid)
+ return 1;
+ else
+ return 0;
+ });
+
+ return data;
+ }
+ }),
+
+ getWirelessStatus: function() {
+ return this.listDeviceNames().then(function(names) {
+ _luci2.rpc.batch();
+
+ for (var i = 0; i < names.length; i++)
+ _luci2.wireless.getDeviceStatus(names[i]);
+
+ return _luci2.rpc.flush();
+ }).then(function(networks) {
+ var rv = { };
+
+ var phy_attrs = [
+ 'country', 'channel', 'frequency', 'frequency_offset',
+ 'txpower', 'txpower_offset', 'hwmodes', 'hardware', 'phy'
+ ];
+
+ var net_attrs = [
+ 'ssid', 'bssid', 'mode', 'quality', 'quality_max',
+ 'signal', 'noise', 'bitrate', 'encryption'
+ ];
+
+ for (var i = 0; i < networks.length; i++)
+ {
+ var phy = rv[networks[i].phy] || (
+ rv[networks[i].phy] = { networks: [ ] }
+ );
+
+ var net = {
+ device: networks[i].device
+ };
+
+ for (var j = 0; j < phy_attrs.length; j++)
+ phy[phy_attrs[j]] = networks[i][phy_attrs[j]];
+
+ for (var j = 0; j < net_attrs.length; j++)
+ net[net_attrs[j]] = networks[i][net_attrs[j]];
+
+ phy.networks.push(net);
+ }
+
+ return rv;
+ });
+ },
+
+ getAssocLists: function()
+ {
+ return this.listDeviceNames().then(function(names) {
+ _luci2.rpc.batch();
+
+ for (var i = 0; i < names.length; i++)
+ _luci2.wireless.getAssocList(names[i]);
+
+ return _luci2.rpc.flush();
+ }).then(function(assoclists) {
+ var rv = [ ];
+
+ for (var i = 0; i < assoclists.length; i++)
+ for (var j = 0; j < assoclists[i].length; j++)
+ rv.push(assoclists[i][j]);
+
+ return rv;
+ });
+ },
+
+ formatEncryption: function(enc)
+ {
+ var format_list = function(l, s)
+ {
+ var rv = [ ];
+ for (var i = 0; i < l.length; i++)
+ rv.push(l[i].toUpperCase());
+ return rv.join(s ? s : ', ');
+ }
+
+ if (!enc || !enc.enabled)
+ return _luci2.tr('None');
+
+ if (enc.wep)
+ {
+ if (enc.wep.length == 2)
+ return _luci2.tr('WEP Open/Shared') + ' (%s)'.format(format_list(enc.ciphers, ', '));
+ else if (enc.wep[0] == 'shared')
+ return _luci2.tr('WEP Shared Auth') + ' (%s)'.format(format_list(enc.ciphers, ', '));
+ else
+ return _luci2.tr('WEP Open System') + ' (%s)'.format(format_list(enc.ciphers, ', '));
+ }
+ else if (enc.wpa)
+ {
+ if (enc.wpa.length == 2)
+ return _luci2.tr('mixed WPA/WPA2') + ' %s (%s)'.format(
+ format_list(enc.authentication, '/'),
+ format_list(enc.ciphers, ', ')
+ );
+ else if (enc.wpa[0] == 2)
+ return 'WPA2 %s (%s)'.format(
+ format_list(enc.authentication, '/'),
+ format_list(enc.ciphers, ', ')
+ );
+ else
+ return 'WPA %s (%s)'.format(
+ format_list(enc.authentication, '/'),
+ format_list(enc.ciphers, ', ')
+ );
+ }
+
+ return _luci2.tr('Unknown');
+ }
+ };
+
+ this.firewall = {
+ getZoneColor: function(zone)
+ {
+ if ($.isPlainObject(zone))
+ zone = zone.name;
+
+ if (zone == 'lan')
+ return '#90f090';
+ else if (zone == 'wan')
+ return '#f09090';
+
+ for (var i = 0, hash = 0;
+ i < zone.length;
+ hash = zone.charCodeAt(i++) + ((hash << 5) - hash));
+
+ for (var i = 0, color = '#';
+ i < 3;
+ color += ('00' + ((hash >> i++ * 8) & 0xFF).tostring(16)).slice(-2));
+
+ return color;
+ },
+
+ findZoneByNetwork: function(network)
+ {
+ var self = this;
+ var zone = undefined;
+
+ return _luci2.uci.sections('firewall', 'zone', function(z) {
+ if (!z.name || !z.network)
+ return;
+
+ if (!$.isArray(z.network))
+ z.network = z.network.split(/\s+/);
+
+ for (var i = 0; i < z.network.length; i++)
+ {
+ if (z.network[i] == network)
+ {
+ zone = z;
+ break;
+ }
+ }
+ }).then(function() {
+ if (zone)
+ zone.color = self.getZoneColor(zone);
+
+ return zone;
+ });
+ }
+ };
+
+ this.NetworkModel = {
+ _device_blacklist: [
+ /^gre[0-9]+$/,
+ /^gretap[0-9]+$/,
+ /^ifb[0-9]+$/,
+ /^ip6tnl[0-9]+$/,
+ /^sit[0-9]+$/,
+ /^wlan[0-9]+\.sta[0-9]+$/
+ ],
+
+ _cache_functions: [
+ 'protolist', 0, _luci2.rpc.declare({
+ object: 'network',
+ method: 'get_proto_handlers',
+ expect: { '': { } }
+ }),
+ 'ifstate', 1, _luci2.rpc.declare({
+ object: 'network.interface',
+ method: 'dump',
+ expect: { 'interface': [ ] }
+ }),
+ 'devstate', 2, _luci2.rpc.declare({
+ object: 'network.device',
+ method: 'status',
+ expect: { '': { } }
+ }),
+ 'wifistate', 0, _luci2.rpc.declare({
+ object: 'network.wireless',
+ method: 'status',
+ expect: { '': { } }
+ }),
+ 'bwstate', 2, _luci2.rpc.declare({
+ object: 'luci2.network.bwmon',
+ method: 'statistics',
+ expect: { 'statistics': { } }
+ }),
+ 'devlist', 2, _luci2.rpc.declare({
+ object: 'luci2.network',
+ method: 'device_list',
+ expect: { 'devices': [ ] }
+ }),
+ 'swlist', 0, _luci2.rpc.declare({
+ object: 'luci2.network',
+ method: 'switch_list',
+ expect: { 'switches': [ ] }
+ })
+ ],
+
+ _fetch_protocol: function(proto)
+ {
+ var url = _luci2.globals.resource + '/proto/' + proto + '.js';
+ var self = _luci2.NetworkModel;
+
+ var def = $.Deferred();
+
+ $.ajax(url, {
+ method: 'GET',
+ cache: true,
+ dataType: 'text'
+ }).then(function(data) {
+ try {
+ var protoConstructorSource = (
+ '(function(L, $) { ' +
+ 'return %s' +
+ '})(_luci2, $);\n\n' +
+ '//@ sourceURL=%s'
+ ).format(data, url);
+
+ var protoClass = eval(protoConstructorSource);
+
+ self._protos[proto] = new protoClass();
+ }
+ catch(e) {
+ alert('Unable to instantiate proto "%s": %s'.format(url, e));
+ };
+
+ def.resolve();
+ }).fail(function() {
+ def.resolve();
+ });
+
+ return def;
+ },
+
+ _fetch_protocols: function()
+ {
+ var self = _luci2.NetworkModel;
+ var deferreds = [ ];
+
+ for (var proto in self._cache.protolist)
+ deferreds.push(self._fetch_protocol(proto));
+
+ return $.when.apply($, deferreds);
+ },
+
+ _fetch_swstate: _luci2.rpc.declare({
+ object: 'luci2.network',
+ method: 'switch_info',
+ params: [ 'switch' ],
+ expect: { 'info': { } }
+ }),
+
+ _fetch_swstate_cb: function(responses) {
+ var self = _luci2.NetworkModel;
+ var swlist = self._cache.swlist;
+ var swstate = self._cache.swstate = { };
+
+ for (var i = 0; i < responses.length; i++)
+ swstate[swlist[i]] = responses[i];
+ },
+
+ _fetch_cache_cb: function(level)
+ {
+ var self = _luci2.NetworkModel;
+ var name = '_fetch_cache_cb_' + level;
+
+ return self[name] || (
+ self[name] = function(responses)
+ {
+ for (var i = 0; i < self._cache_functions.length; i += 3)
+ if (!level || self._cache_functions[i + 1] == level)
+ self._cache[self._cache_functions[i]] = responses.shift();
+
+ if (!level)
+ {
+ _luci2.rpc.batch();
+
+ for (var i = 0; i < self._cache.swlist.length; i++)
+ self._fetch_swstate(self._cache.swlist[i]);
+
+ return _luci2.rpc.flush().then(self._fetch_swstate_cb);
+ }
+
+ return _luci2.deferrable();
+ }
+ );
+ },
+
+ _fetch_cache: function(level)
+ {
+ var self = _luci2.NetworkModel;
+
+ return _luci2.uci.load(['network', 'wireless']).then(function() {
+ _luci2.rpc.batch();
+
+ for (var i = 0; i < self._cache_functions.length; i += 3)
+ if (!level || self._cache_functions[i + 1] == level)
+ self._cache_functions[i + 2]();
+
+ return _luci2.rpc.flush().then(self._fetch_cache_cb(level || 0));
+ });
+ },
+
+ _get: function(pkg, sid, key)
+ {
+ return _luci2.uci.get(pkg, sid, key);
+ },
+
+ _set: function(pkg, sid, key, val)
+ {
+ return _luci2.uci.set(pkg, sid, key, val);
+ },
+
+ _is_blacklisted: function(dev)
+ {
+ for (var i = 0; i < this._device_blacklist.length; i++)
+ if (dev.match(this._device_blacklist[i]))
+ return true;
+
+ return false;
+ },
+
+ _sort_devices: function(a, b)
+ {
+ if (a.options.kind < b.options.kind)
+ return -1;
+ else if (a.options.kind > b.options.kind)
+ return 1;
+
+ if (a.options.name < b.options.name)
+ return -1;
+ else if (a.options.name > b.options.name)
+ return 1;
+
+ return 0;
+ },
+
+ _get_dev: function(ifname)
+ {
+ var alias = (ifname.charAt(0) == '@');
+ return this._devs[ifname] || (
+ this._devs[ifname] = {
+ ifname: ifname,
+ kind: alias ? 'alias' : 'ethernet',
+ type: alias ? 0 : 1,
+ up: false,
+ changed: { }
+ }
+ );
+ },
+
+ _get_iface: function(name)
+ {
+ return this._ifaces[name] || (
+ this._ifaces[name] = {
+ name: name,
+ proto: this._protos.none,
+ changed: { }
+ }
+ );
+ },
+
+ _parse_devices: function()
+ {
+ var self = _luci2.NetworkModel;
+ var wificount = { };
+
+ for (var ifname in self._cache.devstate)
+ {
+ if (self._is_blacklisted(ifname))
+ continue;
+
+ var dev = self._cache.devstate[ifname];
+ var entry = self._get_dev(ifname);
+
+ entry.up = dev.up;
+
+ switch (dev.type)
+ {
+ case 'IP tunnel':
+ entry.kind = 'tunnel';
+ break;
+
+ case 'Bridge':
+ entry.kind = 'bridge';
+ //entry.ports = dev['bridge-members'].sort();
+ break;
+ }
+ }
+
+ for (var i = 0; i < self._cache.devlist.length; i++)
+ {
+ var dev = self._cache.devlist[i];
+
+ if (self._is_blacklisted(dev.device))
+ continue;
+
+ var entry = self._get_dev(dev.device);
+
+ entry.up = dev.is_up;
+ entry.type = dev.type;
+
+ switch (dev.type)
+ {
+ case 1: /* Ethernet */
+ if (dev.is_bridge)
+ entry.kind = 'bridge';
+ else if (dev.is_tuntap)
+ entry.kind = 'tunnel';
+ else if (dev.is_wireless)
+ entry.kind = 'wifi';
+ break;
+
+ case 512: /* PPP */
+ case 768: /* IP-IP Tunnel */
+ case 769: /* IP6-IP6 Tunnel */
+ case 776: /* IPv6-in-IPv4 */
+ case 778: /* GRE over IP */
+ entry.kind = 'tunnel';
+ break;
+ }
+ }
+
+ var net = _luci2.uci.sections('network');
+ for (var i = 0; i < net.length; i++)
+ {
+ var s = net[i];
+ var sid = s['.name'];
+
+ if (s['.type'] == 'device' && s.name)
+ {
+ var entry = self._get_dev(s.name);
+
+ switch (s.type)
+ {
+ case 'macvlan':
+ case 'tunnel':
+ entry.kind = 'tunnel';
+ break;
+ }
+
+ entry.sid = sid;
+ }
+ else if (s['.type'] == 'interface' && !s['.anonymous'] && s.ifname)
+ {
+ var ifnames = _luci2.toArray(s.ifname);
+
+ for (var j = 0; j < ifnames.length; j++)
+ self._get_dev(ifnames[j]);
+
+ if (s['.name'] != 'loopback')
+ {
+ var entry = self._get_dev('@%s'.format(s['.name']));
+
+ entry.type = 0;
+ entry.kind = 'alias';
+ entry.sid = sid;
+ }
+ }
+ else if (s['.type'] == 'switch_vlan' && s.device)
+ {
+ var sw = self._cache.swstate[s.device];
+ var vid = parseInt(s.vid || s.vlan);
+ var ports = _luci2.toArray(s.ports);
+
+ if (!sw || !ports.length || isNaN(vid))
+ continue;
+
+ var ifname = undefined;
+
+ for (var j = 0; j < ports.length; j++)
+ {
+ var port = parseInt(ports[j]);
+ var tag = (ports[j].replace(/[^tu]/g, '') == 't');
+
+ if (port == sw.cpu_port)
+ {
+ // XXX: need a way to map switch to netdev
+ if (tag)
+ ifname = 'eth0.%d'.format(vid);
+ else
+ ifname = 'eth0';
+
+ break;
+ }
+ }
+
+ if (!ifname)
+ continue;
+
+ var entry = self._get_dev(ifname);
+
+ entry.kind = 'vlan';
+ entry.sid = sid;
+ entry.vsw = sw;
+ entry.vid = vid;
+ }
+ }
+
+ var wifi = _luci2.uci.sections('wireless');
+ for (var i = 0; i < wifi.length; i++)
+ {
+ var s = wifi[i];
+ var sid = s['.name'];
+
+ if (s['.type'] == 'wifi-iface' && s.device)
+ {
+ var r = parseInt(s.device.replace(/^[^0-9]+/, ''));
+ var n = wificount[s.device] = (wificount[s.device] || 0) + 1;
+ var id = 'radio%d.network%d'.format(r, n);
+ var ifname = id;
+
+ if (self._cache.wifistate[s.device])
+ {
+ var ifcs = self._cache.wifistate[s.device].interfaces;
+ for (var ifc in ifcs)
+ {
+ if (ifcs[ifc].section == sid)
+ {
+ ifname = ifcs[ifc].ifname;
+ break;
+ }
+ }
+ }
+
+ var entry = self._get_dev(ifname);
+
+ entry.kind = 'wifi';
+ entry.sid = sid;
+ entry.wid = id;
+ entry.wdev = s.device;
+ entry.wmode = s.mode;
+ entry.wssid = s.ssid;
+ entry.wbssid = s.bssid;
+ }
+ }
+
+ for (var i = 0; i < net.length; i++)
+ {
+ var s = net[i];
+ var sid = s['.name'];
+
+ if (s['.type'] == 'interface' && !s['.anonymous'] && s.type == 'bridge')
+ {
+ var ifnames = _luci2.toArray(s.ifname);
+
+ for (var ifname in self._devs)
+ {
+ var dev = self._devs[ifname];
+
+ if (dev.kind != 'wifi')
+ continue;
+
+ var wnets = _luci2.toArray(_luci2.uci.get('wireless', dev.sid, 'network'));
+ if ($.inArray(sid, wnets) > -1)
+ ifnames.push(ifname);
+ }
+
+ entry = self._get_dev('br-%s'.format(s['.name']));
+ entry.type = 1;
+ entry.kind = 'bridge';
+ entry.sid = sid;
+ entry.ports = ifnames.sort();
+ }
+ }
+ },
+
+ _parse_interfaces: function()
+ {
+ var self = _luci2.NetworkModel;
+ var net = _luci2.uci.sections('network');
+
+ for (var i = 0; i < net.length; i++)
+ {
+ var s = net[i];
+ var sid = s['.name'];
+
+ if (s['.type'] == 'interface' && !s['.anonymous'] && s.proto)
+ {
+ var entry = self._get_iface(s['.name']);
+ var proto = self._protos[s.proto] || self._protos.none;
+
+ var l3dev = undefined;
+ var l2dev = undefined;
+
+ var ifnames = _luci2.toArray(s.ifname);
+
+ for (var ifname in self._devs)
+ {
+ var dev = self._devs[ifname];
+
+ if (dev.kind != 'wifi')
+ continue;
+
+ var wnets = _luci2.toArray(_luci2.uci.get('wireless', dev.sid, 'network'));
+ if ($.inArray(entry.name, wnets) > -1)
+ ifnames.push(ifname);
+ }
+
+ if (proto.virtual)
+ l3dev = '%s-%s'.format(s.proto, entry.name);
+ else if (s.type == 'bridge')
+ l3dev = 'br-%s'.format(entry.name);
+ else
+ l3dev = ifnames[0];
+
+ if (!proto.virtual && s.type == 'bridge')
+ l2dev = 'br-%s'.format(entry.name);
+ else if (!proto.virtual)
+ l2dev = ifnames[0];
+
+ entry.proto = proto;
+ entry.sid = sid;
+ entry.l3dev = l3dev;
+ entry.l2dev = l2dev;
+ }
+ }
+
+ for (var i = 0; i < self._cache.ifstate.length; i++)
+ {
+ var iface = self._cache.ifstate[i];
+ var entry = self._get_iface(iface['interface']);
+ var proto = self._protos[iface.proto] || self._protos.none;
+
+ /* this is a virtual interface, either deleted from config but
+ not applied yet or set up from external tools (6rd) */
+ if (!entry.sid)
+ {
+ entry.proto = proto;
+ entry.l2dev = iface.device;
+ entry.l3dev = iface.l3_device;
+ }
+ }
+ },
+
+ init: function()
+ {
+ var self = this;
+
+ if (self._cache)
+ return _luci2.deferrable();
+
+ self._cache = { };
+ self._devs = { };
+ self._ifaces = { };
+ self._protos = { };
+
+ return self._fetch_cache()
+ .then(self._fetch_protocols)
+ .then(self._parse_devices)
+ .then(self._parse_interfaces);
+ },
+
+ update: function()
+ {
+ delete this._cache;
+ return this.init();
+ },
+
+ refreshInterfaceStatus: function()
+ {
+ return this._fetch_cache(1).then(this._parse_interfaces);
+ },
+
+ refreshDeviceStatus: function()
+ {
+ return this._fetch_cache(2).then(this._parse_devices);
+ },
+
+ refreshStatus: function()
+ {
+ return this._fetch_cache(1)
+ .then(this._fetch_cache(2))
+ .then(this._parse_devices)
+ .then(this._parse_interfaces);
+ },
+
+ getDevices: function()
+ {
+ var devs = [ ];
+
+ for (var ifname in this._devs)
+ if (ifname != 'lo')
+ devs.push(new _luci2.NetworkModel.Device(this._devs[ifname]));
+
+ return devs.sort(this._sort_devices);
+ },
+
+ getDeviceByInterface: function(iface)
+ {
+ if (iface instanceof _luci2.NetworkModel.Interface)
+ iface = iface.name();
+
+ if (this._ifaces[iface])
+ return this.getDevice(this._ifaces[iface].l3dev) ||
+ this.getDevice(this._ifaces[iface].l2dev);
+
+ return undefined;
+ },
+
+ getDevice: function(ifname)
+ {
+ if (this._devs[ifname])
+ return new _luci2.NetworkModel.Device(this._devs[ifname]);
+
+ return undefined;
+ },
+
+ createDevice: function(name)
+ {
+ return new _luci2.NetworkModel.Device(this._get_dev(name));
+ },
+
+ getInterfaces: function()
+ {
+ var ifaces = [ ];
+
+ for (var name in this._ifaces)
+ if (name != 'loopback')
+ ifaces.push(this.getInterface(name));
+
+ ifaces.sort(function(a, b) {
+ if (a.name() < b.name())
+ return -1;
+ else if (a.name() > b.name())
+ return 1;
+ else
+ return 0;
+ });
+
+ return ifaces;
+ },
+
+ getInterfacesByDevice: function(dev)
+ {
+ var ifaces = [ ];
+
+ if (dev instanceof _luci2.NetworkModel.Device)
+ dev = dev.name();
+
+ for (var name in this._ifaces)
+ {
+ var iface = this._ifaces[name];
+ if (iface.l2dev == dev || iface.l3dev == dev)
+ ifaces.push(this.getInterface(name));
+ }
+
+ ifaces.sort(function(a, b) {
+ if (a.name() < b.name())
+ return -1;
+ else if (a.name() > b.name())
+ return 1;
+ else
+ return 0;
+ });
+
+ return ifaces;
+ },
+
+ getInterface: function(iface)
+ {
+ if (this._ifaces[iface])
+ return new _luci2.NetworkModel.Interface(this._ifaces[iface]);
+
+ return undefined;
+ },
+
+ getProtocols: function()
+ {
+ var rv = [ ];
+
+ for (var proto in this._protos)
+ {
+ var pr = this._protos[proto];
+
+ rv.push({
+ name: proto,
+ description: pr.description,
+ virtual: pr.virtual,
+ tunnel: pr.tunnel
+ });
+ }
+
+ return rv.sort(function(a, b) {
+ if (a.name < b.name)
+ return -1;
+ else if (a.name > b.name)
+ return 1;
+ else
+ return 0;
+ });
+ },
+
+ _find_wan: function(ipaddr)
+ {
+ for (var i = 0; i < this._cache.ifstate.length; i++)
+ {
+ var ifstate = this._cache.ifstate[i];
+
+ if (!ifstate.route)
+ continue;
+
+ for (var j = 0; j < ifstate.route.length; j++)
+ if (ifstate.route[j].mask == 0 &&
+ ifstate.route[j].target == ipaddr &&
+ typeof(ifstate.route[j].table) == 'undefined')
+ {
+ return this.getInterface(ifstate['interface']);
+ }
+ }
+
+ return undefined;
+ },
+
+ findWAN: function()
+ {
+ return this._find_wan('0.0.0.0');
+ },
+
+ findWAN6: function()
+ {
+ return this._find_wan('::');
+ },
+
+ resolveAlias: function(ifname)
+ {
+ if (ifname instanceof _luci2.NetworkModel.Device)
+ ifname = ifname.name();
+
+ var dev = this._devs[ifname];
+ var seen = { };
+
+ while (dev && dev.kind == 'alias')
+ {
+ // loop
+ if (seen[dev.ifname])
+ return undefined;
+
+ var ifc = this._ifaces[dev.sid];
+
+ seen[dev.ifname] = true;
+ dev = ifc ? this._devs[ifc.l3dev] : undefined;
+ }
+
+ return dev ? this.getDevice(dev.ifname) : undefined;
+ }
+ };
+
+ this.NetworkModel.Device = Class.extend({
+ _wifi_modes: {
+ ap: _luci2.tr('Master'),
+ sta: _luci2.tr('Client'),
+ adhoc: _luci2.tr('Ad-Hoc'),
+ monitor: _luci2.tr('Monitor'),
+ wds: _luci2.tr('Static WDS')
+ },
+
+ _status: function(key)
+ {
+ var s = _luci2.NetworkModel._cache.devstate[this.options.ifname];
+
+ if (s)
+ return key ? s[key] : s;
+
+ return undefined;
+ },
+
+ get: function(key)
+ {
+ var sid = this.options.sid;
+ var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network';
+ return _luci2.NetworkModel._get(pkg, sid, key);
+ },
+
+ set: function(key, val)
+ {
+ var sid = this.options.sid;
+ var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network';
+ return _luci2.NetworkModel._set(pkg, sid, key, val);
+ },
+
+ init: function()
+ {
+ if (typeof(this.options.type) == 'undefined')
+ this.options.type = 1;
+
+ if (typeof(this.options.kind) == 'undefined')
+ this.options.kind = 'ethernet';
+
+ if (typeof(this.options.networks) == 'undefined')
+ this.options.networks = [ ];
+ },
+
+ name: function()
+ {
+ return this.options.ifname;
+ },
+
+ description: function()
+ {
+ switch (this.options.kind)
+ {
+ case 'alias':
+ return _luci2.tr('Alias for network "%s"').format(this.options.ifname.substring(1));
+
+ case 'bridge':
+ return _luci2.tr('Network bridge');
+
+ case 'ethernet':
+ return _luci2.tr('Network device');
+
+ case 'tunnel':
+ switch (this.options.type)
+ {
+ case 1: /* tuntap */
+ return _luci2.tr('TAP device');
+
+ case 512: /* PPP */
+ return _luci2.tr('PPP tunnel');
+
+ case 768: /* IP-IP Tunnel */
+ return _luci2.tr('IP-in-IP tunnel');
+
+ case 769: /* IP6-IP6 Tunnel */
+ return _luci2.tr('IPv6-in-IPv6 tunnel');
+
+ case 776: /* IPv6-in-IPv4 */
+ return _luci2.tr('IPv6-over-IPv4 tunnel');
+ break;
+
+ case 778: /* GRE over IP */
+ return _luci2.tr('GRE-over-IP tunnel');
+
+ default:
+ return _luci2.tr('Tunnel device');
+ }
+
+ case 'vlan':
+ return _luci2.tr('VLAN %d on %s').format(this.options.vid, this.options.vsw.model);
+
+ case 'wifi':
+ var o = this.options;
+ return _luci2.trc('(Wifi-Mode) "(SSID)" on (radioX)', '%s "%h" on %s').format(
+ o.wmode ? this._wifi_modes[o.wmode] : _luci2.tr('Unknown mode'),
+ o.wssid || '?', o.wdev
+ );
+ }
+
+ return _luci2.tr('Unknown device');
+ },
+
+ icon: function(up)
+ {
+ var kind = this.options.kind;
+
+ if (kind == 'alias')
+ kind = 'ethernet';
+
+ if (typeof(up) == 'undefined')
+ up = this.isUp();
+
+ return _luci2.globals.resource + '/icons/%s%s.png'.format(kind, up ? '' : '_disabled');
+ },
+
+ isUp: function()
+ {
+ var l = _luci2.NetworkModel._cache.devlist;
+
+ for (var i = 0; i < l.length; i++)
+ if (l[i].device == this.options.ifname)
+ return (l[i].is_up === true);
+
+ return false;
+ },
+
+ isAlias: function()
+ {
+ return (this.options.kind == 'alias');
+ },
+
+ isBridge: function()
+ {
+ return (this.options.kind == 'bridge');
+ },
+
+ isBridgeable: function()
+ {
+ return (this.options.type == 1 && this.options.kind != 'bridge');
+ },
+
+ isWireless: function()
+ {
+ return (this.options.kind == 'wifi');
+ },
+
+ isInNetwork: function(net)
+ {
+ if (!(net instanceof _luci2.NetworkModel.Interface))
+ net = _luci2.NetworkModel.getInterface(net);
+
+ if (net)
+ {
+ if (net.options.l3dev == this.options.ifname ||
+ net.options.l2dev == this.options.ifname)
+ return true;
+
+ var dev = _luci2.NetworkModel._devs[net.options.l2dev];
+ if (dev && dev.kind == 'bridge' && dev.ports)
+ return ($.inArray(this.options.ifname, dev.ports) > -1);
+ }
+
+ return false;
+ },
+
+ getMTU: function()
+ {
+ var dev = _luci2.NetworkModel._cache.devstate[this.options.ifname];
+ if (dev && !isNaN(dev.mtu))
+ return dev.mtu;
+
+ return undefined;
+ },
+
+ getMACAddress: function()
+ {
+ if (this.options.type != 1)
+ return undefined;
+
+ var dev = _luci2.NetworkModel._cache.devstate[this.options.ifname];
+ if (dev && dev.macaddr)
+ return dev.macaddr.toUpperCase();
+
+ return undefined;
+ },
+
+ getInterfaces: function()
+ {
+ return _luci2.NetworkModel.getInterfacesByDevice(this.options.name);
+ },
+
+ getStatistics: function()
+ {
+ var s = this._status('statistics') || { };
+ return {
+ rx_bytes: (s.rx_bytes || 0),
+ tx_bytes: (s.tx_bytes || 0),
+ rx_packets: (s.rx_packets || 0),
+ tx_packets: (s.tx_packets || 0)
+ };
+ },
+
+ getTrafficHistory: function()
+ {
+ var def = new Array(120);
+
+ for (var i = 0; i < 120; i++)
+ def[i] = 0;
+
+ var h = _luci2.NetworkModel._cache.bwstate[this.options.ifname] || { };
+ return {
+ rx_bytes: (h.rx_bytes || def),
+ tx_bytes: (h.tx_bytes || def),
+ rx_packets: (h.rx_packets || def),
+ tx_packets: (h.tx_packets || def)
+ };
+ },
+
+ removeFromInterface: function(iface)
+ {
+ if (!(iface instanceof _luci2.NetworkModel.Interface))
+ iface = _luci2.NetworkModel.getInterface(iface);
+
+ if (!iface)
+ return;
+
+ var ifnames = _luci2.toArray(iface.get('ifname'));
+ if ($.inArray(this.options.ifname, ifnames) > -1)
+ iface.set('ifname', _luci2.filterArray(ifnames, this.options.ifname));
+
+ if (this.options.kind != 'wifi')
+ return;
+
+ var networks = _luci2.toArray(this.get('network'));
+ if ($.inArray(iface.name(), networks) > -1)
+ this.set('network', _luci2.filterArray(networks, iface.name()));
+ },
+
+ attachToInterface: function(iface)
+ {
+ if (!(iface instanceof _luci2.NetworkModel.Interface))
+ iface = _luci2.NetworkModel.getInterface(iface);
+
+ if (!iface)
+ return;
+
+ if (this.options.kind != 'wifi')
+ {
+ var ifnames = _luci2.toArray(iface.get('ifname'));
+ if ($.inArray(this.options.ifname, ifnames) < 0)
+ {
+ ifnames.push(this.options.ifname);
+ iface.set('ifname', (ifnames.length > 1) ? ifnames : ifnames[0]);
+ }
+ }
+ else
+ {
+ var networks = _luci2.toArray(this.get('network'));
+ if ($.inArray(iface.name(), networks) < 0)
+ {
+ networks.push(iface.name());
+ this.set('network', (networks.length > 1) ? networks : networks[0]);
+ }
+ }
+ }
+ });
+
+ this.NetworkModel.Interface = Class.extend({
+ _status: function(key)
+ {
+ var s = _luci2.NetworkModel._cache.ifstate;
+
+ for (var i = 0; i < s.length; i++)
+ if (s[i]['interface'] == this.options.name)
+ return key ? s[i][key] : s[i];
+
+ return undefined;
+ },
+
+ get: function(key)
+ {
+ return _luci2.NetworkModel._get('network', this.options.name, key);
+ },
+
+ set: function(key, val)
+ {
+ return _luci2.NetworkModel._set('network', this.options.name, key, val);
+ },
+
+ name: function()
+ {
+ return this.options.name;
+ },
+
+ protocol: function()
+ {
+ return (this.get('proto') || 'none');
+ },
+
+ isUp: function()
+ {
+ return (this._status('up') === true);
+ },
+
+ isVirtual: function()
+ {
+ return (typeof(this.options.sid) != 'string');
+ },
+
+ getProtocol: function()
+ {
+ var prname = this.get('proto') || 'none';
+ return _luci2.NetworkModel._protos[prname] || _luci2.NetworkModel._protos.none;
+ },
+
+ getUptime: function()
+ {
+ var uptime = this._status('uptime');
+ return isNaN(uptime) ? 0 : uptime;
+ },
+
+ getDevice: function(resolveAlias)
+ {
+ if (this.options.l3dev)
+ return _luci2.NetworkModel.getDevice(this.options.l3dev);
+
+ return undefined;
+ },
+
+ getPhysdev: function()
+ {
+ if (this.options.l2dev)
+ return _luci2.NetworkModel.getDevice(this.options.l2dev);
+
+ return undefined;
+ },
+
+ getSubdevices: function()
+ {
+ var rv = [ ];
+ var dev = this.options.l2dev ?
+ _luci2.NetworkModel._devs[this.options.l2dev] : undefined;
+
+ if (dev && dev.kind == 'bridge' && dev.ports && dev.ports.length)
+ for (var i = 0; i < dev.ports.length; i++)
+ rv.push(_luci2.NetworkModel.getDevice(dev.ports[i]));
+
+ return rv;
+ },
+
+ getIPv4Addrs: function(mask)
+ {
+ var rv = [ ];
+ var addrs = this._status('ipv4-address');
+
+ if (addrs)
+ for (var i = 0; i < addrs.length; i++)
+ if (!mask)
+ rv.push(addrs[i].address);
+ else
+ rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
+
+ return rv;
+ },
+
+ getIPv6Addrs: function(mask)
+ {
+ var rv = [ ];
+ var addrs;
+
+ addrs = this._status('ipv6-address');
+
+ if (addrs)
+ for (var i = 0; i < addrs.length; i++)
+ if (!mask)
+ rv.push(addrs[i].address);
+ else
+ rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
+
+ addrs = this._status('ipv6-prefix-assignment');
+
+ if (addrs)
+ for (var i = 0; i < addrs.length; i++)
+ if (!mask)
+ rv.push('%s1'.format(addrs[i].address));
+ else
+ rv.push('%s1/%d'.format(addrs[i].address, addrs[i].mask));
+
+ return rv;
+ },
+
+ getDNSAddrs: function()
+ {
+ var rv = [ ];
+ var addrs = this._status('dns-server');
+
+ if (addrs)
+ for (var i = 0; i < addrs.length; i++)
+ rv.push(addrs[i]);
+
+ return rv;
+ },
+
+ getIPv4DNS: function()
+ {
+ var rv = [ ];
+ var dns = this._status('dns-server');
+
+ if (dns)
+ for (var i = 0; i < dns.length; i++)
+ if (dns[i].indexOf(':') == -1)
+ rv.push(dns[i]);
+
+ return rv;
+ },
+
+ getIPv6DNS: function()
+ {
+ var rv = [ ];
+ var dns = this._status('dns-server');
+
+ if (dns)
+ for (var i = 0; i < dns.length; i++)
+ if (dns[i].indexOf(':') > -1)
+ rv.push(dns[i]);
+
+ return rv;
+ },
+
+ getIPv4Gateway: function()
+ {
+ var rt = this._status('route');
+
+ if (rt)
+ for (var i = 0; i < rt.length; i++)
+ if (rt[i].target == '0.0.0.0' && rt[i].mask == 0)
+ return rt[i].nexthop;
+
+ return undefined;
+ },
+
+ getIPv6Gateway: function()
+ {
+ var rt = this._status('route');
+
+ if (rt)
+ for (var i = 0; i < rt.length; i++)
+ if (rt[i].target == '::' && rt[i].mask == 0)
+ return rt[i].nexthop;
+
+ return undefined;
+ },
+
+ getStatistics: function()
+ {
+ var dev = this.getDevice() || new _luci2.NetworkModel.Device({});
+ return dev.getStatistics();
+ },
+
+ getTrafficHistory: function()
+ {
+ var dev = this.getDevice() || new _luci2.NetworkModel.Device({});
+ return dev.getTrafficHistory();
+ },
+
+ setDevices: function(devs)
+ {
+ var dev = this.getPhysdev();
+ var old_devs = [ ];
+ var changed = false;
+
+ if (dev && dev.isBridge())
+ old_devs = this.getSubdevices();
+ else if (dev)
+ old_devs = [ dev ];
+
+ if (old_devs.length != devs.length)
+ changed = true;
+ else
+ for (var i = 0; i < old_devs.length; i++)
+ {
+ var dev = devs[i];
+
+ if (dev instanceof _luci2.NetworkModel.Device)
+ dev = dev.name();
+
+ if (!dev || old_devs[i].name() != dev)
+ {
+ changed = true;
+ break;
+ }
+ }
+
+ if (changed)
+ {
+ for (var i = 0; i < old_devs.length; i++)
+ old_devs[i].removeFromInterface(this);
+
+ for (var i = 0; i < devs.length; i++)
+ {
+ var dev = devs[i];
- return data;
+ if (!(dev instanceof _luci2.NetworkModel.Device))
+ dev = _luci2.NetworkModel.getDevice(dev);
+
+ if (dev)
+ dev.attachToInterface(this);
+ }
}
- }),
+ },
- getWirelessStatus: function() {
- return this.listDeviceNames().then(function(names) {
- _luci2.rpc.batch();
+ changeProtocol: function(proto)
+ {
+ var pr = _luci2.NetworkModel._protos[proto];
- for (var i = 0; i < names.length; i++)
- _luci2.wireless.getDeviceStatus(names[i]);
+ if (!pr)
+ return;
- return _luci2.rpc.flush();
- }).then(function(networks) {
- var rv = { };
+ for (var opt in (this.get() || { }))
+ {
+ switch (opt)
+ {
+ case 'type':
+ case 'ifname':
+ case 'macaddr':
+ if (pr.virtual)
+ this.set(opt, undefined);
+ break;
- var phy_attrs = [
- 'country', 'channel', 'frequency', 'frequency_offset',
- 'txpower', 'txpower_offset', 'hwmodes', 'hardware', 'phy'
- ];
+ case 'auto':
+ case 'mtu':
+ break;
- var net_attrs = [
- 'ssid', 'bssid', 'mode', 'quality', 'quality_max',
- 'signal', 'noise', 'bitrate', 'encryption'
- ];
+ case 'proto':
+ this.set(opt, pr.protocol);
+ break;
- for (var i = 0; i < networks.length; i++)
- {
- var phy = rv[networks[i].phy] || (
- rv[networks[i].phy] = { networks: [ ] }
- );
+ default:
+ this.set(opt, undefined);
+ break;
+ }
+ }
+ },
- var net = {
- device: networks[i].device
- };
+ createForm: function(mapwidget)
+ {
+ var self = this;
+ var proto = self.getProtocol();
+ var device = self.getDevice();
- for (var j = 0; j < phy_attrs.length; j++)
- phy[phy_attrs[j]] = networks[i][phy_attrs[j]];
+ if (!mapwidget)
+ mapwidget = _luci2.cbi.Map;
- for (var j = 0; j < net_attrs.length; j++)
- net[net_attrs[j]] = networks[i][net_attrs[j]];
+ var map = new mapwidget('network', {
+ caption: _luci2.tr('Configure "%s"').format(self.name())
+ });
- phy.networks.push(net);
- }
+ var section = map.section(_luci2.cbi.SingleSection, self.name(), {
+ anonymous: true
+ });
- return rv;
+ section.tab({
+ id: 'general',
+ caption: _luci2.tr('General Settings')
});
- },
- getAssocLists: function()
- {
- return this.listDeviceNames().then(function(names) {
- _luci2.rpc.batch();
+ section.tab({
+ id: 'advanced',
+ caption: _luci2.tr('Advanced Settings')
+ });
- for (var i = 0; i < names.length; i++)
- _luci2.wireless.getAssocList(names[i]);
+ section.tab({
+ id: 'ipv6',
+ caption: _luci2.tr('IPv6')
+ });
- return _luci2.rpc.flush();
- }).then(function(assoclists) {
- var rv = [ ];
+ section.tab({
+ id: 'physical',
+ caption: _luci2.tr('Physical Settings')
+ });
- for (var i = 0; i < assoclists.length; i++)
- for (var j = 0; j < assoclists[i].length; j++)
- rv.push(assoclists[i][j]);
- return rv;
+ section.taboption('general', _luci2.cbi.CheckboxValue, 'auto', {
+ caption: _luci2.tr('Start on boot'),
+ optional: true,
+ initial: true
});
- },
- formatEncryption: function(enc)
- {
- var format_list = function(l, s)
- {
- var rv = [ ];
- for (var i = 0; i < l.length; i++)
- rv.push(l[i].toUpperCase());
- return rv.join(s ? s : ', ');
- }
+ var pr = section.taboption('general', _luci2.cbi.ListValue, 'proto', {
+ caption: _luci2.tr('Protocol')
+ });
- if (!enc || !enc.enabled)
- return _luci2.tr('None');
+ pr.ucivalue = function(sid) {
+ return self.get('proto') || 'none';
+ };
- if (enc.wep)
- {
- if (enc.wep.length == 2)
- return _luci2.tr('WEP Open/Shared') + ' (%s)'.format(format_list(enc.ciphers, ', '));
- else if (enc.wep[0] == 'shared')
- return _luci2.tr('WEP Shared Auth') + ' (%s)'.format(format_list(enc.ciphers, ', '));
- else
- return _luci2.tr('WEP Open System') + ' (%s)'.format(format_list(enc.ciphers, ', '));
- }
- else if (enc.wpa)
- {
- if (enc.wpa.length == 2)
- return _luci2.tr('mixed WPA/WPA2') + ' %s (%s)'.format(
- format_list(enc.authentication, '/'),
- format_list(enc.ciphers, ', ')
- );
- else if (enc.wpa[0] == 2)
- return 'WPA2 %s (%s)'.format(
- format_list(enc.authentication, '/'),
- format_list(enc.ciphers, ', ')
- );
- else
- return 'WPA %s (%s)'.format(
- format_list(enc.authentication, '/'),
- format_list(enc.ciphers, ', ')
- );
- }
+ var ok = section.taboption('general', _luci2.cbi.ButtonValue, '_confirm', {
+ caption: _luci2.tr('Really switch?'),
+ description: _luci2.tr('Changing the protocol will clear all configuration for this interface!'),
+ text: _luci2.tr('Change protocol')
+ });
- return _luci2.tr('Unknown');
- }
- };
+ ok.on('click', function(ev) {
+ self.changeProtocol(pr.formvalue(ev.data.sid));
+ self.createForm(mapwidget).show();
+ });
- this.firewall = {
- getZoneColor: function(zone)
- {
- if ($.isPlainObject(zone))
- zone = zone.name;
+ var protos = _luci2.NetworkModel.getProtocols();
- if (zone == 'lan')
- return '#90f090';
- else if (zone == 'wan')
- return '#f09090';
+ for (var i = 0; i < protos.length; i++)
+ pr.value(protos[i].name, protos[i].description);
- for (var i = 0, hash = 0;
- i < zone.length;
- hash = zone.charCodeAt(i++) + ((hash << 5) - hash));
+ proto.populateForm(section, self);
- for (var i = 0, color = '#';
- i < 3;
- color += ('00' + ((hash >> i++ * 8) & 0xFF).tozoneing(16)).slice(-2));
+ if (!proto.virtual)
+ {
+ var br = section.taboption('physical', _luci2.cbi.CheckboxValue, 'type', {
+ caption: _luci2.tr('Network bridge'),
+ description: _luci2.tr('Merges multiple devices into one logical bridge'),
+ optional: true,
+ enabled: 'bridge',
+ disabled: '',
+ initial: ''
+ });
- return color;
- },
+ section.taboption('physical', _luci2.cbi.DeviceList, '__iface_multi', {
+ caption: _luci2.tr('Devices'),
+ multiple: true,
+ bridges: false
+ }).depends('type', true);
+
+ section.taboption('physical', _luci2.cbi.DeviceList, '__iface_single', {
+ caption: _luci2.tr('Device'),
+ multiple: false,
+ bridges: true
+ }).depends('type', false);
+
+ var mac = section.taboption('physical', _luci2.cbi.InputValue, 'macaddr', {
+ caption: _luci2.tr('Override MAC'),
+ optional: true,
+ placeholder: device ? device.getMACAddress() : undefined,
+ datatype: 'macaddr'
+ })
- findZoneByNetwork: function(network)
- {
- var self = this;
- var zone = undefined;
+ mac.ucivalue = function(sid)
+ {
+ if (device)
+ return device.get('macaddr');
- return _luci2.uci.foreach('firewall', 'zone', function(z) {
- if (!z.name || !z.network)
- return;
+ return this.callSuper('ucivalue', sid);
+ };
- if (!$.isArray(z.network))
- z.network = z.network.split(/\s+/);
+ mac.save = function(sid)
+ {
+ if (!this.changed(sid))
+ return false;
- for (var i = 0; i < z.network.length; i++)
+ if (device)
+ device.set('macaddr', this.formvalue(sid));
+ else
+ this.callSuper('set', sid);
+
+ return true;
+ };
+ }
+
+ section.taboption('physical', _luci2.cbi.InputValue, 'mtu', {
+ caption: _luci2.tr('Override MTU'),
+ optional: true,
+ placeholder: device ? device.getMTU() : undefined,
+ datatype: 'range(1, 9000)'
+ });
+
+ section.taboption('physical', _luci2.cbi.InputValue, 'metric', {
+ caption: _luci2.tr('Override Metric'),
+ optional: true,
+ placeholder: 0,
+ datatype: 'uinteger'
+ });
+
+ for (var field in section.fields)
+ {
+ switch (field)
{
- if (z.network[i] == network)
- {
- zone = z;
- break;
- }
+ case 'proto':
+ break;
+
+ case '_confirm':
+ for (var i = 0; i < protos.length; i++)
+ if (protos[i].name != (this.get('proto') || 'none'))
+ section.fields[field].depends('proto', protos[i].name);
+ break;
+
+ default:
+ section.fields[field].depends('proto', this.get('proto') || 'none', true);
+ break;
}
- }).then(function() {
- if (zone)
- zone.color = self.getZoneColor(zone);
+ }
- return zone;
- });
+ return map;
}
- };
+ });
+
+ this.NetworkModel.Protocol = this.NetworkModel.Interface.extend({
+ description: '__unknown__',
+ tunnel: false,
+ virtual: false,
+
+ populateForm: function(section, iface)
+ {
+
+ }
+ });
this.system = {
getSystemInfo: _luci2.rpc.declare({
window.clearInterval(this._hearbeatInterval);
delete this._hearbeatInterval;
}
+ },
+
+
+ _acls: { },
+
+ _fetch_acls: _luci2.rpc.declare({
+ object: 'session',
+ method: 'access',
+ expect: { '': { } }
+ }),
+
+ _fetch_acls_cb: function(acls)
+ {
+ _luci2.session._acls = acls;
+ },
+
+ updateACLs: function()
+ {
+ return _luci2.session._fetch_acls()
+ .then(_luci2.session._fetch_acls_cb);
+ },
+
+ hasACL: function(scope, object, func)
+ {
+ var acls = _luci2.session._acls;
+
+ if (typeof(func) == 'undefined')
+ return (acls && acls[scope] && acls[scope][object]);
+
+ if (acls && acls[scope] && acls[scope][object])
+ for (var i = 0; i < acls[scope][object].length; i++)
+ if (acls[scope][object][i] == func)
+ return true;
+
+ return false;
}
};
var state = _luci2.ui._loading || (_luci2.ui._loading = {
modal: $('<div />')
+ .css('z-index', 2000)
.addClass('modal fade')
.append($('<div />')
.addClass('modal-dialog')
{
state.dialog.modal('hide');
- return;
+ return state.dialog;
}
var cnt = state.dialog.children().children().children('div.modal-body');
var ftr = state.dialog.children().children().children('div.modal-footer');
- ftr.empty();
+ ftr.empty().show();
if (options.style == 'confirm')
{
.attr('disabled', true));
}
+ if (options.wide)
+ {
+ state.dialog.addClass('wide');
+ }
+ else
+ {
+ state.dialog.removeClass('wide');
+ }
+
state.dialog.find('h4:first').text(title);
state.dialog.modal('show');
cnt.empty().append(content);
+
+ return state.dialog;
},
upload: function(title, content, options)
}
}),
+ _render_change_indicator: function()
+ {
+ return $('<ul />')
+ .addClass('nav navbar-nav navbar-right')
+ .append($('<li />')
+ .append($('<a />')
+ .attr('id', 'changes')
+ .attr('href', '#')
+ .append($('<span />')
+ .addClass('label label-info'))));
+ },
+
renderMainMenu: _luci2.rpc.declare({
object: 'luci2.ui',
method: 'menu',
$('#mainmenu')
.empty()
- .append(_luci2.globals.mainMenu.render(0, 1));
+ .append(_luci2.globals.mainMenu.render(0, 1))
+ .append(_luci2.ui._render_change_indicator());
}
}),
renderView: function()
{
- var node = arguments[0];
- var name = node.view.split(/\//).join('.');
- var args = [ ];
+ var node = arguments[0];
+ var name = node.view.split(/\//).join('.');
+ var cname = _luci2.toClassName(name);
+ var views = _luci2.views || (_luci2.views = { });
+ var args = [ ];
for (var i = 1; i < arguments.length; i++)
args.push(arguments[i]);
_luci2.globals.currentView.finish();
_luci2.ui.renderViewMenu();
-
- if (!_luci2._views)
- _luci2._views = { };
-
_luci2.setHash('view', node.view);
- if (_luci2._views[name] instanceof _luci2.ui.view)
+ if (views[cname] instanceof _luci2.ui.view)
{
- _luci2.globals.currentView = _luci2._views[name];
- return _luci2._views[name].render.apply(_luci2._views[name], args);
+ _luci2.globals.currentView = views[cname];
+ return views[cname].render.apply(views[cname], args);
}
var url = _luci2.globals.resource + '/view/' + name + '.js';
var viewConstructor = eval(viewConstructorSource);
- _luci2._views[name] = new viewConstructor({
+ views[cname] = new viewConstructor({
name: name,
acls: node.write || { }
});
- _luci2.globals.currentView = _luci2._views[name];
- return _luci2._views[name].render.apply(_luci2._views[name], args);
+ _luci2.globals.currentView = views[cname];
+ return views[cname].render.apply(views[cname], args);
}
catch(e) {
alert('Unable to instantiate view "%s": %s'.format(url, e));
});
},
+ changeView: function()
+ {
+ var name = _luci2.getHash('view');
+ var node = _luci2.globals.defaultNode;
+
+ if (name && _luci2.globals.mainMenu)
+ node = _luci2.globals.mainMenu.getNode(name);
+
+ if (node)
+ {
+ _luci2.ui.loading(true);
+ _luci2.ui.renderView(node).then(function() {
+ _luci2.ui.loading(false);
+ });
+ }
+ },
+
updateHostname: function()
{
return _luci2.system.getBoardInfo().then(function(info) {
switch (c[0])
{
case 'order':
+ log.push('uci reorder %s.<ins>%s=<strong>%s</strong></ins>'.format(config, c[1], c[2]));
break;
case 'remove':
_luci2.ui.loading(true);
$.when(
+ _luci2.session.updateACLs(),
_luci2.ui.updateHostname(),
_luci2.ui.updateChanges(),
- _luci2.ui.renderMainMenu()
+ _luci2.ui.renderMainMenu(),
+ _luci2.NetworkModel.init()
).then(function() {
_luci2.ui.renderView(_luci2.globals.defaultNode).then(function() {
_luci2.ui.loading(false);
- })
+ });
+
+ $(window).on('hashchange', function() {
+ _luci2.ui.changeView();
+ });
});
},
_onclick: function(ev)
{
- _luci2.ui.loading(true);
- _luci2.ui.renderView(ev.data).then(function() {
- _luci2.ui.loading(false);
- });
+ _luci2.setHash('view', ev.data);
ev.preventDefault();
this.blur();
}
else
{
- item.find('a').click(nodes[i], this._onclick);
+ item.find('a').click(nodes[i].view, this._onclick);
}
}
}
});
+ this.cbi.ButtonValue = this.cbi.AbstractValue.extend({
+ widget: function(sid)
+ {
+ this.options.optional = true;
+
+ var btn = $('<button />')
+ .addClass('btn btn-default')
+ .attr('id', this.id(sid))
+ .attr('type', 'button')
+ .text(this.label('text'));
+
+ return this.validator(sid, btn);
+ }
+ });
+
this.cbi.NetworkList = this.cbi.AbstractValue.extend({
load: function(sid)
{
- var self = this;
-
- if (!self.interfaces)
- {
- self.interfaces = [ ];
- return _luci2.network.getNetworkStatus().then(function(ifaces) {
- self.interfaces = ifaces;
- self = null;
- });
- }
-
- return undefined;
+ return _luci2.NetworkModel.init();
},
_device_icon: function(dev)
{
- var type = 'ethernet';
- var desc = _luci2.tr('Ethernet device');
-
- if (dev.type == 'IP tunnel')
- {
- type = 'tunnel';
- desc = _luci2.tr('Tunnel interface');
- }
- else if (dev['bridge-members'])
- {
- type = 'bridge';
- desc = _luci2.tr('Bridge');
- }
- else if (dev.wireless)
- {
- type = 'wifi';
- desc = _luci2.tr('Wireless Network');
- }
- else if (dev.device.indexOf('.') > 0)
- {
- type = 'vlan';
- desc = _luci2.tr('VLAN interface');
- }
-
return $('<img />')
- .attr('src', _luci2.globals.resource + '/icons/' + type + (dev.up ? '' : '_disabled') + '.png')
- .attr('title', '%s (%s)'.format(desc, dev.device));
+ .attr('src', dev.icon())
+ .attr('title', '%s (%s)'.format(dev.description(), dev.name() || '?'));
},
widget: function(sid)
for (var i = 0; i < value.length; i++)
check[value[i]] = true;
- if (this.interfaces)
+ var interfaces = _luci2.NetworkModel.getInterfaces();
+
+ for (var i = 0; i < interfaces.length; i++)
{
- for (var i = 0; i < this.interfaces.length; i++)
- {
- var iface = this.interfaces[i];
- var badge = $('<span />')
- .addClass('badge')
- .text('%s: '.format(iface['interface']));
-
- if (iface.device && iface.device.subdevices)
- for (var j = 0; j < iface.device.subdevices.length; j++)
- badge.append(this._device_icon(iface.device.subdevices[j]));
- else if (iface.device)
- badge.append(this._device_icon(iface.device));
- else
- badge.append($('<em />').text(_luci2.tr('(No devices attached)')));
+ 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(_luci2.tr('(No devices attached)')));
- $('<li />')
- .append($('<label />')
- .addClass(itype + ' inline')
- .append($('<input />')
- .attr('name', itype + id)
- .attr('type', itype)
- .attr('value', iface['interface'])
- .prop('checked', !!check[iface['interface']]))
- .append(badge))
- .appendTo(ul);
- }
+ $('<li />')
+ .append($('<label />')
+ .addClass(itype + ' inline')
+ .append($('<input />')
+ .attr('name', itype + id)
+ .attr('type', itype)
+ .attr('value', iface.name())
+ .prop('checked', !!check[iface.name()]))
+ .append(badge))
+ .appendTo(ul);
}
if (!this.options.multiple)
.attr('name', itype + id)
.attr('type', itype)
.attr('value', '')
- .prop('checked', !value))
+ .prop('checked', $.isEmptyObject(check)))
.append(_luci2.tr('unspecified')))
.appendTo(ul);
}
sections: function(cb)
{
- var s1 = this.map.ucisections(this.map.uci_package);
+ var s1 = _luci2.uci.sections(this.map.uci_package);
var s2 = [ ];
for (var i = 0; i < s1.length; i++)
var addb = text.next();
var errt = addb.next();
var name = text.val();
- var used = false;
if (!/^[a-zA-Z0-9_]*$/.test(name))
{
return false;
}
- for (var sid in self.map.uci.values[self.map.uci_package])
- if (sid == name)
- {
- used = true;
- break;
- }
-
- for (var sid in self.map.uci.creates[self.map.uci_package])
- if (sid == name)
- {
- used = true;
- break;
- }
-
- if (used)
+ if (_luci2.uci.get(self.map.uci_package, name))
{
errt.text(_luci2.tr('Name already used')).show();
text.addClass('error');
if (new_idx >= 0 && new_idx < s.length)
{
- var tmp = s[cur_idx]['.index'];
-
- s[cur_idx]['.index'] = s[new_idx]['.index'];
- s[new_idx]['.index'] = tmp;
-
- if (self.active_panel == cur_idx)
- self.active_panel = new_idx;
- else if (self.active_panel == new_idx)
- self.active_panel = cur_idx;
-
- self.map.uci.reorder = true;
+ _luci2.uci.swap(self.map.uci_package, s[cur_idx]['.name'], s[new_idx]['.name']);
self.map.save();
self.map.redraw();
sections: function(cb)
{
var sa = [ ];
- var pkg = this.map.uci.values[this.map.uci_package];
+ var sl = _luci2.uci.sections(this.map.uci_package);
- for (var s in pkg)
- if (pkg[s]['.name'] == this.uci_type)
+ for (var i = 0; i < sl.length; i++)
+ if (sl[i]['.name'] == this.uci_type)
{
- sa.push(pkg[s]);
+ sa.push(sl[i]);
break;
}
if (typeof(cb) == 'function' && sa.length > 0)
- cb.apply(this, [ sa[0] ]);
+ cb.call(this, sa[0]);
return sa;
}
});
+ this.cbi.SingleSection = this.cbi.NamedSection.extend({
+ render: function()
+ {
+ this.instance = { };
+ this.instance[this.uci_type] = { tabs: [ ] };
+
+ return this._render_section_body(this.uci_type, 0);
+ }
+ });
+
this.cbi.DummySection = this.cbi.TypedSection.extend({
sections: function(cb)
{
this.sections = [ ];
this.options = _luci2.defaults(options, {
save: function() { },
- prepare: function() {
- return _luci2.uci.writable(function(writable) {
- self.options.readonly = !writable;
- });
- }
+ prepare: function() { }
});
},
- _load_cb: function(packages)
+ _load_cb: function()
{
- for (var i = 0; i < packages.length; i++)
- {
- this.uci.values[packages[i]['.package']] = packages[i];
- delete packages[i]['.package'];
- }
-
var deferreds = [ _luci2.deferrable(this.options.prepare()) ];
for (var i = 0; i < this.sections.length; i++)
load: function()
{
var self = this;
-
- this.uci = {
- newid: 0,
- values: { },
- creates: { },
- changes: { },
- deletes: { },
- reorder: false
- };
-
var packages = { };
for (var i = 0; i < this.sections.length; i++)
packages[this.uci_package] = true;
- _luci2.rpc.batch();
-
for (var pkg in packages)
- _luci2.uci.get_all(pkg);
+ if (!_luci2.uci.writable(pkg))
+ this.options.readonly = true;
- return _luci2.rpc.flush().then(function(packages) {
- return self._load_cb(packages);
+ return _luci2.uci.load(_luci2.toArray(packages)).then(function() {
+ return self._load_cb();
});
},
add: function(conf, type, name)
{
- var c = this.uci.creates;
- var s = '.new.%d'.format(this.uci.newid++);
-
- if (!c[conf])
- c[conf] = { };
-
- c[conf][s] = {
- '.type': type,
- '.name': s,
- '.create': name,
- '.anonymous': !name,
- '.index': 1000 + this.uci.newid
- };
-
- return s;
+ return _luci2.uci.add(conf, type, name);
},
remove: function(conf, sid)
{
- var n = this.uci.creates;
- var c = this.uci.changes;
- var d = this.uci.deletes;
-
- /* requested deletion of a just created section */
- if (sid.indexOf('.new.') == 0)
- {
- if (n[conf])
- delete n[conf][sid];
- }
- else
- {
- if (c[conf])
- delete c[conf][sid];
-
- if (!d[conf])
- d[conf] = { };
-
- d[conf][sid] = true;
- }
- },
-
- ucisections: function(conf, cb)
- {
- var sa = [ ];
- var pkg = this.uci.values[conf];
- var crt = this.uci.creates[conf];
- var del = this.uci.deletes[conf];
-
- if (!pkg)
- return sa;
-
- for (var s in pkg)
- if (!del || del[s] !== true)
- sa.push(pkg[s]);
-
- if (crt)
- for (var s in crt)
- sa.push(crt[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.apply(this, [ sa[i] ]);
-
- return sa;
+ return _luci2.uci.remove(conf, sid);
},
get: function(conf, sid, opt)
{
- var v = this.uci.values;
- var n = this.uci.creates;
- var c = this.uci.changes;
- var d = this.uci.deletes;
-
- /* requested option in a just created section */
- if (sid.indexOf('.new.') == 0)
- {
- if (!n[conf])
- return undefined;
-
- if (typeof(opt) == 'undefined')
- return (n[conf][sid] || { });
-
- return n[conf][sid][opt];
- }
-
- /* requested an option value */
- if (typeof(opt) != 'undefined')
- {
- /* 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] && typeof(c[conf][sid][opt]) != 'undefined')
- 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;
+ return _luci2.uci.get(conf, sid, opt);
},
set: function(conf, sid, opt, val)
{
- var n = this.uci.creates;
- var c = this.uci.changes;
- var d = this.uci.deletes;
-
- if (sid.indexOf('.new.') == 0)
- {
- if (n[conf] && n[conf][sid])
- {
- if (typeof(val) != 'undefined')
- n[conf][sid][opt] = val;
- else
- delete n[conf][sid][opt];
- }
- }
- else if (typeof(val) != 'undefined')
- {
- if (!c[conf])
- c[conf] = { };
-
- if (!c[conf][sid])
- c[conf][sid] = { };
-
- c[conf][sid][opt] = val;
- }
- else
- {
- if (!d[conf])
- d[conf] = { };
-
- if (!d[conf][sid])
- d[conf][sid] = [ ];
-
- d[conf][sid].push(opt);
- }
+ return _luci2.uci.set(conf, sid, opt, val);
},
validate: function()
save: function()
{
- if (this.options.readonly)
+ var self = this;
+
+ if (self.options.readonly)
return _luci2.deferrable();
- var deferreds = [ _luci2.deferrable(this.options.save()) ];
+ var deferreds = [ ];
- for (var i = 0; i < this.sections.length; i++)
+ for (var i = 0; i < self.sections.length; i++)
{
- if (this.sections[i].options.readonly)
+ if (self.sections[i].options.readonly)
continue;
- for (var f in this.sections[i].fields)
+ for (var f in self.sections[i].fields)
{
- if (typeof(this.sections[i].fields[f].save) != 'function')
+ if (typeof(self.sections[i].fields[f].save) != 'function')
continue;
- var s = this.sections[i].sections();
+ var s = self.sections[i].sections();
for (var j = 0; j < s.length; j++)
{
- var rv = this.sections[i].fields[f].save(s[j]['.name']);
+ var rv = self.sections[i].fields[f].save(s[j]['.name']);
if (_luci2.isDeferred(rv))
deferreds.push(rv);
}
}
}
- return $.when.apply($, deferreds);
- },
-
- _send_uci_reorder: function()
- {
- if (!this.uci.reorder)
- return _luci2.deferrable();
-
- _luci2.rpc.batch();
-
- /*
- gather all created and existing sections, sort them according
- to their index value and issue an uci order call
- */
- for (var c in this.uci.values)
- {
- var o = [ ];
-
- if (this.uci.creates && this.uci.creates[c])
- for (var s in this.uci.creates[c])
- o.push(this.uci.creates[c][s]);
-
- for (var s in this.uci.values[c])
- o.push(this.uci.values[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']);
-
- _luci2.uci.order(c, sids);
- }
- }
-
- return _luci2.rpc.flush();
- },
-
- _send_uci: function()
- {
- _luci2.rpc.batch();
-
- var self = this;
- var snew = [ ];
-
- if (this.uci.creates)
- for (var c in this.uci.creates)
- for (var s in this.uci.creates[c])
- {
- var r = {
- config: c,
- values: { }
- };
-
- for (var k in this.uci.creates[c][s])
- {
- if (k == '.type')
- r.type = this.uci.creates[c][s][k];
- else if (k == '.create')
- r.name = this.uci.creates[c][s][k];
- else if (k.charAt(0) != '.')
- r.values[k] = this.uci.creates[c][s][k];
- }
-
- snew.push(this.uci.creates[c][s]);
-
- _luci2.uci.add(r.config, r.type, r.name, r.values);
- }
-
- if (this.uci.changes)
- for (var c in this.uci.changes)
- for (var s in this.uci.changes[c])
- _luci2.uci.set(c, s, this.uci.changes[c][s]);
-
- if (this.uci.deletes)
- for (var c in this.uci.deletes)
- for (var s in this.uci.deletes[c])
- {
- var o = this.uci.deletes[c][s];
- _luci2.uci['delete'](c, s, (o === true) ? undefined : o);
- }
-
- return _luci2.rpc.flush().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._send_uci_reorder();
+ return $.when.apply($, deferreds).then(function() {
+ return _luci2.deferrable(self.options.save());
});
},
_luci2.ui.loading(true);
return this.save().then(function() {
- return self._send_uci();
+ return _luci2.uci.save();
}).then(function() {
return _luci2.ui.updateChanges();
}).then(function() {