for (var i = 0, color = '#';
i < 3;
- color += ('00' + ((hash >> i++ * 8) & 0xFF).tozoneing(16)).slice(-2));
+ color += ('00' + ((hash >> i++ * 8) & 0xFF).tostring(16)).slice(-2));
return color;
},
}
};
+ 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];
+
+ if (!(dev instanceof _luci2.NetworkModel.Device))
+ dev = _luci2.NetworkModel.getDevice(dev);
+
+ if (dev)
+ dev.attachToInterface(this);
+ }
+ }
+ },
+
+ changeProtocol: function(proto)
+ {
+ var pr = _luci2.NetworkModel._protos[proto];
+
+ if (!pr)
+ return;
+
+ for (var opt in (this.get() || { }))
+ {
+ switch (opt)
+ {
+ case 'type':
+ case 'ifname':
+ case 'macaddr':
+ if (pr.virtual)
+ this.set(opt, undefined);
+ break;
+
+ case 'auto':
+ case 'mtu':
+ break;
+
+ case 'proto':
+ this.set(opt, pr.protocol);
+ break;
+
+ default:
+ this.set(opt, undefined);
+ break;
+ }
+ }
+ },
+
+ createForm: function(mapwidget)
+ {
+ var self = this;
+ var proto = self.getProtocol();
+ var device = self.getDevice();
+
+ if (!mapwidget)
+ mapwidget = _luci2.cbi.Map;
+
+ var map = new mapwidget('network', {
+ caption: _luci2.tr('Configure "%s"').format(self.name())
+ });
+
+ var section = map.section(_luci2.cbi.SingleSection, self.name(), {
+ anonymous: true
+ });
+
+ section.tab({
+ id: 'general',
+ caption: _luci2.tr('General Settings')
+ });
+
+ section.tab({
+ id: 'advanced',
+ caption: _luci2.tr('Advanced Settings')
+ });
+
+ section.tab({
+ id: 'ipv6',
+ caption: _luci2.tr('IPv6')
+ });
+
+ section.tab({
+ id: 'physical',
+ caption: _luci2.tr('Physical Settings')
+ });
+
+
+ section.taboption('general', _luci2.cbi.CheckboxValue, 'auto', {
+ caption: _luci2.tr('Start on boot'),
+ optional: true,
+ initial: true
+ });
+
+ var pr = section.taboption('general', _luci2.cbi.ListValue, 'proto', {
+ caption: _luci2.tr('Protocol')
+ });
+
+ pr.ucivalue = function(sid) {
+ return self.get('proto') || 'none';
+ };
+
+ 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')
+ });
+
+ ok.on('click', function(ev) {
+ self.changeProtocol(pr.formvalue(ev.data.sid));
+ self.createForm(mapwidget).show();
+ });
+
+ var protos = _luci2.NetworkModel.getProtocols();
+
+ for (var i = 0; i < protos.length; i++)
+ pr.value(protos[i].name, protos[i].description);
+
+ proto.populateForm(section, self);
+
+ 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: ''
+ });
+
+ 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'
+ })
+
+ mac.ucivalue = function(sid)
+ {
+ if (device)
+ return device.get('macaddr');
+
+ return this.callSuper('ucivalue', sid);
+ };
+
+ mac.save = function(sid)
+ {
+ if (!this.changed(sid))
+ return false;
+
+ 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)
+ {
+ 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;
+ }
+ }
+
+ 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({
object: 'system',
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));
};
- return $.Deferred().resolve();
- });
+ return $.Deferred().resolve();
+ });
+ },
+
+ 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()
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':
if (n > 0)
$('#changes')
- .empty()
- .show()
- .append($('<a />')
- .attr('href', '#')
- .addClass('label')
- .addClass('notice')
- .text(_luci2.trcp('Pending configuration changes', '1 change', '%d changes', n).format(n))
- .click(function(ev) {
- _luci2.ui.dialog(_luci2.tr('Staged configuration changes'), html, { style: 'close' });
- ev.preventDefault();
- }));
+ .click(function(ev) {
+ _luci2.ui.dialog(_luci2.tr('Staged configuration changes'), html, {
+ style: 'confirm',
+ confirm: function() {
+ _luci2.uci.apply().then(
+ function(code) { alert('Success with code ' + code); },
+ function(code) { alert('Error with code ' + code); }
+ );
+ }
+ });
+ ev.preventDefault();
+ })
+ .children('span')
+ .show()
+ .text(_luci2.trcp('Pending configuration changes', '1 change', '%d changes', n).format(n));
else
- $('#changes')
- .hide();
+ $('#changes').children('span').hide();
});
},
_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.instance = { };
this.dependencies = [ ];
this.rdependency = { };
+ this.events = { };
this.options = _luci2.defaults(options, {
placeholder: '',
return i.top;
},
+ active: function(sid)
+ {
+ return (this.instance[sid] && !this.instance[sid].disabled);
+ },
+
ucipath: function(sid)
{
return {
}
if (rv)
+ {
for (var field in d.self.rdependency)
d.self.rdependency[field].toggle(d.sid);
+ d.self.section.tabtoggle(d.sid);
+ }
+
return rv;
},
validator: function(sid, elem, multi)
{
+ var evdata = {
+ self: this,
+ sid: sid,
+ elem: elem,
+ multi: multi,
+ inst: this.instance[sid],
+ opt: this.options.optional
+ };
+
+ for (var evname in this.events)
+ elem.on(evname, evdata, this.events[evname]);
+
if (typeof(this.options.datatype) == 'undefined' && $.isEmptyObject(this.rdependency))
return elem;
if (typeof(this.options.datatype) == 'string')
{
try {
- vstack = _luci2.cbi.validation.compile(this.options.datatype);
+ evdata.vstack = _luci2.cbi.validation.compile(this.options.datatype);
} catch(e) { };
}
else if (typeof(this.options.datatype) == 'function')
{
var vfunc = this.options.datatype;
- vstack = [ function(elem) {
+ evdata.vstack = [ function(elem) {
var rv = vfunc(this, elem);
if (rv !== true)
validation.message = rv;
}, [ elem ] ];
}
- var evdata = {
- self: this,
- sid: sid,
- elem: elem,
- multi: multi,
- vstack: vstack,
- inst: this.instance[sid],
- opt: this.options.optional
- };
-
if (elem.prop('tagName') == 'SELECT')
{
elem.change(evdata, this._ev_validate);
return (i.disabled || i.error.text() == '');
},
- depends: function(d, v)
+ depends: function(d, v, add)
{
var dep;
if ($.isEmptyObject(dep))
return this;
- this.dependencies.push(dep);
+ if (!add || !this.dependencies.length)
+ this.dependencies.push(dep);
+ else
+ for (var i = 0; i < this.dependencies.length; i++)
+ $.extend(this.dependencies[i], dep);
return this;
},
break;
}
}
- else if (typeof(cmp) == 'string')
+ else if (typeof(cmp) == 'string' || typeof(cmp) == 'number')
{
if (val != cmp)
{
}
return false;
+ },
+
+ on: function(evname, evfunc)
+ {
+ this.events[evname] = evfunc;
+ return this;
}
});
var s = $('<select />')
.addClass('form-control');
- if (this.options.optional)
+ if (this.options.optional && !this.has_empty)
$('<option />')
.attr('value', '')
.text(_luci2.tr('-- Please choose --'))
if (!this.choices)
this.choices = [ ];
+ if (k == '')
+ this.has_empty = true;
+
this.choices.push([k, v || k]);
return this;
}
{
ev.data.select.hide();
ev.data.input.show().focus();
-
- var v = ev.data.input.val();
- ev.data.input.val(' ');
- ev.data.input.val(v);
+ ev.data.input.val('');
}
else if (self.options.optional && s.selectedIndex == 0)
{
{
ev.data.input.val(ev.data.select.val());
}
+
+ ev.stopPropagation();
},
_blur: function(ev)
ev.data.select.empty();
- if (self.options.optional)
+ if (self.options.optional && !self.has_empty)
$('<option />')
.attr('value', '')
.text(_luci2.tr('-- please choose --'))
.appendTo(ev.data.select);
ev.data.input.hide();
- ev.data.select.val(val).show().focus();
+ ev.data.select.val(val).show().blur();
},
_enter: function(ev)
.attr('id', this.id(sid));
var t = $('<input />')
+ .addClass('form-control')
.attr('type', 'text')
.hide()
.appendTo(d);
var s = $('<select />')
+ .addClass('form-control')
.appendTo(d);
var evdata = {
self: this,
- input: this.validator(sid, t),
- select: this.validator(sid, s)
+ input: t,
+ select: s
};
s.change(evdata, this._change);
t.val(this.ucivalue(sid));
t.blur();
+ this.validator(sid, t);
+ this.validator(sid, s);
+
return d;
},
if (!this.choices)
this.choices = [ ];
+ if (k == '')
+ this.has_empty = true;
+
this.choices.push([k, v || k]);
return this;
},
formvalue: function(sid)
{
var rv = [ ];
- var fields = $('#' + this.id(sid) + ' > input');
+ var fields = $('#' + this.id(sid) + ' input');
for (var i = 0; i < fields.length; i++)
if (typeof(fields[i].value) == 'string' && fields[i].value.length)
}
});
- this.cbi.NetworkList = this.cbi.AbstractValue.extend({
- load: function(sid)
+ this.cbi.ButtonValue = this.cbi.AbstractValue.extend({
+ widget: function(sid)
{
- var self = this;
+ this.options.optional = true;
- if (!self.interfaces)
- {
- self.interfaces = [ ];
- return _luci2.network.getNetworkStatus().then(function(ifaces) {
- self.interfaces = ifaces;
- self = null;
- });
- }
+ var btn = $('<button />')
+ .addClass('btn btn-default')
+ .attr('id', this.id(sid))
+ .attr('type', 'button')
+ .text(this.label('text'));
- return undefined;
+ return this.validator(sid, btn);
+ }
+ });
+
+ this.cbi.NetworkList = this.cbi.AbstractValue.extend({
+ load: function(sid)
+ {
+ 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)
- {
- for (var i = 0; i < this.interfaces.length; i++)
- {
- var iface = this.interfaces[i];
- var badge = $('<span />')
- .addClass('badge')
- .text('%s: '.format(iface['interface']));
+ var interfaces = _luci2.NetworkModel.getInterfaces();
- 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)')));
+ 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(_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);
}
}
});
+ this.cbi.DeviceList = this.cbi.NetworkList.extend({
+ _ev_focus: function(ev)
+ {
+ var self = ev.data.self;
+ var input = $(this);
+
+ input.parent().prev().prop('checked', true);
+ },
+
+ _ev_blur: function(ev)
+ {
+ ev.which = 10;
+ ev.data.self._ev_keydown.call(this, ev);
+ },
+
+ _ev_keydown: function(ev)
+ {
+ if (ev.which != 10 && ev.which != 13)
+ return;
+
+ var sid = ev.data.sid;
+ var self = ev.data.self;
+ var input = $(this);
+ var ifnames = _luci2.toArray(input.val());
+
+ if (!ifnames.length)
+ return;
+
+ _luci2.NetworkModel.createDevice(ifnames[0]);
+
+ self._redraw(sid, $('#' + self.id(sid)), ifnames[0]);
+ },
+
+ load: function(sid)
+ {
+ return _luci2.NetworkModel.init();
+ },
+
+ _redraw: function(sid, ul, sel)
+ {
+ var id = ul.attr('id');
+ var devs = _luci2.NetworkModel.getDevices();
+ var iface = _luci2.NetworkModel.getInterface(sid);
+ var itype = this.options.multiple ? 'checkbox' : 'radio';
+ var check = { };
+
+ if (!sel)
+ {
+ for (var i = 0; i < devs.length; i++)
+ if (devs[i].isInNetwork(iface))
+ check[devs[i].name()] = true;
+ }
+ else
+ {
+ if (this.options.multiple)
+ check = _luci2.toObject(this.formvalue(sid));
+
+ check[sel] = true;
+ }
+
+ ul.empty();
+
+ for (var i = 0; i < devs.length; i++)
+ {
+ var dev = devs[i];
+
+ if (dev.isBridge() && this.options.bridges === false)
+ continue;
+
+ if (!dev.isBridgeable() && this.options.multiple)
+ continue;
+
+ var badge = $('<span />')
+ .addClass('badge')
+ .append($('<img />').attr('src', dev.icon()))
+ .append(' %s: %s'.format(dev.name(), dev.description()));
+
+ //var ifcs = dev.getInterfaces();
+ //if (ifcs.length)
+ //{
+ // for (var j = 0; j < ifcs.length; j++)
+ // badge.append((j ? ', ' : ' (') + ifcs[j].name());
+ //
+ // badge.append(')');
+ //}
+
+ $('<li />')
+ .append($('<label />')
+ .addClass(itype + ' inline')
+ .append($('<input />')
+ .attr('name', itype + id)
+ .attr('type', itype)
+ .attr('value', dev.name())
+ .prop('checked', !!check[dev.name()]))
+ .append(badge))
+ .appendTo(ul);
+ }
+
+
+ $('<li />')
+ .append($('<label />')
+ .attr('for', 'custom' + id)
+ .addClass(itype + ' inline')
+ .append($('<input />')
+ .attr('name', itype + id)
+ .attr('type', itype)
+ .attr('value', ''))
+ .append($('<span />')
+ .addClass('badge')
+ .append($('<input />')
+ .attr('id', 'custom' + id)
+ .attr('type', 'text')
+ .attr('placeholder', _luci2.tr('Custom device …'))
+ .on('focus', { self: this, sid: sid }, this._ev_focus)
+ .on('blur', { self: this, sid: sid }, this._ev_blur)
+ .on('keydown', { self: this, sid: sid }, this._ev_keydown))))
+ .appendTo(ul);
+
+ if (!this.options.multiple)
+ {
+ $('<li />')
+ .append($('<label />')
+ .addClass(itype + ' inline text-muted')
+ .append($('<input />')
+ .attr('name', itype + id)
+ .attr('type', itype)
+ .attr('value', '')
+ .prop('checked', $.isEmptyObject(check)))
+ .append(_luci2.tr('unspecified')))
+ .appendTo(ul);
+ }
+ },
+
+ widget: function(sid)
+ {
+ var id = this.id(sid);
+ var ul = $('<ul />')
+ .attr('id', id)
+ .addClass('list-unstyled');
+
+ this._redraw(sid, ul);
+
+ return ul;
+ },
+
+ save: function(sid)
+ {
+ if (this.instance[sid].disabled)
+ return;
+
+ var ifnames = this.formvalue(sid);
+ //if (!ifnames)
+ // return;
+
+ var iface = _luci2.NetworkModel.getInterface(sid);
+ if (!iface)
+ return;
+
+ iface.setDevices($.isArray(ifnames) ? ifnames : [ ifnames ]);
+ }
+ });
+
this.cbi.AbstractSection = this.ui.AbstractWidget.extend({
id: function()
return w;
},
+ tabtoggle: function(sid)
+ {
+ for (var i = 0; i < this.tabs.length; i++)
+ {
+ var tab = this.tabs[i];
+ var elem = $('#' + this.id('nodetab', sid, tab.id));
+ var empty = true;
+
+ for (var j = 0; j < tab.fields.length; j++)
+ {
+ if (tab.fields[j].active(sid))
+ {
+ empty = false;
+ break;
+ }
+ }
+
+ if (empty && elem.is(':visible'))
+ elem.fadeOut();
+ else if (!empty)
+ elem.fadeIn();
+ }
+ },
+
ucipackages: function(pkg)
{
for (var i = 0; i < this.tabs.length; i++)
validate: function()
{
- this.error_count = 0;
-
+ var errors = 0;
var as = this.sections();
for (var i = 0; i < as.length; i++)
var invals = this.validate_section(as[i]['.name']);
if (invals > 0)
- this.error_count += invals;
+ errors += invals;
}
var badge = $('#' + this.id('sectiontab')).children('span:first');
- if (this.error_count > 0)
+ if (errors > 0)
badge.show()
- .text(this.error_count)
- .attr('title', _luci2.trp('1 Error', '%d Errors', this.error_count).format(this.error_count));
+ .text(errors)
+ .attr('title', _luci2.trp('1 Error', '%d Errors', errors).format(errors));
else
badge.hide();
- return (this.error_count == 0);
+ return (errors == 0);
}
});
this.options = options;
this.tabs = [ ];
this.fields = { };
- this.error_count = 0;
this.active_panel = 0;
this.active_tab = { };
},
if (typeof(cb) == 'function')
for (var i = 0; i < s2.length; i++)
- cb.apply(this, [ s2[i] ]);
+ cb.call(this, s2[i]);
return s2;
},
if (cur == tab_index)
tabh.addClass('active');
+ if (!tab.fields.length)
+ tabh.hide();
+
return tabh;
},
}
});
+ 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)
{
return body;
},
+ _render_footer: function()
+ {
+ return $('<div />')
+ .addClass('panel panel-default panel-body text-right')
+ .append($('<div />')
+ .addClass('btn-group')
+ .append(_luci2.ui.button(_luci2.tr('Save & Apply'), 'primary')
+ .click({ self: this }, function(ev) { }))
+ .append(_luci2.ui.button(_luci2.tr('Save'), 'default')
+ .click({ self: this }, function(ev) { ev.data.self.send(); }))
+ .append(_luci2.ui.button(_luci2.tr('Reset'), 'default')
+ .click({ self: this }, function(ev) { ev.data.self.insertInto(ev.data.self.target); })));
+ },
+
render: function()
{
var map = $('<form />');
map.append(this._render_body());
if (this.options.pageaction !== false)
- {
- map.append($('<div />')
- .addClass('panel panel-default panel-body text-right')
- .append($('<div />')
- .addClass('btn-group')
- .append(_luci2.ui.button(_luci2.tr('Save & Apply'), 'primary')
- .click({ self: this }, function(ev) { }))
- .append(_luci2.ui.button(_luci2.tr('Save'), 'default')
- .click({ self: this }, function(ev) { ev.data.self.send(); }))
- .append(_luci2.ui.button(_luci2.tr('Reset'), 'default')
- .click({ self: this }, function(ev) { ev.data.self.insertInto(ev.data.self.target); }))));
- }
+ map.append(this._render_footer());
return map;
},
});
}
});
+
+ this.cbi.Modal = this.cbi.Map.extend({
+ _render_footer: function()
+ {
+ return $('<div />')
+ .addClass('btn-group')
+ .append(_luci2.ui.button(_luci2.tr('Save & Apply'), 'primary')
+ .click({ self: this }, function(ev) { }))
+ .append(_luci2.ui.button(_luci2.tr('Save'), 'default')
+ .click({ self: this }, function(ev) { ev.data.self.send(); }))
+ .append(_luci2.ui.button(_luci2.tr('Cancel'), 'default')
+ .click({ self: this }, function(ev) { _luci2.ui.dialog(false); }));
+ },
+
+ render: function()
+ {
+ var modal = _luci2.ui.dialog(this.label('caption'), null, { wide: true });
+ var map = $('<form />');
+
+ var desc = this.label('description');
+ if (desc)
+ map.append($('<p />').text(desc));
+
+ map.append(this._render_body());
+
+ modal.find('.modal-body').append(map);
+ modal.find('.modal-footer').append(this._render_footer());
+
+ return modal;
+ },
+
+ redraw: function()
+ {
+ this.render();
+ this.finish();
+ },
+
+ show: function()
+ {
+ var self = this;
+
+ _luci2.ui.loading(true);
+
+ return self.load().then(function() {
+ self.render();
+ self.finish();
+
+ _luci2.ui.loading(false);
+ });
+ }
+ });
};