X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fluci2%2Fui.git;a=blobdiff_plain;f=luci2%2Fhtdocs%2Fluci2%2Fluci2.js;h=2a2a35984be4c7a09614a4ed096a9e893e61a07c;hp=58d38df249aad5efcb159b8249d36739ae45079f;hb=b7bde2e0ae9f21a3726fd6567e2b5eacbb8dbb92;hpb=5627975f94aaea2d2933bbbc4020a73d052c3ad0 diff --git a/luci2/htdocs/luci2/luci2.js b/luci2/htdocs/luci2/luci2.js index 58d38df..2a2a359 100644 --- a/luci2/htdocs/luci2/luci2.js +++ b/luci2/htdocs/luci2/luci2.js @@ -1,7 +1,7 @@ /* LuCI2 - OpenWrt Web Interface - Copyright 2013 Jo-Philipp Wich + Copyright 2013-2014 Jo-Philipp Wich Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -175,7 +175,7 @@ String.prototype.format = function() function LuCI2() { - var _luci2 = this; + var L = this; var Class = function() { }; @@ -253,42 +253,42 @@ function LuCI2() plural: function(n) { return 0 + (n != 1) }, init: function() { - if (_luci2.i18n.loaded) + if (L.i18n.loaded) return; var lang = (navigator.userLanguage || navigator.language || 'en').toLowerCase(); var langs = (lang.indexOf('-') > -1) ? [ lang, lang.split(/-/)[0] ] : [ lang ]; for (var i = 0; i < langs.length; i++) - $.ajax('%s/i18n/base.%s.json'.format(_luci2.globals.resource, langs[i]), { + $.ajax('%s/i18n/base.%s.json'.format(L.globals.resource, langs[i]), { async: false, cache: true, dataType: 'json', success: function(data) { - $.extend(_luci2.i18n.catalog, data); + $.extend(L.i18n.catalog, data); - var pe = _luci2.i18n.catalog['']; + var pe = L.i18n.catalog['']; if (pe) { - delete _luci2.i18n.catalog['']; + delete L.i18n.catalog['']; try { var pf = new Function('n', 'return 0 + (' + pe + ')'); - _luci2.i18n.plural = pf; + L.i18n.plural = pf; } catch (e) { }; } } }); - _luci2.i18n.loaded = true; + L.i18n.loaded = true; } }; this.tr = function(msgid) { - _luci2.i18n.init(); + L.i18n.init(); - var msgstr = _luci2.i18n.catalog[msgid]; + var msgstr = L.i18n.catalog[msgid]; if (typeof(msgstr) == 'undefined') return msgid; @@ -300,23 +300,23 @@ function LuCI2() this.trp = function(msgid, msgid_plural, count) { - _luci2.i18n.init(); + L.i18n.init(); - var msgstr = _luci2.i18n.catalog[msgid]; + var msgstr = L.i18n.catalog[msgid]; if (typeof(msgstr) == 'undefined') return (count == 1) ? msgid : msgid_plural; else if (typeof(msgstr) == 'string') return msgstr; else - return msgstr[_luci2.i18n.plural(count)]; + return msgstr[L.i18n.plural(count)]; }; this.trc = function(msgctx, msgid) { - _luci2.i18n.init(); + L.i18n.init(); - var msgstr = _luci2.i18n.catalog[msgid + '\u0004' + msgctx]; + var msgstr = L.i18n.catalog[msgid + '\u0004' + msgctx]; if (typeof(msgstr) == 'undefined') return msgid; @@ -328,16 +328,16 @@ function LuCI2() this.trcp = function(msgctx, msgid, msgid_plural, count) { - _luci2.i18n.init(); + L.i18n.init(); - var msgstr = _luci2.i18n.catalog[msgid + '\u0004' + msgctx]; + var msgstr = L.i18n.catalog[msgid + '\u0004' + msgctx]; if (typeof(msgstr) == 'undefined') return (count == 1) ? msgid : msgid_plural; else if (typeof(msgstr) == 'string') return msgstr; else - return msgstr[_luci2.i18n.plural(count)]; + return msgstr[L.i18n.plural(count)]; }; this.setHash = function(key, value) @@ -485,6 +485,152 @@ function LuCI2() return n; }; + this.toColor = function(str) + { + if (typeof(str) != 'string' || str.length == 0) + return '#CCCCCC'; + + if (str == 'wan') + return '#F09090'; + else if (str == 'lan') + return '#90F090'; + + var i = 0, hash = 0; + + while (i < str.length) + hash = str.charCodeAt(i++) + ((hash << 5) - hash); + + var r = (hash & 0xFF) % 128; + var g = ((hash >> 8) & 0xFF) % 128; + + var min = 0; + var max = 128; + + if ((r + g) < 128) + min = 128 - r - g; + else + max = 255 - r - g; + + var b = min + (((hash >> 16) & 0xFF) % (max - min)); + + return '#%02X%02X%02X'.format(0xFF - r, 0xFF - g, 0xFF - b); + }; + + this.parseIPv4 = function(str) + { + if ((typeof(str) != 'string' && !(str instanceof String)) || + !str.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) + return undefined; + + var num = [ ]; + var parts = str.split(/\./); + + for (var i = 0; i < parts.length; i++) + { + var n = parseInt(parts[i], 10); + if (isNaN(n) || n > 255) + return undefined; + + num.push(n); + } + + return num; + }; + + this.parseIPv6 = function(str) + { + if ((typeof(str) != 'string' && !(str instanceof String)) || + !str.match(/^[a-fA-F0-9:]+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/)) + return undefined; + + var parts = str.split(/::/); + if (parts.length == 0 || parts.length > 2) + return undefined; + + var lnum = [ ]; + if (parts[0].length > 0) + { + var left = parts[0].split(/:/); + for (var i = 0; i < left.length; i++) + { + var n = parseInt(left[i], 16); + if (isNaN(n)) + return undefined; + + lnum.push((n / 256) >> 0); + lnum.push(n % 256); + } + } + + var rnum = [ ]; + if (parts.length > 1 && parts[1].length > 0) + { + var right = parts[1].split(/:/); + + for (var i = 0; i < right.length; i++) + { + if (right[i].indexOf('.') > 0) + { + var addr = L.parseIPv4(right[i]); + if (!addr) + return undefined; + + rnum.push.apply(rnum, addr); + continue; + } + + var n = parseInt(right[i], 16); + if (isNaN(n)) + return undefined; + + rnum.push((n / 256) >> 0); + rnum.push(n % 256); + } + } + + if (rnum.length > 0 && (lnum.length + rnum.length) > 15) + return undefined; + + var num = [ ]; + + num.push.apply(num, lnum); + + for (var i = 0; i < (16 - lnum.length - rnum.length); i++) + num.push(0); + + num.push.apply(num, rnum); + + if (num.length > 16) + return undefined; + + return num; + }; + + this.isNetmask = function(addr) + { + if (!$.isArray(addr)) + return false; + + var c; + + for (c = 0; (c < addr.length) && (addr[c] == 255); c++); + + if (c == addr.length) + return true; + + if ((addr[c] == 254) || (addr[c] == 252) || (addr[c] == 248) || + (addr[c] == 240) || (addr[c] == 224) || (addr[c] == 192) || + (addr[c] == 128) || (addr[c] == 0)) + { + for (c++; (c < addr.length) && (addr[c] == 0); c++); + + if (c == addr.length) + return true; + } + + return false; + }; + this.globals = { timeout: 15000, resource: '/luci2', @@ -505,7 +651,7 @@ function LuCI2() data: JSON.stringify(req), dataType: 'json', type: 'POST', - timeout: _luci2.globals.timeout, + timeout: L.globals.timeout, _rpc_req: req }).then(cb, cb); }, @@ -536,7 +682,7 @@ function LuCI2() for (var i = 0; i < msg.length; i++) { /* fetch related request info */ - var req = _luci2.rpc._requests[reqs[i].id]; + var req = L.rpc._requests[reqs[i].id]; if (typeof(req) != 'object') throw 'No related request for JSON response'; @@ -567,7 +713,7 @@ function LuCI2() { req.priv[0] = ret; req.priv[1] = req.params; - ret = req.filter.apply(_luci2.rpc, req.priv); + ret = req.filter.apply(L.rpc, req.priv); } /* store response data */ @@ -577,7 +723,7 @@ function LuCI2() data = ret; /* delete request object */ - delete _luci2.rpc._requests[reqs[i].id]; + delete L.rpc._requests[reqs[i].id]; } return $.Deferred().resolveWith(this, [ data ]); @@ -608,7 +754,7 @@ function LuCI2() flush: function() { if (!$.isArray(this._batch)) - return _luci2.deferrable([ ]); + return L.deferrable([ ]); var req = this._batch; delete this._batch; @@ -648,7 +794,7 @@ function LuCI2() id: _rpc._id++, method: 'call', params: [ - _luci2.globals.sid, + L.globals.sid, options.object, options.method, params @@ -660,7 +806,7 @@ function LuCI2() if ($.isArray(_rpc._batch)) { req.index = _rpc._batch.push(msg) - 1; - return _luci2.deferrable(msg); + return L.deferrable(msg); } /* call rpc */ @@ -674,7 +820,7 @@ function LuCI2() init: function() { this.state = { - newid: 0, + newidx: 0, values: { }, creates: { }, changes: { }, @@ -683,38 +829,107 @@ function LuCI2() }; }, - _load: _luci2.rpc.declare({ + callLoad: L.rpc.declare({ object: 'uci', method: 'get', params: [ 'config' ], expect: { values: { } } }), - _order: _luci2.rpc.declare({ + callOrder: L.rpc.declare({ object: 'uci', method: 'order', params: [ 'config', 'sections' ] }), - _add: _luci2.rpc.declare({ + callAdd: L.rpc.declare({ object: 'uci', method: 'add', params: [ 'config', 'type', 'name', 'values' ], expect: { section: '' } }), - _set: _luci2.rpc.declare({ + callSet: L.rpc.declare({ object: 'uci', method: 'set', params: [ 'config', 'section', 'values' ] }), - _delete: _luci2.rpc.declare({ + callDelete: L.rpc.declare({ object: 'uci', method: 'delete', params: [ 'config', 'section', 'options' ] }), + callApply: L.rpc.declare({ + object: 'uci', + method: 'apply', + params: [ 'timeout', 'rollback' ] + }), + + callConfirm: L.rpc.declare({ + object: 'uci', + method: 'confirm' + }), + + createSID: function(conf) + { + var v = this.state.values; + var n = this.state.creates; + var sid; + + do { + sid = "new%06x".format(Math.random() * 0xFFFFFF); + } while ((n[conf] && n[conf][sid]) || (v[conf] && v[conf][sid])); + + return sid; + }, + + reorderSections: function() + { + var v = this.state.values; + var n = this.state.creates; + var r = this.state.reorder; + + if ($.isEmptyObject(r)) + return L.deferrable(); + + L.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 r) + { + var o = [ ]; + + if (n[c]) + for (var s in n[c]) + o.push(n[c][s]); + + for (var s in v[c]) + o.push(v[c][s]); + + if (o.length > 0) + { + o.sort(function(a, b) { + return (a['.index'] - b['.index']); + }); + + var sids = [ ]; + + for (var i = 0; i < o.length; i++) + sids.push(o[i]['.name']); + + this.callOrder(c, sids); + } + } + + this.state.reorder = { }; + return L.rpc.flush(); + }, + load: function(packages) { var self = this; @@ -724,17 +939,17 @@ function LuCI2() if (!$.isArray(packages)) packages = [ packages ]; - _luci2.rpc.batch(); + L.rpc.batch(); for (var i = 0; i < packages.length; i++) - if (!seen[packages[i]]) + if (!seen[packages[i]] && !self.state.values[packages[i]]) { pkgs.push(packages[i]); seen[packages[i]] = true; - self._load(packages[i]); + self.callLoad(packages[i]); } - return _luci2.rpc.flush().then(function(responses) { + return L.rpc.flush().then(function(responses) { for (var i = 0; i < responses.length; i++) self.state.values[pkgs[i]] = responses[i]; @@ -758,21 +973,21 @@ function LuCI2() add: function(conf, type, name) { - var c = this.state.creates; - var s = '.new.%d'.format(this.state.newid++); + var n = this.state.creates; + var sid = this.createSID(conf); - if (!c[conf]) - c[conf] = { }; + if (!n[conf]) + n[conf] = { }; - c[conf][s] = { + n[conf][sid] = { '.type': type, - '.name': s, + '.name': sid, '.create': name, '.anonymous': !name, - '.index': 1000 + this.state.newid + '.index': 1000 + this.state.newidx++ }; - return s; + return sid; }, remove: function(conf, sid) @@ -782,10 +997,9 @@ function LuCI2() var d = this.state.deletes; /* requested deletion of a just created section */ - if (sid.indexOf('.new.') == 0) + if (n[conf] && n[conf][sid]) { - if (n[conf]) - delete n[conf][sid]; + delete n[conf][sid]; } else { @@ -845,7 +1059,7 @@ function LuCI2() return undefined; /* requested option in a just created section */ - if (sid.indexOf('.new.') == 0) + if (n[conf] && n[conf][sid]) { if (!n[conf]) return undefined; @@ -890,6 +1104,7 @@ function LuCI2() set: function(conf, sid, opt, val) { + var v = this.state.values; var n = this.state.creates; var c = this.state.changes; var d = this.state.deletes; @@ -899,15 +1114,12 @@ function LuCI2() opt.charAt(0) == '.') return; - if (sid.indexOf('.new.') == 0) + if (n[conf] && n[conf][sid]) { - if (n[conf] && n[conf][sid]) - { - if (typeof(val) != 'undefined') - n[conf][sid][opt] = val; - else - delete n[conf][sid][opt]; - } + if (typeof(val) != 'undefined') + n[conf][sid][opt] = val; + else + delete n[conf][sid][opt]; } else if (typeof(val) != 'undefined') { @@ -915,6 +1127,10 @@ function LuCI2() if (d[conf] && d[conf][sid] === true) return; + /* only set in existing sections */ + if (!v[conf] || !v[conf][sid]) + return; + if (!c[conf]) c[conf] = { }; @@ -923,12 +1139,16 @@ function LuCI2() /* undelete option */ if (d[conf] && d[conf][sid]) - d[conf][sid] = _luci2.filterArray(d[conf][sid], opt); + d[conf][sid] = L.filterArray(d[conf][sid], opt); c[conf][sid][opt] = val; } else { + /* only delete in existing sections */ + if (!v[conf] || !v[conf][sid]) + return; + if (!d[conf]) d[conf] = { }; @@ -945,61 +1165,33 @@ function LuCI2() return this.set(conf, sid, opt, undefined); }, - _reload: function() + get_first: function(conf, type, opt) { - var pkgs = [ ]; - - for (var pkg in this.state.values) - pkgs.push(pkg); + var sid = undefined; - this.init(); + L.uci.sections(conf, type, function(s) { + if (typeof(sid) != 'string') + sid = s['.name']; + }); - return this.load(pkgs); + return this.get(conf, sid, opt); }, - _reorder: function() + set_first: function(conf, type, opt, val) { - var v = this.state.values; - var n = this.state.creates; - var r = this.state.reorder; - - if ($.isEmptyObject(r)) - 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 r) - { - var o = [ ]; - - if (n && n[c]) - for (var s in n[c]) - o.push(n[c][s]); - - for (var s in v[c]) - o.push(v[c][s]); - - if (o.length > 0) - { - o.sort(function(a, b) { - return (a['.index'] - b['.index']); - }); - - var sids = [ ]; + var sid = undefined; - for (var i = 0; i < o.length; i++) - sids.push(o[i]['.name']); + L.uci.sections(conf, type, function(s) { + if (typeof(sid) != 'string') + sid = s['.name']; + }); - this._order(c, sids); - } - } + return this.set(conf, sid, opt, val); + }, - this.state.reorder = { }; - return _luci2.rpc.flush(); + unset_first: function(conf, type, opt) + { + return this.set_first(conf, type, opt, undefined); }, swap: function(conf, sid1, sid2) @@ -1022,49 +1214,67 @@ function LuCI2() save: function() { - _luci2.rpc.batch(); + L.rpc.batch(); + + var v = this.state.values; + var n = this.state.creates; + var c = this.state.changes; + var d = this.state.deletes; var self = this; var snew = [ ]; + var pkgs = { }; - if (self.state.creates) - for (var c in self.state.creates) - for (var s in self.state.creates[c]) + if (n) + for (var conf in n) + { + for (var sid in n[conf]) { var r = { - config: c, + config: conf, values: { } }; - for (var k in self.state.creates[c][s]) + for (var k in n[conf][sid]) { if (k == '.type') - r.type = self.state.creates[c][s][k]; + r.type = n[conf][sid][k]; else if (k == '.create') - r.name = self.state.creates[c][s][k]; + r.name = n[conf][sid][k]; else if (k.charAt(0) != '.') - r.values[k] = self.state.creates[c][s][k]; + r.values[k] = n[conf][sid][k]; } - snew.push(self.state.creates[c][s]); + snew.push(n[conf][sid]); - self._add(r.config, r.type, r.name, r.values); + self.callAdd(r.config, r.type, r.name, r.values); } - if (self.state.changes) - for (var c in self.state.changes) - for (var s in self.state.changes[c]) - self._set(c, s, self.state.changes[c][s]); + pkgs[conf] = true; + } + + if (c) + for (var conf in c) + { + for (var sid in c[conf]) + self.callSet(conf, sid, c[conf][sid]); + + pkgs[conf] = true; + } - if (self.state.deletes) - for (var c in self.state.deletes) - for (var s in self.state.deletes[c]) + if (d) + for (var conf in d) + { + for (var sid in d[conf]) { - var o = self.state.deletes[c][s]; - self._delete(c, s, (o === true) ? undefined : o); + var o = d[conf][sid]; + self.callDelete(conf, sid, (o === true) ? undefined : o); } - return _luci2.rpc.flush().then(function(responses) { + pkgs[conf] = true; + } + + return L.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 @@ -1072,20 +1282,15 @@ function LuCI2() for (var i = 0; i < snew.length; i++) snew[i]['.name'] = responses[i]; - return self._reorder(); - }); - }, + return self.reorderSections(); + }).then(function() { + pkgs = L.toArray(pkgs); - _apply: _luci2.rpc.declare({ - object: 'uci', - method: 'apply', - params: [ 'timeout', 'rollback' ] - }), + self.unload(pkgs); - _confirm: _luci2.rpc.declare({ - object: 'uci', - method: 'confirm' - }), + return self.load(pkgs); + }); + }, apply: function(timeout) { @@ -1096,7 +1301,7 @@ function LuCI2() if (typeof(timeout) != 'number' || timeout < 1) timeout = 10; - self._apply(timeout, true).then(function(rv) { + self.callApply(timeout, true).then(function(rv) { if (rv != 0) { deferred.rejectWith(self, [ rv ]); @@ -1106,7 +1311,7 @@ function LuCI2() var try_deadline = date.getTime() + 1000 * timeout; var try_confirm = function() { - return self._confirm().then(function(rv) { + return self.callConfirm().then(function(rv) { if (rv != 0) { if (date.getTime() < try_deadline) @@ -1127,7 +1332,7 @@ function LuCI2() return deferred; }, - changes: _luci2.rpc.declare({ + changes: L.rpc.declare({ object: 'uci', method: 'changes', expect: { changes: { } } @@ -1135,703 +1340,1668 @@ function LuCI2() readable: function(conf) { - return _luci2.session.hasACL('uci', conf, 'read'); + return L.session.hasACL('uci', conf, 'read'); }, writable: function(conf) { - return _luci2.session.hasACL('uci', conf, 'write'); + return L.session.hasACL('uci', conf, 'write'); } }); - this.uci = { + this.uci = new this.UCIContext(); - writable: function() - { - return _luci2.session.access('ubus', 'uci', 'commit'); - }, - - add: _luci2.rpc.declare({ - object: 'uci', - method: 'add', - params: [ 'config', 'type', 'name', 'values' ], - expect: { section: '' } + this.wireless = { + listDeviceNames: L.rpc.declare({ + object: 'iwinfo', + method: 'devices', + expect: { 'devices': [ ] }, + filter: function(data) { + data.sort(); + return data; + } }), - apply: function() - { + getDeviceStatus: L.rpc.declare({ + object: 'iwinfo', + method: 'info', + params: [ 'device' ], + expect: { '': { } }, + filter: function(data, params) { + if (!$.isEmptyObject(data)) + { + data['device'] = params['device']; + return data; + } + return undefined; + } + }), - }, + getAssocList: L.rpc.declare({ + object: 'iwinfo', + method: 'assoclist', + params: [ 'device' ], + expect: { results: [ ] }, + filter: function(data, params) { + for (var i = 0; i < data.length; i++) + data[i]['device'] = params['device']; - configs: _luci2.rpc.declare({ - object: 'uci', - method: 'configs', - expect: { configs: [ ] } - }), + data.sort(function(a, b) { + if (a.bssid < b.bssid) + return -1; + else if (a.bssid > b.bssid) + return 1; + else + return 0; + }); - _changes: _luci2.rpc.declare({ - object: 'uci', - method: 'changes', - params: [ 'config' ], - expect: { changes: [ ] } + return data; + } }), - changes: function(config) - { - if (typeof(config) == 'string') - return this._changes(config); - - var configlist; - return this.configs().then(function(configs) { - _luci2.rpc.batch(); - configlist = configs; + getWirelessStatus: function() { + return this.listDeviceNames().then(function(names) { + L.rpc.batch(); - for (var i = 0; i < configs.length; i++) - _luci2.uci._changes(configs[i]); + for (var i = 0; i < names.length; i++) + L.wireless.getDeviceStatus(names[i]); - return _luci2.rpc.flush(); - }).then(function(changes) { + return L.rpc.flush(); + }).then(function(networks) { var rv = { }; - for (var i = 0; i < configlist.length; i++) - if (changes[i].length) - rv[configlist[i]] = changes[i]; + var phy_attrs = [ + 'country', 'channel', 'frequency', 'frequency_offset', + 'txpower', 'txpower_offset', 'hwmodes', 'hardware', 'phy' + ]; - return rv; - }); - }, + var net_attrs = [ + 'ssid', 'bssid', 'mode', 'quality', 'quality_max', + 'signal', 'noise', 'bitrate', 'encryption' + ]; - commit: _luci2.rpc.declare({ - object: 'uci', - method: 'commit', - params: [ 'config' ] - }), + for (var i = 0; i < networks.length; i++) + { + var phy = rv[networks[i].phy] || ( + rv[networks[i].phy] = { networks: [ ] } + ); - _delete_one: _luci2.rpc.declare({ - object: 'uci', - method: 'delete', - params: [ 'config', 'section', 'option' ] - }), + var net = { + device: networks[i].device + }; - _delete_multiple: _luci2.rpc.declare({ - object: 'uci', - method: 'delete', - params: [ 'config', 'section', 'options' ] - }), + for (var j = 0; j < phy_attrs.length; j++) + phy[phy_attrs[j]] = networks[i][phy_attrs[j]]; - 'delete': function(config, section, option) - { - if ($.isArray(option)) - return this._delete_multiple(config, section, option); - else - return this._delete_one(config, section, option); - }, + for (var j = 0; j < net_attrs.length; j++) + net[net_attrs[j]] = networks[i][net_attrs[j]]; - delete_all: _luci2.rpc.declare({ - object: 'uci', - method: 'delete', - params: [ 'config', 'type', 'match' ] - }), + phy.networks.push(net); + } - _foreach: _luci2.rpc.declare({ - object: 'uci', - method: 'get', - params: [ 'config', 'type' ], - expect: { values: { } } - }), + return rv; + }); + }, - foreach: function(config, type, cb) + getAssocLists: function() { - return this._foreach(config, type).then(function(sections) { - for (var s in sections) - cb(sections[s]); + return this.listDeviceNames().then(function(names) { + L.rpc.batch(); + + for (var i = 0; i < names.length; i++) + L.wireless.getAssocList(names[i]); + + return L.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; }); }, - 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; + 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 : ', '); } - }), - 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 (!enc || !enc.enabled) + return L.tr('None'); + + if (enc.wep) + { + if (enc.wep.length == 2) + return L.tr('WEP Open/Shared') + ' (%s)'.format(format_list(enc.ciphers, ', ')); + else if (enc.wep[0] == 'shared') + return L.tr('WEP Shared Auth') + ' (%s)'.format(format_list(enc.ciphers, ', ')); + else + return L.tr('WEP Open System') + ' (%s)'.format(format_list(enc.ciphers, ', ')); } - }), + else if (enc.wpa) + { + if (enc.wpa.length == 2) + return L.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 L.tr('Unknown'); + } + }; - get_first: function(config, type, option) + this.firewall = { + getZoneColor: function(zone) { - return this._foreach(config, type).then(function(sections) { - for (var s in sections) - { - var val = (typeof(option) == 'string') ? sections[s][option] : sections[s]['.name']; + if ($.isPlainObject(zone)) + zone = zone.name; - if (typeof(val) != 'undefined') - return val; - } + if (zone == 'lan') + return '#90f090'; + else if (zone == 'wan') + return '#f09090'; - return undefined; - }); - }, + for (var i = 0, hash = 0; + i < zone.length; + hash = zone.charCodeAt(i++) + ((hash << 5) - hash)); - section: _luci2.rpc.declare({ - object: 'uci', - method: 'add', - params: [ 'config', 'type', 'name', 'values' ], - expect: { section: '' } - }), + for (var i = 0, color = '#'; + i < 3; + color += ('00' + ((hash >> i++ * 8) & 0xFF).tostring(16)).slice(-2)); - _set: _luci2.rpc.declare({ - object: 'uci', - method: 'set', - params: [ 'config', 'section', 'values' ] - }), + return color; + }, - set: function(config, section, option, value) + findZoneByNetwork: function(network) { - 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 */ + var self = this; + var zone = undefined; - var values = { }; - values[option] = value; + return L.uci.sections('firewall', 'zone', function(z) { + if (!z.name || !z.network) + return; - return this._set(config, section, values); - }, + if (!$.isArray(z.network)) + z.network = z.network.split(/\s+/); - order: _luci2.rpc.declare({ - object: 'uci', - method: 'order', - params: [ 'config', 'sections' ] - }) - }; + 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); - 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; + return zone; }); - }, + } + }; - 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; - } - }), + this.NetworkModel = { + deviceBlacklist: [ + /^gre[0-9]+$/, + /^gretap[0-9]+$/, + /^ifb[0-9]+$/, + /^ip6tnl[0-9]+$/, + /^sit[0-9]+$/, + /^wlan[0-9]+\.sta[0-9]+$/ + ], + + rpcCacheFunctions: [ + 'protolist', 0, L.rpc.declare({ + object: 'network', + method: 'get_proto_handlers', + expect: { '': { } } + }), + 'ifstate', 1, L.rpc.declare({ + object: 'network.interface', + method: 'dump', + expect: { 'interface': [ ] } + }), + 'devstate', 2, L.rpc.declare({ + object: 'network.device', + method: 'status', + expect: { '': { } } + }), + 'wifistate', 0, L.rpc.declare({ + object: 'network.wireless', + method: 'status', + expect: { '': { } } + }), + 'bwstate', 2, L.rpc.declare({ + object: 'luci2.network.bwmon', + method: 'statistics', + expect: { 'statistics': { } } + }), + 'devlist', 2, L.rpc.declare({ + object: 'luci2.network', + method: 'device_list', + expect: { 'devices': [ ] } + }), + 'swlist', 0, L.rpc.declare({ + object: 'luci2.network', + method: 'switch_list', + expect: { 'switches': [ ] } + }) + ], + + loadProtocolHandler: function(proto) + { + var url = L.globals.resource + '/proto/' + proto + '.js'; + var self = L.NetworkModel; + + var def = $.Deferred(); + + $.ajax(url, { + method: 'GET', + cache: true, + dataType: 'text' + }).then(function(data) { + try { + var protoConstructorSource = ( + '(function(L, $) { ' + + 'return %s' + + '})(L, $);\n\n' + + '//@ sourceURL=%s' + ).format(data, url); - getNetworkStatus: function() - { - var nets = [ ]; - var devs = { }; + var protoClass = eval(protoConstructorSource); - return this.listNetworkNames().then(function(names) { - _luci2.rpc.batch(); + self.protocolHandlers[proto] = new protoClass(); + } + catch(e) { + alert('Unable to instantiate proto "%s": %s'.format(url, e)); + }; - for (var i = 0; i < names.length; i++) - _luci2.network.getInterfaceStatus(names[i]); + def.resolve(); + }).fail(function() { + def.resolve(); + }); - return _luci2.rpc.flush(); - }).then(function(networks) { - for (var i = 0; i < networks.length; i++) - { - var net = nets[i] = networks[i]; - var dev = net.l3_device || net.l2_device; - if (dev) - net.device = devs[dev] || (devs[dev] = { }); - } + return def; + }, - _luci2.rpc.batch(); + loadProtocolHandlers: function() + { + var self = L.NetworkModel; + var deferreds = [ + self.loadProtocolHandler('none') + ]; - for (var dev in devs) - _luci2.network.getDeviceStatus(dev); + for (var proto in self.rpcCache.protolist) + deferreds.push(self.loadProtocolHandler(proto)); - return _luci2.rpc.flush(); - }).then(function(devices) { - _luci2.rpc.batch(); + return $.when.apply($, deferreds); + }, - for (var i = 0; i < devices.length; i++) - { - var brm = devices[i]['bridge-members']; - delete devices[i]['bridge-members']; + callSwitchInfo: L.rpc.declare({ + object: 'luci2.network', + method: 'switch_info', + params: [ 'switch' ], + expect: { 'info': { } } + }), - $.extend(devs[devices[i]['device']], devices[i]); + callSwitchInfoCallback: function(responses) { + var self = L.NetworkModel; + var swlist = self.rpcCache.swlist; + var swstate = self.rpcCache.swstate = { }; - if (!brm) - continue; + for (var i = 0; i < responses.length; i++) + swstate[swlist[i]] = responses[i]; + }, - devs[devices[i]['device']].subdevices = [ ]; + loadCacheCallback: function(level) + { + var self = L.NetworkModel; + var name = '_fetch_cache_cb_' + level; + + return self[name] || ( + self[name] = function(responses) + { + for (var i = 0; i < self.rpcCacheFunctions.length; i += 3) + if (!level || self.rpcCacheFunctions[i + 1] == level) + self.rpcCache[self.rpcCacheFunctions[i]] = responses.shift(); - for (var j = 0; j < brm.length; j++) + if (!level) { - if (!devs[brm[j]]) - { - devs[brm[j]] = { }; - _luci2.network.getDeviceStatus(brm[j]); - } + L.rpc.batch(); - devs[devices[i]['device']].subdevices[j] = devs[brm[j]]; - } - } + for (var i = 0; i < self.rpcCache.swlist.length; i++) + self.callSwitchInfo(self.rpcCache.swlist[i]); - return _luci2.rpc.flush(); - }).then(function(subdevices) { - for (var i = 0; i < subdevices.length; i++) - $.extend(devs[subdevices[i]['device']], subdevices[i]); + return L.rpc.flush().then(self.callSwitchInfoCallback); + } - _luci2.rpc.batch(); + return L.deferrable(); + } + ); + }, - for (var dev in devs) - _luci2.wireless.getDeviceStatus(dev); + loadCache: function(level) + { + var self = L.NetworkModel; - 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]; + return L.uci.load(['network', 'wireless']).then(function() { + L.rpc.batch(); - nets.sort(function(a, b) { - if (a['interface'] < b['interface']) - return -1; - else if (a['interface'] > b['interface']) - return 1; - else - return 0; - }); + for (var i = 0; i < self.rpcCacheFunctions.length; i += 3) + if (!level || self.rpcCacheFunctions[i + 1] == level) + self.rpcCacheFunctions[i + 2](); - return nets; + return L.rpc.flush().then(self.loadCacheCallback(level || 0)); }); }, - findWanInterfaces: function(cb) + isBlacklistedDevice: function(dev) { - return this.listNetworkNames().then(function(names) { - _luci2.rpc.batch(); - - for (var i = 0; i < names.length; i++) - _luci2.network.getInterfaceStatus(names[i]); + for (var i = 0; i < this.deviceBlacklist.length; i++) + if (dev.match(this.deviceBlacklist[i])) + return true; - return _luci2.rpc.flush(); - }).then(function(interfaces) { - var rv = [ undefined, undefined ]; + return false; + }, - for (var i = 0; i < interfaces.length; i++) - { - if (!interfaces[i].route) - continue; + sortDevicesCallback: function(a, b) + { + if (a.options.kind < b.options.kind) + return -1; + else if (a.options.kind > b.options.kind) + return 1; - for (var j = 0; j < interfaces[i].route.length; j++) - { - var rt = interfaces[i].route[j]; + if (a.options.name < b.options.name) + return -1; + else if (a.options.name > b.options.name) + return 1; - if (typeof(rt.table) != 'undefined') - continue; + return 0; + }, - 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]; - } + getDeviceObject: function(ifname) + { + var alias = (ifname.charAt(0) == '@'); + return this.deviceObjects[ifname] || ( + this.deviceObjects[ifname] = { + ifname: ifname, + kind: alias ? 'alias' : 'ethernet', + type: alias ? 0 : 1, + up: false, + changed: { } } + ); + }, - return rv; - }); + getInterfaceObject: function(name) + { + return this.interfaceObjects[name] || ( + this.interfaceObjects[name] = { + name: name, + proto: this.protocolHandlers.none, + changed: { } + } + ); }, - getDHCPLeases: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'dhcp_leases', - expect: { leases: [ ] } - }), + loadDevicesCallback: function() + { + var self = L.NetworkModel; + var wificount = { }; - getDHCPv6Leases: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'dhcp6_leases', - expect: { leases: [ ] } - }), + for (var ifname in self.rpcCache.devstate) + { + if (self.isBlacklistedDevice(ifname)) + continue; - getRoutes: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'routes', - expect: { routes: [ ] } - }), + var dev = self.rpcCache.devstate[ifname]; + var entry = self.getDeviceObject(ifname); - getIPv6Routes: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'routes', - expect: { routes: [ ] } - }), + entry.up = dev.up; - getARPTable: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'arp_table', - expect: { entries: [ ] } - }), + switch (dev.type) + { + case 'IP tunnel': + entry.kind = 'tunnel'; + break; - 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; + case 'Bridge': + entry.kind = 'bridge'; + //entry.ports = dev['bridge-members'].sort(); + break; + } } - }), - getDeviceStatus: _luci2.rpc.declare({ - object: 'network.device', - method: 'status', - params: [ 'name' ], - expect: { '': { } }, - filter: function(data, params) { - data['device'] = params['name']; - return data; - } - }), + for (var i = 0; i < self.rpcCache.devlist.length; i++) + { + var dev = self.rpcCache.devlist[i]; - getConntrackCount: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'conntrack_count', - expect: { '': { count: 0, limit: 0 } } - }), + if (self.isBlacklistedDevice(dev.device)) + continue; - listSwitchNames: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'switch_list', - expect: { switches: [ ] } - }), + var entry = self.getDeviceObject(dev.device); - 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']; + entry.up = dev.is_up; + entry.type = dev.type; - delete data.vlan; - delete data.port; + 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; - return data; + 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; + } } - }), - getSwitchStatus: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'switch_status', - params: [ 'switch' ], - expect: { ports: [ ] } - }), + var net = L.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.getDeviceObject(s.name); - runPing: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'ping', - params: [ 'data' ], - expect: { '': { code: -1 } } - }), + switch (s.type) + { + case 'macvlan': + case 'tunnel': + entry.kind = 'tunnel'; + break; + } - runPing6: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'ping6', - params: [ 'data' ], - expect: { '': { code: -1 } } - }), + entry.sid = sid; + } + else if (s['.type'] == 'interface' && !s['.anonymous'] && s.ifname) + { + var ifnames = L.toArray(s.ifname); - runTraceroute: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'traceroute', - params: [ 'data' ], - expect: { '': { code: -1 } } - }), + for (var j = 0; j < ifnames.length; j++) + self.getDeviceObject(ifnames[j]); - runTraceroute6: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'traceroute6', - params: [ 'data' ], - expect: { '': { code: -1 } } - }), + if (s['.name'] != 'loopback') + { + var entry = self.getDeviceObject('@%s'.format(s['.name'])); - runNslookup: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'nslookup', - params: [ 'data' ], - expect: { '': { code: -1 } } - }), + entry.type = 0; + entry.kind = 'alias'; + entry.sid = sid; + } + } + else if (s['.type'] == 'switch_vlan' && s.device) + { + var sw = self.rpcCache.swstate[s.device]; + var vid = parseInt(s.vid || s.vlan); + var ports = L.toArray(s.ports); + if (!sw || !ports.length || isNaN(vid)) + continue; - setUp: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'ifup', - params: [ 'data' ], - expect: { '': { code: -1 } } - }), + var ifname = undefined; - setDown: _luci2.rpc.declare({ - object: 'luci2.network', - method: 'ifdown', - params: [ 'data' ], - expect: { '': { code: -1 } } - }) - }; + for (var j = 0; j < ports.length; j++) + { + var port = parseInt(ports[j]); + var tag = (ports[j].replace(/[^tu]/g, '') == 't'); - this.wireless = { - listDeviceNames: _luci2.rpc.declare({ - object: 'iwinfo', - method: 'devices', - expect: { 'devices': [ ] }, - filter: function(data) { - data.sort(); - return data; + 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.getDeviceObject(ifname); + + entry.kind = 'vlan'; + entry.sid = sid; + entry.vsw = sw; + entry.vid = vid; + } } - }), - getDeviceStatus: _luci2.rpc.declare({ - object: 'iwinfo', - method: 'info', - params: [ 'device' ], - expect: { '': { } }, - filter: function(data, params) { - if (!$.isEmptyObject(data)) + var wifi = L.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) { - data['device'] = params['device']; - return data; + 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.rpcCache.wifistate[s.device]) + { + var ifcs = self.rpcCache.wifistate[s.device].interfaces; + for (var ifc in ifcs) + { + if (ifcs[ifc].section == sid && ifcs[ifc].ifname) + { + ifname = ifcs[ifc].ifname; + break; + } + } + } + + var entry = self.getDeviceObject(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; } - return undefined; } - }), - getAssocList: _luci2.rpc.declare({ - object: 'iwinfo', - method: 'assoclist', - params: [ 'device' ], - expect: { results: [ ] }, - filter: function(data, params) { - for (var i = 0; i < data.length; i++) - data[i]['device'] = params['device']; + for (var i = 0; i < net.length; i++) + { + var s = net[i]; + var sid = s['.name']; - data.sort(function(a, b) { - if (a.bssid < b.bssid) - return -1; - else if (a.bssid > b.bssid) - return 1; - else - return 0; - }); + if (s['.type'] == 'interface' && !s['.anonymous'] && s.type == 'bridge') + { + var ifnames = L.toArray(s.ifname); - return data; - } - }), + for (var ifname in self.deviceObjects) + { + var dev = self.deviceObjects[ifname]; - getWirelessStatus: function() { - return this.listDeviceNames().then(function(names) { - _luci2.rpc.batch(); + if (dev.kind != 'wifi') + continue; - for (var i = 0; i < names.length; i++) - _luci2.wireless.getDeviceStatus(names[i]); + var wnets = L.toArray(L.uci.get('wireless', dev.sid, 'network')); + if ($.inArray(sid, wnets) > -1) + ifnames.push(ifname); + } - return _luci2.rpc.flush(); - }).then(function(networks) { - var rv = { }; + entry = self.getDeviceObject('br-%s'.format(s['.name'])); + entry.type = 1; + entry.kind = 'bridge'; + entry.sid = sid; + entry.ports = ifnames.sort(); + } + } + }, - var phy_attrs = [ - 'country', 'channel', 'frequency', 'frequency_offset', - 'txpower', 'txpower_offset', 'hwmodes', 'hardware', 'phy' - ]; + loadInterfacesCallback: function() + { + var self = L.NetworkModel; + var net = L.uci.sections('network'); - var net_attrs = [ - 'ssid', 'bssid', 'mode', 'quality', 'quality_max', - 'signal', 'noise', 'bitrate', 'encryption' - ]; + for (var i = 0; i < net.length; i++) + { + var s = net[i]; + var sid = s['.name']; - for (var i = 0; i < networks.length; i++) + if (s['.type'] == 'interface' && !s['.anonymous'] && s.proto) { - var phy = rv[networks[i].phy] || ( - rv[networks[i].phy] = { networks: [ ] } - ); + var entry = self.getInterfaceObject(s['.name']); + var proto = self.protocolHandlers[s.proto] || self.protocolHandlers.none; - var net = { - device: networks[i].device - }; + var l3dev = undefined; + var l2dev = undefined; - for (var j = 0; j < phy_attrs.length; j++) - phy[phy_attrs[j]] = networks[i][phy_attrs[j]]; + var ifnames = L.toArray(s.ifname); - for (var j = 0; j < net_attrs.length; j++) - net[net_attrs[j]] = networks[i][net_attrs[j]]; + for (var ifname in self.deviceObjects) + { + var dev = self.deviceObjects[ifname]; - phy.networks.push(net); + if (dev.kind != 'wifi') + continue; + + var wnets = L.toArray(L.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; } + } - return rv; - }); + for (var i = 0; i < self.rpcCache.ifstate.length; i++) + { + var iface = self.rpcCache.ifstate[i]; + var entry = self.getInterfaceObject(iface['interface']); + var proto = self.protocolHandlers[iface.proto] || self.protocolHandlers.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; + } + } }, - getAssocLists: function() + init: function() { - return this.listDeviceNames().then(function(names) { - _luci2.rpc.batch(); + var self = this; - for (var i = 0; i < names.length; i++) - _luci2.wireless.getAssocList(names[i]); + if (self.rpcCache) + return L.deferrable(); - return _luci2.rpc.flush(); - }).then(function(assoclists) { - var rv = [ ]; + self.rpcCache = { }; + self.deviceObjects = { }; + self.interfaceObjects = { }; + self.protocolHandlers = { }; - for (var i = 0; i < assoclists.length; i++) - for (var j = 0; j < assoclists[i].length; j++) - rv.push(assoclists[i][j]); + return self.loadCache() + .then(self.loadProtocolHandlers) + .then(self.loadDevicesCallback) + .then(self.loadInterfacesCallback); + }, - return rv; + update: function() + { + delete this.rpcCache; + return this.init(); + }, + + refreshInterfaceStatus: function() + { + return this.loadCache(1).then(this.loadInterfacesCallback); + }, + + refreshDeviceStatus: function() + { + return this.loadCache(2).then(this.loadDevicesCallback); + }, + + refreshStatus: function() + { + return this.loadCache(1) + .then(this.loadCache(2)) + .then(this.loadDevicesCallback) + .then(this.loadInterfacesCallback); + }, + + getDevices: function() + { + var devs = [ ]; + + for (var ifname in this.deviceObjects) + if (ifname != 'lo') + devs.push(new L.NetworkModel.Device(this.deviceObjects[ifname])); + + return devs.sort(this.sortDevicesCallback); + }, + + getDeviceByInterface: function(iface) + { + if (iface instanceof L.NetworkModel.Interface) + iface = iface.name(); + + if (this.interfaceObjects[iface]) + return this.getDevice(this.interfaceObjects[iface].l3dev) || + this.getDevice(this.interfaceObjects[iface].l2dev); + + return undefined; + }, + + getDevice: function(ifname) + { + if (this.deviceObjects[ifname]) + return new L.NetworkModel.Device(this.deviceObjects[ifname]); + + return undefined; + }, + + createDevice: function(name) + { + return new L.NetworkModel.Device(this.getDeviceObject(name)); + }, + + getInterfaces: function() + { + var ifaces = [ ]; + + for (var name in this.interfaceObjects) + 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; }, - formatEncryption: function(enc) + getInterfacesByDevice: function(dev) { - var format_list = function(l, s) + var ifaces = [ ]; + + if (dev instanceof L.NetworkModel.Device) + dev = dev.name(); + + for (var name in this.interfaceObjects) { - var rv = [ ]; - for (var i = 0; i < l.length; i++) - rv.push(l[i].toUpperCase()); - return rv.join(s ? s : ', '); + var iface = this.interfaceObjects[name]; + if (iface.l2dev == dev || iface.l3dev == dev) + ifaces.push(this.getInterface(name)); } - if (!enc || !enc.enabled) - return _luci2.tr('None'); + ifaces.sort(function(a, b) { + if (a.name() < b.name()) + return -1; + else if (a.name() > b.name()) + return 1; + else + return 0; + }); - if (enc.wep) + return ifaces; + }, + + getInterface: function(iface) + { + if (this.interfaceObjects[iface]) + return new L.NetworkModel.Interface(this.interfaceObjects[iface]); + + return undefined; + }, + + getProtocols: function() + { + var rv = [ ]; + + for (var proto in this.protocolHandlers) { - 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, ', ')); + var pr = this.protocolHandlers[proto]; + + rv.push({ + name: proto, + description: pr.description, + virtual: pr.virtual, + tunnel: pr.tunnel + }); } - 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, ', ') - ); + + return rv.sort(function(a, b) { + if (a.name < b.name) + return -1; + else if (a.name > b.name) + return 1; else - return 'WPA %s (%s)'.format( - format_list(enc.authentication, '/'), - format_list(enc.ciphers, ', ') - ); + return 0; + }); + }, + + findWANByAddr: function(ipaddr) + { + for (var i = 0; i < this.rpcCache.ifstate.length; i++) + { + var ifstate = this.rpcCache.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 _luci2.tr('Unknown'); + return undefined; + }, + + findWAN: function() + { + return this.findWANByAddr('0.0.0.0'); + }, + + findWAN6: function() + { + return this.findWANByAddr('::'); + }, + + resolveAlias: function(ifname) + { + if (ifname instanceof L.NetworkModel.Device) + ifname = ifname.name(); + + var dev = this.deviceObjects[ifname]; + var seen = { }; + + while (dev && dev.kind == 'alias') + { + // loop + if (seen[dev.ifname]) + return undefined; + + var ifc = this.interfaceObjects[dev.sid]; + + seen[dev.ifname] = true; + dev = ifc ? this.deviceObjects[ifc.l3dev] : undefined; + } + + return dev ? this.getDevice(dev.ifname) : undefined; } }; - this.firewall = { - getZoneColor: function(zone) + this.NetworkModel.Device = Class.extend({ + wifiModeStrings: { + ap: L.tr('Master'), + sta: L.tr('Client'), + adhoc: L.tr('Ad-Hoc'), + monitor: L.tr('Monitor'), + wds: L.tr('Static WDS') + }, + + getStatus: function(key) { - if ($.isPlainObject(zone)) - zone = zone.name; + var s = L.NetworkModel.rpcCache.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 L.uci.get(pkg, sid, key); + }, + + set: function(key, val) + { + var sid = this.options.sid; + var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network'; + return L.uci.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 L.tr('Alias for network "%s"').format(this.options.ifname.substring(1)); + + case 'bridge': + return L.tr('Network bridge'); + + case 'ethernet': + return L.tr('Network device'); + + case 'tunnel': + switch (this.options.type) + { + case 1: /* tuntap */ + return L.tr('TAP device'); + + case 512: /* PPP */ + return L.tr('PPP tunnel'); + + case 768: /* IP-IP Tunnel */ + return L.tr('IP-in-IP tunnel'); + + case 769: /* IP6-IP6 Tunnel */ + return L.tr('IPv6-in-IPv6 tunnel'); + + case 776: /* IPv6-in-IPv4 */ + return L.tr('IPv6-over-IPv4 tunnel'); + break; + + case 778: /* GRE over IP */ + return L.tr('GRE-over-IP tunnel'); + + default: + return L.tr('Tunnel device'); + } + + case 'vlan': + return L.tr('VLAN %d on %s').format(this.options.vid, this.options.vsw.model); + + case 'wifi': + var o = this.options; + return L.trc('(Wifi-Mode) "(SSID)" on (radioX)', '%s "%h" on %s').format( + o.wmode ? this.wifiModeStrings[o.wmode] : L.tr('Unknown mode'), + o.wssid || '?', o.wdev + ); + } + + return L.tr('Unknown device'); + }, + + icon: function(up) + { + var kind = this.options.kind; + + if (kind == 'alias') + kind = 'ethernet'; + + if (typeof(up) == 'undefined') + up = this.isUp(); + + return L.globals.resource + '/icons/%s%s.png'.format(kind, up ? '' : '_disabled'); + }, + + isUp: function() + { + var l = L.NetworkModel.rpcCache.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 L.NetworkModel.Interface)) + net = L.NetworkModel.getInterface(net); + + if (net) + { + if (net.options.l3dev == this.options.ifname || + net.options.l2dev == this.options.ifname) + return true; + + var dev = L.NetworkModel.deviceObjects[net.options.l2dev]; + if (dev && dev.kind == 'bridge' && dev.ports) + return ($.inArray(this.options.ifname, dev.ports) > -1); + } + + return false; + }, + + getMTU: function() + { + var dev = L.NetworkModel.rpcCache.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 = L.NetworkModel.rpcCache.devstate[this.options.ifname]; + if (dev && dev.macaddr) + return dev.macaddr.toUpperCase(); + + return undefined; + }, + + getInterfaces: function() + { + return L.NetworkModel.getInterfacesByDevice(this.options.name); + }, + + getStatistics: function() + { + var s = this.getStatus('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 = L.NetworkModel.rpcCache.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 L.NetworkModel.Interface)) + iface = L.NetworkModel.getInterface(iface); + + if (!iface) + return; + + var ifnames = L.toArray(iface.get('ifname')); + if ($.inArray(this.options.ifname, ifnames) > -1) + iface.set('ifname', L.filterArray(ifnames, this.options.ifname)); + + if (this.options.kind != 'wifi') + return; + + var networks = L.toArray(this.get('network')); + if ($.inArray(iface.name(), networks) > -1) + this.set('network', L.filterArray(networks, iface.name())); + }, + + attachToInterface: function(iface) + { + if (!(iface instanceof L.NetworkModel.Interface)) + iface = L.NetworkModel.getInterface(iface); + + if (!iface) + return; + + if (this.options.kind != 'wifi') + { + var ifnames = L.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 = L.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({ + getStatus: function(key) + { + var s = L.NetworkModel.rpcCache.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 L.uci.get('network', this.options.name, key); + }, + + set: function(key, val) + { + return L.uci.set('network', this.options.name, key, val); + }, + + name: function() + { + return this.options.name; + }, + + protocol: function() + { + return (this.get('proto') || 'none'); + }, + + isUp: function() + { + return (this.getStatus('up') === true); + }, + + isVirtual: function() + { + return (typeof(this.options.sid) != 'string'); + }, + + getProtocol: function() + { + var prname = this.get('proto') || 'none'; + return L.NetworkModel.protocolHandlers[prname] || L.NetworkModel.protocolHandlers.none; + }, + + getUptime: function() + { + var uptime = this.getStatus('uptime'); + return isNaN(uptime) ? 0 : uptime; + }, + + getDevice: function(resolveAlias) + { + if (this.options.l3dev) + return L.NetworkModel.getDevice(this.options.l3dev); + + return undefined; + }, + + getPhysdev: function() + { + if (this.options.l2dev) + return L.NetworkModel.getDevice(this.options.l2dev); + + return undefined; + }, + + getSubdevices: function() + { + var rv = [ ]; + var dev = this.options.l2dev ? + L.NetworkModel.deviceObjects[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(L.NetworkModel.getDevice(dev.ports[i])); + + return rv; + }, + + getIPv4Addrs: function(mask) + { + var rv = [ ]; + var addrs = this.getStatus('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.getStatus('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.getStatus('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.getStatus('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.getStatus('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.getStatus('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.getStatus('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.getStatus('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 L.NetworkModel.Device({}); + return dev.getStatistics(); + }, + + getTrafficHistory: function() + { + var dev = this.getDevice() || new L.NetworkModel.Device({}); + return dev.getTrafficHistory(); + }, + + renderBadge: function() + { + var badge = $('') + .addClass('badge') + .text('%s: '.format(this.name())); + + var dev = this.getDevice(); + var subdevs = this.getSubdevices(); + + if (subdevs.length) + for (var j = 0; j < subdevs.length; j++) + badge.append($('') + .attr('src', subdevs[j].icon()) + .attr('title', '%s (%s)'.format(subdevs[j].description(), subdevs[j].name() || '?'))); + else if (dev) + badge.append($('') + .attr('src', dev.icon()) + .attr('title', '%s (%s)'.format(dev.description(), dev.name() || '?'))); + else + badge.append($('').text(L.tr('(No devices attached)'))); + + return badge; + }, + + 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 L.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 L.NetworkModel.Device)) + dev = L.NetworkModel.getDevice(dev); + + if (dev) + dev.attachToInterface(this); + } + } + }, + + changeProtocol: function(proto) + { + var pr = L.NetworkModel.protocolHandlers[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 = L.cbi.Map; + + var map = new mapwidget('network', { + caption: L.tr('Configure "%s"').format(self.name()) + }); + + var section = map.section(L.cbi.SingleSection, self.name(), { + anonymous: true + }); + + section.tab({ + id: 'general', + caption: L.tr('General Settings') + }); + + section.tab({ + id: 'advanced', + caption: L.tr('Advanced Settings') + }); + + section.tab({ + id: 'ipv6', + caption: L.tr('IPv6') + }); + + section.tab({ + id: 'physical', + caption: L.tr('Physical Settings') + }); + + + section.taboption('general', L.cbi.CheckboxValue, 'auto', { + caption: L.tr('Start on boot'), + optional: true, + initial: true + }); + + var pr = section.taboption('general', L.cbi.ListValue, 'proto', { + caption: L.tr('Protocol') + }); + + pr.ucivalue = function(sid) { + return self.get('proto') || 'none'; + }; + + var ok = section.taboption('general', L.cbi.ButtonValue, '_confirm', { + caption: L.tr('Really switch?'), + description: L.tr('Changing the protocol will clear all configuration for this interface!'), + text: L.tr('Change protocol') + }); + + ok.on('click', function(ev) { + self.changeProtocol(pr.formvalue(ev.data.sid)); + self.createForm(mapwidget).show(); + }); + + var protos = L.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', L.cbi.CheckboxValue, 'type', { + caption: L.tr('Network bridge'), + description: L.tr('Merges multiple devices into one logical bridge'), + optional: true, + enabled: 'bridge', + disabled: '', + initial: '' + }); - if (zone == 'lan') - return '#90f090'; - else if (zone == 'wan') - return '#f09090'; + section.taboption('physical', L.cbi.DeviceList, '__iface_multi', { + caption: L.tr('Devices'), + multiple: true, + bridges: false + }).depends('type', true); + + section.taboption('physical', L.cbi.DeviceList, '__iface_single', { + caption: L.tr('Device'), + multiple: false, + bridges: true + }).depends('type', false); + + var mac = section.taboption('physical', L.cbi.InputValue, 'macaddr', { + caption: L.tr('Override MAC'), + optional: true, + placeholder: device ? device.getMACAddress() : undefined, + datatype: 'macaddr' + }) - for (var i = 0, hash = 0; - i < zone.length; - hash = zone.charCodeAt(i++) + ((hash << 5) - hash)); + mac.ucivalue = function(sid) + { + if (device) + return device.get('macaddr'); - for (var i = 0, color = '#'; - i < 3; - color += ('00' + ((hash >> i++ * 8) & 0xFF).tozoneing(16)).slice(-2)); + return this.callSuper('ucivalue', sid); + }; - return color; - }, + mac.save = function(sid) + { + if (!this.changed(sid)) + return false; - findZoneByNetwork: function(network) - { - var self = this; - var zone = undefined; + if (device) + device.set('macaddr', this.formvalue(sid)); + else + this.callSuper('set', sid); - return _luci2.uci.foreach('firewall', 'zone', function(z) { - if (!z.name || !z.network) - return; + return true; + }; + } - if (!$.isArray(z.network)) - z.network = z.network.split(/\s+/); + section.taboption('physical', L.cbi.InputValue, 'mtu', { + caption: L.tr('Override MTU'), + optional: true, + placeholder: device ? device.getMTU() : undefined, + datatype: 'range(1, 9000)' + }); - for (var i = 0; i < z.network.length; i++) + section.taboption('physical', L.cbi.InputValue, 'metric', { + caption: L.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({ + getSystemInfo: L.rpc.declare({ object: 'system', method: 'info', expect: { '': { } } }), - getBoardInfo: _luci2.rpc.declare({ + getBoardInfo: L.rpc.declare({ object: 'system', method: 'board', expect: { '': { } } }), - getDiskInfo: _luci2.rpc.declare({ + getDiskInfo: L.rpc.declare({ object: 'luci2.system', method: 'diskfree', expect: { '': { } } @@ -1839,13 +3009,13 @@ function LuCI2() getInfo: function(cb) { - _luci2.rpc.batch(); + L.rpc.batch(); this.getSystemInfo(); this.getBoardInfo(); this.getDiskInfo(); - return _luci2.rpc.flush().then(function(info) { + return L.rpc.flush().then(function(info) { var rv = { }; $.extend(rv, info[0]); @@ -1856,43 +3026,8 @@ function LuCI2() }); }, - getProcessList: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'process_list', - expect: { processes: [ ] }, - filter: function(data) { - data.sort(function(a, b) { return a.pid - b.pid }); - return data; - } - }), - - getSystemLog: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'syslog', - expect: { log: '' } - }), - - getKernelLog: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'dmesg', - expect: { log: '' } - }), - - getZoneInfo: function(cb) - { - return $.getJSON(_luci2.globals.resource + '/zoneinfo.json', cb); - }, - - sendSignal: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'process_signal', - params: [ 'pid', 'signal' ], - filter: function(data) { - return (data == 0); - } - }), - initList: _luci2.rpc.declare({ + initList: L.rpc.declare({ object: 'luci2.system', method: 'init_list', expect: { initscripts: [ ] }, @@ -1913,7 +3048,7 @@ function LuCI2() }); }, - initRun: _luci2.rpc.declare({ + initRun: L.rpc.declare({ object: 'luci2.system', method: 'init_action', params: [ 'name', 'action' ], @@ -1922,257 +3057,30 @@ function LuCI2() } }), - initStart: function(init, cb) { return _luci2.system.initRun(init, 'start', cb) }, - initStop: function(init, cb) { return _luci2.system.initRun(init, 'stop', cb) }, - initRestart: function(init, cb) { return _luci2.system.initRun(init, 'restart', cb) }, - initReload: function(init, cb) { return _luci2.system.initRun(init, 'reload', cb) }, - initEnable: function(init, cb) { return _luci2.system.initRun(init, 'enable', cb) }, - initDisable: function(init, cb) { return _luci2.system.initRun(init, 'disable', cb) }, - - - getRcLocal: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'rclocal_get', - expect: { data: '' } - }), - - setRcLocal: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'rclocal_set', - params: [ 'data' ] - }), - - - getCrontab: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'crontab_get', - expect: { data: '' } - }), - - setCrontab: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'crontab_set', - params: [ 'data' ] - }), - - - getSSHKeys: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'sshkeys_get', - expect: { keys: [ ] } - }), - - setSSHKeys: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'sshkeys_set', - params: [ 'keys' ] - }), - - - setPassword: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'password_set', - params: [ 'user', 'password' ] - }), - - - listLEDs: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'led_list', - expect: { leds: [ ] } - }), - - listUSBDevices: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'usb_list', - expect: { devices: [ ] } - }), - - - testUpgrade: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'upgrade_test', - expect: { '': { } } - }), - - startUpgrade: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'upgrade_start', - params: [ 'keep' ] - }), - - cleanUpgrade: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'upgrade_clean' - }), - - - restoreBackup: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'backup_restore' - }), - - cleanBackup: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'backup_clean' - }), - - - getBackupConfig: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'backup_config_get', - expect: { config: '' } - }), - - setBackupConfig: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'backup_config_set', - params: [ 'data' ] - }), - - - listBackup: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'backup_list', - expect: { files: [ ] } - }), - - - testReset: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'reset_test', - expect: { supported: false } - }), - - startReset: _luci2.rpc.declare({ - object: 'luci2.system', - method: 'reset_start' - }), + initStart: function(init, cb) { return L.system.initRun(init, 'start', cb) }, + initStop: function(init, cb) { return L.system.initRun(init, 'stop', cb) }, + initRestart: function(init, cb) { return L.system.initRun(init, 'restart', cb) }, + initReload: function(init, cb) { return L.system.initRun(init, 'reload', cb) }, + initEnable: function(init, cb) { return L.system.initRun(init, 'enable', cb) }, + initDisable: function(init, cb) { return L.system.initRun(init, 'disable', cb) }, - performReboot: _luci2.rpc.declare({ + performReboot: L.rpc.declare({ object: 'luci2.system', method: 'reboot' }) }; - this.opkg = { - updateLists: _luci2.rpc.declare({ - object: 'luci2.opkg', - method: 'update', - expect: { '': { } } - }), - - _allPackages: _luci2.rpc.declare({ - object: 'luci2.opkg', - method: 'list', - params: [ 'offset', 'limit', 'pattern' ], - expect: { '': { } } - }), - - _installedPackages: _luci2.rpc.declare({ - object: 'luci2.opkg', - method: 'list_installed', - params: [ 'offset', 'limit', 'pattern' ], - expect: { '': { } } - }), - - _findPackages: _luci2.rpc.declare({ - object: 'luci2.opkg', - method: 'find', - params: [ 'offset', 'limit', 'pattern' ], - expect: { '': { } } - }), - - _fetchPackages: function(action, offset, limit, pattern) - { - var packages = [ ]; - - return action(offset, limit, pattern).then(function(list) { - if (!list.total || !list.packages) - return { length: 0, total: 0 }; - - packages.push.apply(packages, list.packages); - packages.total = list.total; - - if (limit <= 0) - limit = list.total; - - if (packages.length >= limit) - return packages; - - _luci2.rpc.batch(); - - for (var i = offset + packages.length; i < limit; i += 100) - action(i, (Math.min(i + 100, limit) % 100) || 100, pattern); - - return _luci2.rpc.flush(); - }).then(function(lists) { - for (var i = 0; i < lists.length; i++) - { - if (!lists[i].total || !lists[i].packages) - continue; - - packages.push.apply(packages, lists[i].packages); - packages.total = lists[i].total; - } - - return packages; - }); - }, - - listPackages: function(offset, limit, pattern) - { - return _luci2.opkg._fetchPackages(_luci2.opkg._allPackages, offset, limit, pattern); - }, - - installedPackages: function(offset, limit, pattern) - { - return _luci2.opkg._fetchPackages(_luci2.opkg._installedPackages, offset, limit, pattern); - }, - - findPackages: function(offset, limit, pattern) - { - return _luci2.opkg._fetchPackages(_luci2.opkg._findPackages, offset, limit, pattern); - }, - - installPackage: _luci2.rpc.declare({ - object: 'luci2.opkg', - method: 'install', - params: [ 'package' ], - expect: { '': { } } - }), - - removePackage: _luci2.rpc.declare({ - object: 'luci2.opkg', - method: 'remove', - params: [ 'package' ], - expect: { '': { } } - }), - - getConfig: _luci2.rpc.declare({ - object: 'luci2.opkg', - method: 'config_get', - expect: { config: '' } - }), - - setConfig: _luci2.rpc.declare({ - object: 'luci2.opkg', - method: 'config_set', - params: [ 'data' ] - }) - }; - this.session = { - login: _luci2.rpc.declare({ + login: L.rpc.declare({ object: 'session', method: 'login', params: [ 'username', 'password' ], expect: { '': { } } }), - access: _luci2.rpc.declare({ + access: L.rpc.declare({ object: 'session', method: 'access', params: [ 'scope', 'object', 'function' ], @@ -2181,21 +3089,21 @@ function LuCI2() isAlive: function() { - return _luci2.session.access('ubus', 'session', 'access'); + return L.session.access('ubus', 'session', 'access'); }, startHeartbeat: function() { this._hearbeatInterval = window.setInterval(function() { - _luci2.session.isAlive().then(function(alive) { + L.session.isAlive().then(function(alive) { if (!alive) { - _luci2.session.stopHeartbeat(); - _luci2.ui.login(true); + L.session.stopHeartbeat(); + L.ui.login(true); } }); - }, _luci2.globals.timeout * 2); + }, L.globals.timeout * 2); }, stopHeartbeat: function() @@ -2208,28 +3116,28 @@ function LuCI2() }, - _acls: { }, + aclCache: { }, - _fetch_acls: _luci2.rpc.declare({ + callAccess: L.rpc.declare({ object: 'session', method: 'access', expect: { '': { } } }), - _fetch_acls_cb: function(acls) + callAccessCallback: function(acls) { - _luci2.session._acls = acls; + L.session.aclCache = acls; }, updateACLs: function() { - return _luci2.session._fetch_acls() - .then(_luci2.session._fetch_acls_cb); + return L.session.callAccess() + .then(L.session.callAccessCallback); }, hasACL: function(scope, object, func) { - var acls = _luci2.session._acls; + var acls = L.session.aclCache; if (typeof(func) == 'undefined') return (acls && acls[scope] && acls[scope][object]); @@ -2265,8 +3173,9 @@ function LuCI2() var win = $(window); var body = $('body'); - var state = _luci2.ui._loading || (_luci2.ui._loading = { + var state = L.ui._loading || (L.ui._loading = { modal: $('
') + .css('z-index', 2000) .addClass('modal fade') .append($('
') .addClass('modal-dialog') @@ -2274,7 +3183,7 @@ function LuCI2() .addClass('modal-content luci2-modal-loader') .append($('
') .addClass('modal-body') - .text(_luci2.tr('Loading data…'))))) + .text(L.tr('Loading data…'))))) .appendTo(body) .modal({ backdrop: 'static', @@ -2290,7 +3199,7 @@ function LuCI2() var win = $(window); var body = $('body'); - var state = _luci2.ui._dialog || (_luci2.ui._dialog = { + var state = L.ui._dialog || (L.ui._dialog = { dialog: $('
') .addClass('modal fade') .append($('
') @@ -2305,7 +3214,7 @@ function LuCI2() .addClass('modal-body')) .append($('
') .addClass('modal-footer') - .append(_luci2.ui.button(_luci2.tr('Close'), 'primary') + .append(L.ui.button(L.tr('Close'), 'primary') .click(function() { $(this).parents('div.modal').modal('hide'); }))))) @@ -2319,42 +3228,53 @@ function LuCI2() { 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') { - ftr.append(_luci2.ui.button(_luci2.tr('Ok'), 'primary') - .click(options.confirm || function() { _luci2.ui.dialog(false) })); + ftr.append(L.ui.button(L.tr('Ok'), 'primary') + .click(options.confirm || function() { L.ui.dialog(false) })); - ftr.append(_luci2.ui.button(_luci2.tr('Cancel'), 'default') - .click(options.cancel || function() { _luci2.ui.dialog(false) })); + ftr.append(L.ui.button(L.tr('Cancel'), 'default') + .click(options.cancel || function() { L.ui.dialog(false) })); } else if (options.style == 'close') { - ftr.append(_luci2.ui.button(_luci2.tr('Close'), 'primary') - .click(options.close || function() { _luci2.ui.dialog(false) })); + ftr.append(L.ui.button(L.tr('Close'), 'primary') + .click(options.close || function() { L.ui.dialog(false) })); } else if (options.style == 'wait') { - ftr.append(_luci2.ui.button(_luci2.tr('Close'), 'primary') + ftr.append(L.ui.button(L.tr('Close'), 'primary') .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) { - var state = _luci2.ui._upload || (_luci2.ui._upload = { + var state = L.ui._upload || (L.ui._upload = { form: $('
') .attr('method', 'post') .attr('action', '/cgi-bin/luci-upload') @@ -2396,17 +3316,17 @@ function LuCI2() json = $.parseJSON(body.innerHTML); } catch(e) { json = { - message: _luci2.tr('Invalid server response received'), - error: [ -1, _luci2.tr('Invalid data') ] + message: L.tr('Invalid server response received'), + error: [ -1, L.tr('Invalid data') ] }; }; if (json.error) { L.ui.dialog(L.tr('File upload'), [ - $('

').text(_luci2.tr('The file upload failed with the server response below:')), + $('

').text(L.tr('The file upload failed with the server response below:')), $('

').addClass('alert-message').text(json.message || json.error[1]),
-							$('

').text(_luci2.tr('In case of network problems try uploading the file again.')) + $('

').text(L.tr('In case of network problems try uploading the file again.')) ], { style: 'close' }); } else if (typeof(state.success_cb) == 'function') @@ -2428,7 +3348,7 @@ function LuCI2() f.hide(); b.show(); - p.text(_luci2.tr('File upload in progress …')); + p.text(L.tr('File upload in progress …')); state.form.parent().parent().find('button').prop('disabled', true); } @@ -2436,14 +3356,14 @@ function LuCI2() state.form.find('.progress').hide(); state.form.find('.cbi-input-file').val('').show(); - state.form.find('p').text(content || _luci2.tr('Select the file to upload and press "%s" to proceed.').format(_luci2.tr('Ok'))); + state.form.find('p').text(content || L.tr('Select the file to upload and press "%s" to proceed.').format(L.tr('Ok'))); - state.form.find('[name=sessionid]').val(_luci2.globals.sid); + state.form.find('[name=sessionid]').val(L.globals.sid); state.form.find('[name=filename]').val(options.filename); state.success_cb = options.success; - _luci2.ui.dialog(title || _luci2.tr('File upload'), state.form, { + L.ui.dialog(title || L.tr('File upload'), state.form, { style: 'confirm', confirm: state.confirm_cb }); @@ -2457,9 +3377,9 @@ function LuCI2() var images = $(); var interval, timeout; - _luci2.ui.dialog( - _luci2.tr('Waiting for device'), [ - $('

').text(_luci2.tr('Please stand by while the device is reconfiguring …')), + L.ui.dialog( + L.tr('Waiting for device'), [ + $('

').text(L.tr('Please stand by while the device is reconfiguring …')), $('

') .css('width', '100%') .addClass('progressbar') @@ -2472,7 +3392,7 @@ function LuCI2() for (var i = 0; i < protocols.length; i++) images = images.add($('').attr('url', protocols[i] + '://' + address + ':' + ports[i])); - //_luci2.network.getNetworkStatus(function(s) { + //L.network.getNetworkStatus(function(s) { // for (var i = 0; i < protocols.length; i++) // { // for (var j = 0; j < s.length; j++) @@ -2487,12 +3407,12 @@ function LuCI2() //}).then(function() { images.on('load', function() { var url = this.getAttribute('url'); - _luci2.session.isAlive().then(function(access) { + L.session.isAlive().then(function(access) { if (access) { window.clearTimeout(timeout); window.clearInterval(interval); - _luci2.ui.dialog(false); + L.ui.dialog(false); images = null; } else @@ -2504,7 +3424,7 @@ function LuCI2() interval = window.setInterval(function() { images.each(function() { - this.setAttribute('src', this.getAttribute('url') + _luci2.globals.resource + '/icons/loading.gif?r=' + Math.random()); + this.setAttribute('src', this.getAttribute('url') + L.globals.resource + '/icons/loading.gif?r=' + Math.random()); }); }, 5000); @@ -2512,9 +3432,9 @@ function LuCI2() window.clearInterval(interval); images.off('load'); - _luci2.ui.dialog( - _luci2.tr('Device not responding'), - _luci2.tr('The device was not responding within 180 seconds, you might need to manually reconnect your computer or use SSH to regain access.'), + L.ui.dialog( + L.tr('Device not responding'), + L.tr('The device was not responding within 180 seconds, you might need to manually reconnect your computer or use SSH to regain access.'), { style: 'close' } ); }, 180000); @@ -2523,16 +3443,16 @@ function LuCI2() login: function(invalid) { - var state = _luci2.ui._login || (_luci2.ui._login = { + var state = L.ui._login || (L.ui._login = { form: $('') .attr('target', '') .attr('method', 'post') .append($('

') .addClass('alert-message') - .text(_luci2.tr('Wrong username or password given!'))) + .text(L.tr('Wrong username or password given!'))) .append($('

') .append($('

') .append($('

') - .text(_luci2.tr('Enter your username and password above, then click "%s" to proceed.').format(_luci2.tr('Ok')))), + .text(L.tr('Enter your username and password above, then click "%s" to proceed.').format(L.tr('Ok')))), response_cb: function(response) { if (!response.ubus_rpc_session) { - _luci2.ui.login(true); + L.ui.login(true); } else { - _luci2.globals.sid = response.ubus_rpc_session; - _luci2.setHash('id', _luci2.globals.sid); - _luci2.session.startHeartbeat(); - _luci2.ui.dialog(false); + L.globals.sid = response.ubus_rpc_session; + L.setHash('id', L.globals.sid); + L.session.startHeartbeat(); + L.ui.dialog(false); state.deferred.resolve(); } }, @@ -2580,9 +3500,9 @@ function LuCI2() if (!u) return; - _luci2.ui.dialog( - _luci2.tr('Logging in'), [ - $('

').text(_luci2.tr('Log in in progress …')), + L.ui.dialog( + L.tr('Logging in'), [ + $('

').text(L.tr('Log in in progress …')), $('

') .css('width', '100%') .addClass('progressbar') @@ -2592,8 +3512,8 @@ function LuCI2() ], { style: 'wait' } ); - _luci2.globals.sid = '00000000000000000000000000000000'; - _luci2.session.login(u, p).then(state.response_cb); + L.globals.sid = '00000000000000000000000000000000'; + L.session.login(u, p).then(state.response_cb); } }); @@ -2601,20 +3521,20 @@ function LuCI2() state.deferred = $.Deferred(); /* try to find sid from hash */ - var sid = _luci2.getHash('id'); + var sid = L.getHash('id'); if (sid && sid.match(/^[a-f0-9]{32}$/)) { - _luci2.globals.sid = sid; - _luci2.session.isAlive().then(function(access) { + L.globals.sid = sid; + L.session.isAlive().then(function(access) { if (access) { - _luci2.session.startHeartbeat(); + L.session.startHeartbeat(); state.deferred.resolve(); } else { - _luci2.setHash('id', undefined); - _luci2.ui.login(); + L.setHash('id', undefined); + L.ui.login(); } }); @@ -2626,7 +3546,7 @@ function LuCI2() else state.form.find('.alert-message').hide(); - _luci2.ui.dialog(_luci2.tr('Authorization Required'), state.form, { + L.ui.dialog(L.tr('Authorization Required'), state.form, { style: 'confirm', confirm: state.confirm_cb }); @@ -2636,7 +3556,7 @@ function LuCI2() return state.deferred; }, - cryptPassword: _luci2.rpc.declare({ + cryptPassword: L.rpc.declare({ object: 'luci2.ui', method: 'crypt', params: [ 'data' ], @@ -2644,7 +3564,7 @@ function LuCI2() }), - _acl_merge_scope: function(acl_scope, scope) + mergeACLScope: function(acl_scope, scope) { if ($.isArray(scope)) { @@ -2666,19 +3586,19 @@ function LuCI2() } }, - _acl_merge_permission: function(acl_perm, perm) + mergeACLPermission: function(acl_perm, perm) { if ($.isPlainObject(perm)) { for (var scope_name in perm) { var acl_scope = acl_perm[scope_name] || (acl_perm[scope_name] = { }); - this._acl_merge_scope(acl_scope, perm[scope_name]); + L.ui.mergeACLScope(acl_scope, perm[scope_name]); } } }, - _acl_merge_group: function(acl_group, group) + mergeACLGroup: function(acl_group, group) { if ($.isPlainObject(group)) { @@ -2688,88 +3608,112 @@ function LuCI2() if (group.read) { var acl_perm = acl_group.read || (acl_group.read = { }); - this._acl_merge_permission(acl_perm, group.read); + L.ui.mergeACLPermission(acl_perm, group.read); } if (group.write) { var acl_perm = acl_group.write || (acl_group.write = { }); - this._acl_merge_permission(acl_perm, group.write); + L.ui.mergeACLPermission(acl_perm, group.write); } } }, - _acl_merge_tree: function(acl_tree, tree) + callACLsCallback: function(trees) { - if ($.isPlainObject(tree)) + var acl_tree = { }; + + for (var i = 0; i < trees.length; i++) { - for (var group_name in tree) + if (!$.isPlainObject(trees[i])) + continue; + + for (var group_name in trees[i]) { var acl_group = acl_tree[group_name] || (acl_tree[group_name] = { }); - this._acl_merge_group(acl_group, tree[group_name]); + L.ui.mergeACLGroup(acl_group, trees[i][group_name]); } } + + return acl_tree; }, - listAvailableACLs: _luci2.rpc.declare({ + callACLs: L.rpc.declare({ object: 'luci2.ui', method: 'acls', - expect: { acls: [ ] }, - filter: function(trees) { - var acl_tree = { }; - for (var i = 0; i < trees.length; i++) - _luci2.ui._acl_merge_tree(acl_tree, trees[i]); - return acl_tree; - } + expect: { acls: [ ] } }), - renderMainMenu: _luci2.rpc.declare({ + getAvailableACLs: function() + { + return this.callACLs().then(this.callACLsCallback); + }, + + renderChangeIndicator: function() + { + return $('