X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fluci2%2Fui.git;a=blobdiff_plain;f=luci2%2Fhtdocs%2Fluci2%2Fluci2.js;h=291af572d31119ec0e318aa03a68220b420ff3bd;hp=a012e5f662d3479579c090dd281b0058ed089271;hb=8f89e46fcaec5cb1deb9e73b9f77b3b1cd0d8ad1;hpb=2f0e65dd27549ef4fde5b18588083968a59b66bd diff --git a/luci2/htdocs/luci2/luci2.js b/luci2/htdocs/luci2/luci2.js index a012e5f..291af57 100644 --- a/luci2/htdocs/luci2/luci2.js +++ b/luci2/htdocs/luci2/luci2.js @@ -177,52 +177,6 @@ function LuCI2() { var _luci2 = this; - var alphacmp = function(a, b) - { - if (a < b) - return -1; - else if (a > b) - return 1; - else - return 0; - }; - - var retcb = function(cb, rv) - { - if (typeof(cb) == 'function') - cb(rv); - - return rv; - }; - - var isa = function(x, t) - { - if (typeof(x) != 'string' && typeof(t) == 'string') - return (Object.prototype.toString.call(x) == '[object ' + t + ']'); - - return (Object.prototype.toString.call(x) == Object.prototype.toString.call(t)); - }; - - var rcall = function(obj, func, params, res_attr, res_default, cb, filter) - { - if (typeof(params) == 'undefined') - params = { }; - - return _luci2.rpc.call(obj, func, params).then(function(res) { - if (res[0] != 0 || typeof(res[1]) == 'undefined') - return retcb(cb, res_default); - - var rv = (typeof(res_attr) != 'undefined') ? res[1][res_attr] : res[1]; - if (typeof(rv) == 'undefined' || (typeof(res_default) != 'undefined' && !isa(rv, res_default))) - return retcb(cb, res_default); - - if (typeof(filter) == 'function') - rv = filter(rv); - - return retcb(cb, rv); - }); - }; - var Class = function() { }; Class.extend = function(properties) @@ -274,7 +228,7 @@ function LuCI2() return obj; }; - this.deferred = function(x) + this.isDeferred = function(x) { return (typeof(x) == 'object' && typeof(x.then) == 'function' && @@ -283,7 +237,7 @@ function LuCI2() this.deferrable = function() { - if (this.deferred(arguments[0])) + if (this.isDeferred(arguments[0])) return arguments[0]; var d = $.Deferred(); @@ -433,349 +387,460 @@ function LuCI2() }; this.globals = { - resource: '/luci2' + timeout: 15000, + resource: '/luci2', + sid: '00000000000000000000000000000000' }; this.rpc = { - _msg_id: 1, + _id: 1, + _batch: undefined, + _requests: { }, + + _call: function(req, cb) + { + return $.ajax('/ubus', { + cache: false, + contentType: 'application/json', + data: JSON.stringify(req), + dataType: 'json', + type: 'POST', + timeout: _luci2.globals.timeout + }).then(cb); + }, - _wrap_msg: function(method, object, func, args) + _list_cb: function(msg) { - if (typeof(args) != 'object') - args = { }; + /* verify message frame */ + if (typeof(msg) != 'object' || msg.jsonrpc != '2.0' || !msg.id) + throw 'Invalid JSON response'; - return { - id: _luci2.rpc._msg_id++, - jsonrpc: "2.0", - method: method, - params: (method == 'call') ? [ _luci2.globals.sid, object, func, args ] : object - }; + return msg.result; }, - _parse_response: function(keys, priv) + _call_cb: function(msg) { - return function(data) { - var obj; - try { - obj = $.parseJSON(data); - } catch(e) { } + var data = [ ]; + var type = Object.prototype.toString; - if (typeof(obj) != 'object') - return undefined; + if (!$.isArray(msg)) + msg = [ msg ]; + + for (var i = 0; i < msg.length; i++) + { + /* verify message frame */ + if (typeof(msg[i]) != 'object' || msg[i].jsonrpc != '2.0' || !msg[i].id) + throw 'Invalid JSON response'; + + /* fetch related request info */ + var req = _luci2.rpc._requests[msg[i].id]; + if (typeof(req) != 'object') + throw 'No related request for JSON response'; - /* is a batched response */ - if (keys) + /* fetch response attribute and verify returned type */ + var ret = undefined; + + if ($.isArray(msg[i].result) && msg[i].result[0] == 0) + ret = (msg[i].result.length > 1) ? msg[i].result[1] : msg[i].result[0]; + + if (req.expect) { - var rv = { }; - for (var i = 0; i < obj.length; i++) + for (var key in req.expect) { - var p = (typeof(priv) != 'undefined') ? priv[i] : undefined; + if (typeof(ret) != 'undefined' && key != '') + ret = ret[key]; - if ($.isArray(obj[i].result) && typeof(priv) != 'undefined') - obj[i].result[2] = p; + if (type.call(ret) != type.call(req.expect[key])) + ret = req.expect[key]; - if (obj[i].jsonrpc != '2.0' || obj[i].error || !obj[i].result) - rv[keys[i]] = [ 4 /* UBUS_STATUS_NO_DATA */, undefined, p ]; - else - rv[keys[i]] = obj[i].result; + break; } - return rv; } - if (obj.jsonrpc != '2.0' || obj.error || !obj.result) - return [ 4 /* UBUS_STATUS_NO_DATA */, undefined, priv ]; + /* apply filter */ + if (typeof(req.filter) == 'function') + { + req.priv[0] = ret; + req.priv[1] = req.params; + ret = req.filter.apply(_luci2.rpc, req.priv); + } - if ($.isArray(obj.result) && typeof(priv) != 'undefined') - obj.result[2] = priv; + /* store response data */ + if (typeof(req.index) == 'number') + data[req.index] = ret; + else + data = ret; - return obj.result; - }; + /* delete request object */ + delete _luci2.rpc._requests[msg[i].id]; + } + + return data; }, - _post_msg: function(message, cb, keys, priv) + list: function() { - return $.ajax('/ubus', { - cache: false, - contentType: 'application/json', - data: JSON.stringify(message), - dataFilter: _luci2.rpc._parse_response(keys, priv), - dataType: 'text', - success: cb, - type: 'POST' - }); + var params = [ ]; + for (var i = 0; i < arguments.length; i++) + params[i] = arguments[i]; + + var msg = { + jsonrpc: '2.0', + id: this._id++, + method: 'list', + params: (params.length > 0) ? params : undefined + }; + + return this._call(msg, this._list_cb); }, - _post_single: function(object, method, args, cb, priv) + batch: function() { - var msg = _luci2.rpc._wrap_msg('call', object, method, args, priv); - return _luci2.rpc._post_msg(msg, cb, undefined, priv); + if (!$.isArray(this._batch)) + this._batch = [ ]; }, - _post_batch: function(methods, cb) + flush: function() { - if (typeof(methods) != 'object') - return undefined; + if (!$.isArray(this._batch)) + return _luci2.deferrable([ ]); - var msgs = [ ]; - var keys = [ ]; - var priv = [ ]; + var req = this._batch; + delete this._batch; - for (var k in methods) - { - if (typeof(methods[k]) != 'object' || methods[k].length < 2) - continue; + /* call rpc */ + return this._call(req, this._call_cb); + }, - keys.push(k); - priv.push(methods[k][3]); - msgs.push(_luci2.rpc._wrap_msg('call', methods[k][0], methods[k][1], methods[k][2])); - } + declare: function(options) + { + var _rpc = this; - if (msgs.length > 0) - return _luci2.rpc._post_msg(msgs, cb, keys, priv); + return function() { + /* build parameter object */ + var p_off = 0; + var params = { }; + if ($.isArray(options.params)) + for (p_off = 0; p_off < options.params.length; p_off++) + params[options.params[p_off]] = arguments[p_off]; - return _luci2.deferrable([ ]); - }, + /* all remaining arguments are private args */ + var priv = [ undefined, undefined ]; + for (; p_off < arguments.length; p_off++) + priv.push(arguments[p_off]); - call: function() - { - var a = arguments; - if (typeof a[0] == 'string') - return _luci2.rpc._post_single(a[0], a[1], a[2], a[3], a[4]); - else - return _luci2.rpc._post_batch(a[0], a[1]); - }, + /* store request info */ + var req = _rpc._requests[_rpc._id] = { + expect: options.expect, + filter: options.filter, + params: params, + priv: priv + }; - list: function(objects) - { - var msg = _luci2.rpc._wrap_msg('list', objects); - return _luci2.rpc._post_msg(msg); - }, + /* build message object */ + var msg = { + jsonrpc: '2.0', + id: _rpc._id++, + method: 'call', + params: [ + _luci2.globals.sid, + options.object, + options.method, + params + ] + }; - access: function(scope, object, method, cb) - { - return _luci2.rpc._post_single('session', 'access', { - 'sid': _luci2.globals.sid, - 'scope': scope, - 'object': object, - 'function': method - }, function(rv) { - return retcb(cb, (rv[0] == 0 && rv[1] && rv[1].access == true)); - }); + /* when a batch is in progress then store index in request data + * and push message object onto the stack */ + if ($.isArray(_rpc._batch)) + { + req.index = _rpc._batch.push(msg) - 1; + return _luci2.deferrable(msg); + } + + /* call rpc */ + return _rpc._call(msg, _rpc._call_cb); + }; } }; this.uci = { - writable: function(cb) + writable: function() { - return _luci2.rpc.access('ubus', 'uci', 'commit', cb); + return _luci2.session.access('ubus', 'uci', 'commit'); }, - add: function(config, type, cb) - { - return rcall('uci', 'add', { config: config, type: type }, 'section', '', cb); - }, + add: _luci2.rpc.declare({ + object: 'uci', + method: 'add', + params: [ 'config', 'type', 'name', 'values' ], + expect: { section: '' } + }), apply: function() { }, + configs: _luci2.rpc.declare({ + object: 'uci', + method: 'configs', + expect: { configs: [ ] } + }), + + _changes: _luci2.rpc.declare({ + object: 'uci', + method: 'changes', + params: [ 'config' ], + expect: { changes: [ ] } + }), + changes: function(config) { - return rcall('uci', 'changes', { config: config }, 'changes', [ ], cb); - }, + if (typeof(config) == 'string') + return this._changes(config); - commit: function(config) - { - return rcall('uci', 'commit', { config: config }, undefined, undefined, cb); - }, + var configlist; + return this.configs().then(function(configs) { + _luci2.rpc.batch(); + configlist = configs; - 'delete': function(config, section, option) - { - var req = { config: config, section: section }; + for (var i = 0; i < configs.length; i++) + _luci2.uci._changes(configs[i]); - if (isa(option, 'Array')) - req.options = option; - else - req.option = option; + return _luci2.rpc.flush(); + }).then(function(changes) { + var rv = { }; + + for (var i = 0; i < configlist.length; i++) + if (changes[i].length) + rv[configlist[i]] = changes[i]; - return rcall('uci', 'delete', req, undefined, undefined, cb); + return rv; + }); }, - delete_all: function(config, type, matches) + commit: _luci2.rpc.declare({ + object: 'uci', + method: 'commit', + params: [ 'config' ] + }), + + _delete_one: _luci2.rpc.declare({ + object: 'uci', + method: 'delete', + params: [ 'config', 'section', 'option' ] + }), + + _delete_multiple: _luci2.rpc.declare({ + object: 'uci', + method: 'delete', + params: [ 'config', 'section', 'options' ] + }), + + 'delete': function(config, section, option) { - return rcall('uci', 'delete', { config: config, type: type, match: matches }, undefined, undefined, cb); + if ($.isArray(option)) + return this._delete_multiple(config, section, option); + else + return this._delete_one(config, section, option); }, + delete_all: _luci2.rpc.declare({ + object: 'uci', + method: 'delete', + params: [ 'config', 'type', 'match' ] + }), + + _foreach: _luci2.rpc.declare({ + object: 'uci', + method: 'get', + params: [ 'config', 'type' ], + expect: { values: { } } + }), + foreach: function(config, type, cb) { - return rcall('uci', 'get', { config: config, type: type }, 'values', { }, function(sections) { + return this._foreach(config, type).then(function(sections) { for (var s in sections) cb(sections[s]); }); }, - get: function(config, section, option, cb) - { - return rcall('uci', 'get', { config: config, section: section, option: option }, undefined, { }, function(res) { - if (typeof(option) == 'undefined') - return retcb(cb, (res.values && res.values['.type']) ? res.values['.type'] : undefined); - - return retcb(cb, res.value); - }); - }, - - get_all: function(config, section, cb) - { - return rcall('uci', 'get', { config: config, section: section }, 'values', { }, cb); - }, + 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; + } + }), + + 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; + } + }), - get_first: function(config, type, option, cb) + get_first: function(config, type, option) { - return rcall('uci', 'get', { config: config, type: type }, 'values', { }, function(sections) { + 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 (typeof(val) != 'undefined') - return retcb(cb, val); + return val; } - return retcb(cb, undefined); + return undefined; }); }, - section: function(config, type, name, values, cb) - { - return rcall('uci', 'add', { config: config, type: type, name: name, values: values }, 'section', undefined, cb); - }, + section: _luci2.rpc.declare({ + object: 'uci', + method: 'add', + params: [ 'config', 'type', 'name', 'values' ], + expect: { section: '' } + }), - set: function(config, section, option, value, cb) + _set: _luci2.rpc.declare({ + object: 'uci', + method: 'set', + params: [ 'config', 'section', 'values' ] + }), + + set: function(config, section, option, value) { if (typeof(value) == 'undefined' && typeof(option) == 'string') - return rcall('uci', 'add', { config: config, section: section, type: option }, undefined, undefined, cb); - else if (isa(option, 'Object')) - return rcall('uci', 'set', { config: config, section: section, values: option }, undefined, undefined, cb); - else + return this.section(config, section, option); /* option -> type */ + else if ($.isPlainObject(option)) + return this._set(config, section, option); /* option -> values */ var values = { }; values[option] = value; - return rcall('uci', 'set', { config: config, section: section, values: values }, undefined, undefined, cb); + return this._set(config, section, values); }, - order: function(config, sections, cb) - { - return rcall('uci', 'order', { config: config, sections: sections }, undefined, undefined, cb); - } + order: _luci2.rpc.declare({ + object: 'uci', + method: 'order', + params: [ 'config', 'sections' ] + }) }; this.network = { - getNetworkStatus: function(cb) - { - var ifaces = [ ]; - var assign = function(target, key) - { - return function(value) { - if (typeof(value) != 'undefined' && !$.isEmptyObject(value)) - target[key] = value; - }; - }; + 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 _luci2.rpc.list().then(function(data) { - var requests = [ ]; + 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; + } + }), - for (var i = 0; i < data.length; i++) - { - if (data[i].indexOf('network.interface.') != 0) - continue; + getNetworkStatus: function() + { + var nets = [ ]; + var devs = { }; - var ifname = data[i].substring(18); - if (ifname == 'loopback') - continue; + return this.listNetworkNames().then(function(names) { + _luci2.rpc.batch(); - var iface = { 'name': ifname }; + for (var i = 0; i < names.length; i++) + _luci2.network.getInterfaceStatus(names[i]); - ifaces.push(iface); - requests.push(['network.interface', 'status', { 'interface': ifname }, iface]); + 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 _luci2.rpc.call(requests, function(responses) { - for (var key in responses) - if (responses[key][0] == 0 && responses[key][1] && responses[key][2]) - $.extend(responses[key][2], responses[key][1]); - }); - }).then(function() { - var requests = [ ]; + _luci2.rpc.batch(); - for (var i = 0; i < ifaces.length; i++) - { - var iface = ifaces[i]; + for (var dev in devs) + _luci2.network.getDeviceStatus(dev); - var dev = iface.l3_device || iface.l2_device; - if (!dev) - continue; + return _luci2.rpc.flush(); + }).then(function(devices) { + _luci2.rpc.batch(); - iface.device = { 'name': dev }; - requests[dev] = ['network.device', 'status', { 'name': dev }, iface.device]; - } + for (var i = 0; i < devices.length; i++) + { + var brm = devices[i]['bridge-members']; + delete devices[i]['bridge-members']; - return _luci2.rpc.call(requests, function(responses) { - for (var key in responses) - if (responses[key][0] == 0 && responses[key][1] && responses[key][2]) - $.extend(responses[key][2], responses[key][1]); - }); - }).then(function() { - var requests = [ ]; + $.extend(devs[devices[i]['device']], devices[i]); - for (var i = 0; i < ifaces.length; i++) - { - var iface = ifaces[i]; - if (!iface.device) + if (!brm) continue; - var subdevs = iface.device['bridge-members']; - if (!subdevs) - continue; + devs[devices[i]['device']].subdevices = [ ]; - iface.subdevices = [ ]; - for (var j = 0; j < subdevs.length; j++) + for (var j = 0; j < brm.length; j++) { - iface.subdevices[j] = { 'name': subdevs[j] }; - requests.push(['network.device', 'status', { 'name': subdevs[j] }, iface.subdevices[j]]); + if (!devs[brm[j]]) + { + devs[brm[j]] = { }; + _luci2.network.getDeviceStatus(brm[j]); + } + + devs[devices[i]['device']].subdevices[j] = devs[brm[j]]; } } - return _luci2.rpc.call(requests, function(responses) { - for (var key in responses) - if (responses[key][0] == 0 && responses[key][1] && responses[key][2]) - $.extend(responses[key][2], responses[key][1]); - }); - }).then(function() { - var requests = [ ]; + return _luci2.rpc.flush(); + }).then(function(subdevices) { + for (var i = 0; i < subdevices.length; i++) + $.extend(devs[subdevices[i]['device']], subdevices[i]); - for (var i = 0; i < ifaces.length; i++) - { - var iface = ifaces[i]; + _luci2.rpc.batch(); - if (iface.device) - requests.push(['iwinfo', 'info', { 'device': iface.device.name }, iface.device]); + for (var dev in devs) + _luci2.wireless.getDeviceStatus(dev); - if (iface.subdevices) - for (var j = 0; j < iface.subdevices.length; j++) - requests.push(['iwinfo', 'info', { 'device': iface.subdevices[j].name }, iface.subdevices[j]]); - } + 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 _luci2.rpc.call(requests, function(responses) { - for (var key in responses) - if (responses[key][0] == 0 && responses[key][1] && responses[key][2]) - if (!$.isEmptyObject(responses[key][1])) - responses[key][2].wireless = responses[key][1]; - }); - }).then(function() { - ifaces.sort(function(a, b) { + nets.sort(function(a, b) { if (a['interface'] < b['interface']) return -1; else if (a['interface'] > b['interface']) @@ -783,119 +848,247 @@ function LuCI2() else return 0; }); - return retcb(cb, ifaces); + + return nets; }); }, findWanInterfaces: function(cb) { - return _luci2.rpc.list().then(function(data) { - var requests = { }; - for (var i = 0; i < data.length; i++) - { - if (data[i].indexOf('network.interface.') == 0) - { - var ifname = data[i].substring(18); - requests[ifname] = ['network.interface', 'status', { 'interface': ifname }]; - } - } - return _luci2.rpc.call(requests); - }).then(function(responses) { - var rv = [ ]; - for (var ifname in responses) - { - var response = responses[ifname]; + return this.listNetworkNames().then(function(names) { + _luci2.rpc.batch(); - if (response[0] != 0 || !response[1] || !response[1].route) + for (var i = 0; i < names.length; i++) + _luci2.network.getInterfaceStatus(names[i]); + + return _luci2.rpc.flush(); + }).then(function(interfaces) { + var rv = [ undefined, undefined ]; + + for (var i = 0; i < interfaces.length; i++) + { + if (!interfaces[i].route) continue; - for (var rn = 0, rt = response[1].route[rn]; - rn < response[1].route.length; - rn++, rt = response[1].route[rn]) + for (var j = 0; j < interfaces[i].route.length; j++) { + var rt = interfaces[i].route[j]; + if (typeof(rt.table) != 'undefined') continue; if (rt.target == '0.0.0.0' && rt.mask == 0) - rv[0] = response[1]; + rv[0] = interfaces[i]; else if (rt.target == '::' && rt.mask == 0) - rv[1] = response[1]; + rv[1] = interfaces[i]; } } - return retcb(cb, rv); - }); - }, - - getDHCPLeases: function(cb) - { - return rcall('luci2.network', 'dhcp_leases', undefined, 'leases', [ ], cb); - }, - - getDHCPv6Leases: function(cb) - { - return rcall('luci2.network', 'dhcp6_leases', undefined, 'leases', [ ], cb); - }, - - getRoutes: function(cb) - { - return rcall('luci2.network', 'routes', undefined, 'routes', [ ], cb); - }, - - getIPv6Routes: function(cb) - { - return rcall('luci2.network', 'routes6', undefined, 'routes', [ ], cb); - }, - - getARPTable: function(cb) - { - return rcall('luci2.network', 'arp_table', undefined, 'entries', [ ], cb); - }, - - getInterfaceStatus: function(iface, cb) - { - return rcall('network.interface', 'status', { 'interface': iface }, undefined, { }, cb, function(rv) { - rv['interface'] = iface; - rv['l2_device'] = rv['device']; return rv; }); }, - getDeviceStatus: function(dev, cb) - { - return rcall('network.device', 'status', { name: dev }, undefined, { }, cb, function(rv) { - if (typeof(dev) == 'string') - rv.device = dev; - return rv; - }); - }, - - getConntrackCount: function(cb) - { - return rcall('luci2.network', 'conntrack_count', undefined, undefined, { - count: 0, - limit: 0 - }, cb); - } + getDHCPLeases: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'dhcp_leases', + expect: { leases: [ ] } + }), + + getDHCPv6Leases: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'dhcp6_leases', + expect: { leases: [ ] } + }), + + getRoutes: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'routes', + expect: { routes: [ ] } + }), + + getIPv6Routes: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'routes', + expect: { routes: [ ] } + }), + + getARPTable: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'arp_table', + expect: { entries: [ ] } + }), + + 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; + } + }), + + getDeviceStatus: _luci2.rpc.declare({ + object: 'network.device', + method: 'status', + params: [ 'name' ], + expect: { '': { } }, + filter: function(data, params) { + data['device'] = params['name']; + return data; + } + }), + + getConntrackCount: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'conntrack_count', + expect: { '': { count: 0, limit: 0 } } + }), + + listSwitchNames: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'switch_list', + expect: { switches: [ ] } + }), + + 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']; + + delete data.vlan; + delete data.port; + + return data; + } + }), + + getSwitchStatus: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'switch_status', + params: [ 'switch' ], + expect: { ports: [ ] } + }), + + + runPing: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'ping', + params: [ 'data' ], + expect: { '': { code: -1 } } + }), + + runPing6: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'ping6', + params: [ 'data' ], + expect: { '': { code: -1 } } + }), + + runTraceroute: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'traceroute', + params: [ 'data' ], + expect: { '': { code: -1 } } + }), + + runTraceroute6: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'traceroute6', + params: [ 'data' ], + expect: { '': { code: -1 } } + }), + + runNslookup: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'nslookup', + params: [ 'data' ], + expect: { '': { code: -1 } } + }), + + + setUp: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'ifup', + params: [ 'data' ], + expect: { '': { code: -1 } } + }), + + setDown: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'ifdown', + params: [ 'data' ], + expect: { '': { code: -1 } } + }) }; this.wireless = { - getDevices: function(cb) { - return rcall('iwinfo', 'devices', undefined, 'devices', [ ], cb, function(rv) { - rv.sort(); - return rv; - }); - }, + listDeviceNames: _luci2.rpc.declare({ + object: 'iwinfo', + method: 'devices', + expect: { 'devices': [ ] }, + filter: function(data) { + data.sort(); + return data; + } + }), + + getDeviceStatus: _luci2.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: _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']; - getInfo: function(dev, cb) { - var parse_info = function(device, info, rv) - { - if (!rv[info.phy]) - rv[info.phy] = { - networks: [ ] - }; + data.sort(function(a, b) { + if (a.bssid < b.bssid) + return -1; + else if (a.bssid > b.bssid) + return 1; + else + return 0; + }); + + return data; + } + }), - var phy = rv[info.phy]; + getWirelessStatus: function() { + return this.listDeviceNames().then(function(names) { + _luci2.rpc.batch(); + + for (var i = 0; i < names.length; i++) + _luci2.wireless.getDeviceStatus(names[i]); + + return _luci2.rpc.flush(); + }).then(function(networks) { + var rv = { }; var phy_attrs = [ 'country', 'channel', 'frequency', 'frequency_offset', @@ -907,125 +1100,46 @@ function LuCI2() 'signal', 'noise', 'bitrate', 'encryption' ]; - for (var i = 0; i < phy_attrs.length; i++) - phy[phy_attrs[i]] = info[phy_attrs[i]]; - - var net = { - device: device - }; - - for (var i = 0; i < net_attrs.length; i++) - net[net_attrs[i]] = info[net_attrs[i]]; - - phy.networks.push(net); - - return phy; - }; - - if (!dev) - { - return _luci2.wireless.getDevices().then(function(devices) { - var requests = [ ]; - - for (var i = 0; i < devices.length; i++) - { - if (devices[i].indexOf('.sta') >= 0) - continue; - - requests[devices[i]] = [ 'iwinfo', 'info', { device: devices[i] } ]; - } - - return _luci2.rpc.call(requests); - }).then(function(responses) { - var rv = { }; - - for (var device in responses) - { - var response = responses[device]; + for (var i = 0; i < networks.length; i++) + { + var phy = rv[networks[i].phy] || ( + rv[networks[i].phy] = { networks: [ ] } + ); - if (response[0] != 0 || !response[1]) - continue; + var net = { + device: networks[i].device + }; - parse_info(device, response[1], rv); - } + for (var j = 0; j < phy_attrs.length; j++) + phy[phy_attrs[j]] = networks[i][phy_attrs[j]]; - return retcb(cb, rv); - }); - } + for (var j = 0; j < net_attrs.length; j++) + net[net_attrs[j]] = networks[i][net_attrs[j]]; - return _luci2.rpc.call('iwinfo', 'info', { device: dev }).then(function(response) { - if (response[0] != 0 || !response[1]) - return retcb(cb, { }); + phy.networks.push(net); + } - return retcb(cb, parse_info(dev, response[1], { })); + return rv; }); }, - getAssocList: function(dev, cb) + getAssocLists: function() { - if (!dev) - { - return _luci2.wireless.getDevices().then(function(devices) { - var requests = { }; - - for (var i = 0; i < devices.length; i++) - { - if (devices[i].indexOf('.sta') >= 0) - continue; - - requests[devices[i]] = [ 'iwinfo', 'assoclist', { device: devices[i] } ]; - } - - return _luci2.rpc.call(requests); - }).then(function(responses) { - var rv = [ ]; + return this.listDeviceNames().then(function(names) { + _luci2.rpc.batch(); - for (var device in responses) - { - var response = responses[device]; + for (var i = 0; i < names.length; i++) + _luci2.wireless.getAssocList(names[i]); - if (response[0] != 0 || !response[1] || !response[1].results) - continue; - - for (var i = 0; i < response[1].results.length; i++) - { - var station = response[1].results[i]; - - station.device = device; - rv.push(station); - } - } - - rv.sort(function(a, b) { - return (a.device == b.device) - ? (a.bssid < b.bssid) - : (a.device > b.device) - ; - }); - - return retcb(cb, rv); - }); - } - - return _luci2.rpc.call('iwinfo', 'assoclist', { device: dev }).then(function(response) { + return _luci2.rpc.flush(); + }).then(function(assoclists) { var rv = [ ]; - if (response[0] != 0 || !response[1] || !response[1].results) - return retcb(cb, rv); - - for (var i = 0; i < response[1].results.length; i++) - { - var station = response[1].results[i]; - - station.device = dev; - rv.push(station); - } + for (var i = 0; i < assoclists.length; i++) + for (var j = 0; j < assoclists[i].length; j++) + rv.push(assoclists[i][j]); - rv.sort(function(a, b) { - return (a.bssid < b.bssid); - }); - - return retcb(cb, rv); + return rv; }); }, @@ -1074,94 +1188,160 @@ function LuCI2() } }; - this.system = { - getInfo: function(cb) + this.firewall = { + getZoneColor: function(zone) { - return _luci2.rpc.call({ - info: [ 'system', 'info', { } ], - board: [ 'system', 'board', { } ], - disk: [ 'luci2.system', 'diskfree', { } ] - }).then(function(responses) { - var rv = { }; + if ($.isPlainObject(zone)) + zone = zone.name; - if (responses.info[0] == 0) - $.extend(rv, responses.info[1]); + if (zone == 'lan') + return '#90f090'; + else if (zone == 'wan') + return '#f09090'; - if (responses.board[0] == 0) - $.extend(rv, responses.board[1]); + for (var i = 0, hash = 0; + i < zone.length; + hash = zone.charCodeAt(i++) + ((hash << 5) - hash)); - if (responses.disk[0] == 0) - $.extend(rv, responses.disk[1]); + for (var i = 0, color = '#'; + i < 3; + color += ('00' + ((hash >> i++ * 8) & 0xFF).tozoneing(16)).slice(-2)); - return retcb(cb, rv); - }); + return color; }, - getProcessList: function(cb) + findZoneByNetwork: function(network) { - return rcall('luci2.system', 'process_list', undefined, 'processes', [ ], cb, function(rv) { - rv.sort(function(a, b) { return a.pid - b.pid }); - return rv; + var self = this; + var zone = undefined; + + return _luci2.uci.foreach('firewall', 'zone', function(z) { + if (!z.name || !z.network) + return; + + if (!$.isArray(z.network)) + z.network = z.network.split(/\s+/); + + for (var i = 0; i < z.network.length; i++) + { + if (z.network[i] == network) + { + zone = z; + break; + } + } + }).then(function() { + if (zone) + zone.color = self.getZoneColor(zone); + + return zone; }); - }, + } + }; - getSystemLog: function(cb) - { - return rcall('luci2.system', 'syslog', undefined, 'log', '', cb); - }, + this.system = { + getSystemInfo: _luci2.rpc.declare({ + object: 'system', + method: 'info', + expect: { '': { } } + }), + + getBoardInfo: _luci2.rpc.declare({ + object: 'system', + method: 'board', + expect: { '': { } } + }), + + getDiskInfo: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'diskfree', + expect: { '': { } } + }), - getKernelLog: function(cb) + getInfo: function(cb) { - return rcall('luci2.system', 'dmesg', undefined, 'log', '', cb); - }, + _luci2.rpc.batch(); - getZoneInfo: function(cb) - { - return $.getJSON(_luci2.globals.resource + '/zoneinfo.json', cb); - }, + this.getSystemInfo(); + this.getBoardInfo(); + this.getDiskInfo(); - canSendSignal: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'process_signal', cb); - }, + return _luci2.rpc.flush().then(function(info) { + var rv = { }; - sendSignal: function(pid, sig, cb) - { - return _luci2.rpc.call('luci2.system', 'process_signal', { pid: pid, signal: sig }).then(function(response) { - return retcb(cb, response[0] == 0); + $.extend(rv, info[0]); + $.extend(rv, info[1]); + $.extend(rv, info[2]); + + return rv; }); }, - initList: function(cb) + 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 rcall('luci2.system', 'init_list', undefined, 'initscripts', [ ], cb, function(rv) { - rv.sort(function(a, b) { return (a.start || 0) - (b.start || 0) }); - return rv; - }); + 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({ + object: 'luci2.system', + method: 'init_list', + expect: { initscripts: [ ] }, + filter: function(data) { + data.sort(function(a, b) { return (a.start || 0) - (b.start || 0) }); + return data; + } + }), + initEnabled: function(init, cb) { - return this.initList(function(list) { + return this.initList().then(function(list) { for (var i = 0; i < list.length; i++) if (list[i].name == init) - return retcb(cb, !!list[i].enabled); + return !!list[i].enabled; - return retcb(cb, false); - }); - }, - - initRun: function(init, action, cb) - { - return _luci2.rpc.call('luci2.system', 'init_action', { name: init, action: action }).then(function(response) { - return retcb(cb, response[0] == 0); + return false; }); }, - canInitRun: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'init_action', cb); - }, + initRun: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'init_action', + params: [ 'name', 'action' ], + filter: function(data) { + return (data == 0); + } + }), initStart: function(init, cb) { return _luci2.system.initRun(init, 'start', cb) }, initStop: function(init, cb) { return _luci2.system.initRun(init, 'stop', cb) }, @@ -1171,257 +1351,312 @@ function LuCI2() initDisable: function(init, cb) { return _luci2.system.initRun(init, 'disable', cb) }, - getRcLocal: function(cb) - { - return rcall('luci2.system', 'rclocal_get', undefined, 'data', '', cb); - }, - - setRcLocal: function(data, cb) - { - return rcall('luci2.system', 'rclocal_set', { data: data }, undefined, undefined, cb); - }, - - canSetRcLocal: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'rclocal_set', cb); - }, - - - getCrontab: function(cb) - { - return rcall('luci2.system', 'crontab_get', undefined, 'data', '', 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' ] + }), - setCrontab: function(data, cb) - { - return rcall('luci2.system', 'crontab_set', { data: data }, undefined, undefined, cb); - }, - canSetCrontab: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'crontab_set', cb); - }, + 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: function(cb) - { - return rcall('luci2.system', 'sshkeys_get', undefined, 'keys', [ ], cb); - }, - setSSHKeys: function(keys, cb) - { - return rcall('luci2.system', 'sshkeys_set', { keys: keys }, undefined, undefined, cb); - }, - - canSetSSHKeys: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'sshkeys_set', cb); - }, - - - setPassword: function(user, pass, cb) - { - return rcall('luci2.system', 'password_set', { user: user, password: pass }, undefined, undefined, cb); - }, - - canSetPassword: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'password_set', cb); - }, - - - listLEDs: function(cb) - { - return rcall('luci2.system', 'led_list', undefined, 'leds', [ ], cb); - }, - - listUSBDevices: function(cb) - { - return rcall('luci2.system', 'usb_list', undefined, 'devices', [ ], cb); - }, - - - testUpgrade: function(cb) - { - return rcall('luci2.system', 'upgrade_test', undefined, undefined, { }, cb); - }, + getSSHKeys: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'sshkeys_get', + expect: { keys: [ ] } + }), - startUpgrade: function(keep, cb) - { - return rcall('luci2.system', 'upgrade_start', { keep: !!keep }, undefined, undefined, cb); - }, + setSSHKeys: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'sshkeys_set', + params: [ 'keys' ] + }), - cleanUpgrade: function(cb) - { - return rcall('luci2.system', 'upgrade_clean', undefined, undefined, undefined, cb); - }, - canUpgrade: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'upgrade_start', cb); - }, + setPassword: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'password_set', + params: [ 'user', 'password' ] + }), - restoreBackup: function(cb) - { - return rcall('luci2.system', 'backup_restore', undefined, undefined, undefined, cb); - }, + listLEDs: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'led_list', + expect: { leds: [ ] } + }), - cleanBackup: function(cb) - { - return rcall('luci2.system', 'backup_clean', undefined, undefined, undefined, cb); - }, + listUSBDevices: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'usb_list', + expect: { devices: [ ] } + }), - canRestoreBackup: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'backup_restore', cb); - }, + testUpgrade: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'upgrade_test', + expect: { '': { } } + }), - getBackupConfig: function(cb) - { - return rcall('luci2.system', 'backup_config_get', undefined, 'config', '', cb); - }, + startUpgrade: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'upgrade_start', + params: [ 'keep' ] + }), - setBackupConfig: function(data, cb) - { - return rcall('luci2.system', 'backup_config_set', { data: data }, undefined, undefined, cb); - }, + cleanUpgrade: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'upgrade_clean' + }), - canSetBackupConfig: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'backup_config_set', cb); - }, + restoreBackup: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'backup_restore' + }), - listBackup: function(cb) - { - return rcall('luci2.system', 'backup_list', undefined, 'files', [ ], cb); - }, + cleanBackup: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'backup_clean' + }), - performReboot: function(cb) - { - return rcall('luci2.system', 'reboot', undefined, undefined, undefined, cb); - }, + getBackupConfig: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'backup_config_get', + expect: { config: '' } + }), - canPerformReboot: function(cb) - { - return _luci2.rpc.access('ubus', 'luci2.system', 'reboot', cb); - } + 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' + }), + + + performReboot: _luci2.rpc.declare({ + object: 'luci2.system', + method: 'reboot' + }) }; this.opkg = { - updateLists: function(cb) - { - return rcall('luci2.opkg', 'update', undefined, undefined, { }, cb); - }, - - _fetchPackages: function(action, offset, limit, pattern, cb) + 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 = [ ]; - var reqlimit = Math.min(limit, 100); - if (reqlimit <= 0) - reqlimit = 100; + return action(offset, limit, pattern).then(function(list) { + if (!list.total || !list.packages) + return { length: 0, total: 0 }; - return _luci2.rpc.call('luci2.opkg', action, { offset: offset, limit: reqlimit, pattern: pattern }).then(function(response) { - if (response[0] != 0 || !response[1] || !response[1].total) - return retcb(cb, { length: 0, total: 0 }); - - packages.push.apply(packages, response[1].packages); - packages.total = response[1].total; + packages.push.apply(packages, list.packages); + packages.total = list.total; if (limit <= 0) - limit = response[1].total; + limit = list.total; if (packages.length >= limit) - return retcb(cb, packages); + return packages; + + _luci2.rpc.batch(); - var requests = [ ]; for (var i = offset + packages.length; i < limit; i += 100) - requests.push(['luci2.opkg', action, { offset: i, limit: (Math.min(i + 100, limit) % 100) || 100, pattern: pattern }]); + action(i, (Math.min(i + 100, limit) % 100) || 100, pattern); - return _luci2.rpc.call(requests); - }).then(function(responses) { - for (var key in responses) + return _luci2.rpc.flush(); + }).then(function(lists) { + for (var i = 0; i < lists.length; i++) { - var response = responses[key]; - - if (response[0] != 0 || !response[1] || !response[1].packages) + if (!lists[i].total || !lists[i].packages) continue; - packages.push.apply(packages, response[1].packages); - packages.total = response[1].total; + packages.push.apply(packages, lists[i].packages); + packages.total = lists[i].total; } - return retcb(cb, packages); + return packages; }); }, - listPackages: function(offset, limit, pattern, cb) + listPackages: function(offset, limit, pattern) { - return _luci2.opkg._fetchPackages('list', offset, limit, pattern, cb); + return _luci2.opkg._fetchPackages(_luci2.opkg._allPackages, offset, limit, pattern); }, - installedPackages: function(offset, limit, pattern, cb) + installedPackages: function(offset, limit, pattern) { - return _luci2.opkg._fetchPackages('list_installed', offset, limit, pattern, cb); + return _luci2.opkg._fetchPackages(_luci2.opkg._installedPackages, offset, limit, pattern); }, - findPackages: function(offset, limit, pattern, cb) + findPackages: function(offset, limit, pattern) { - return _luci2.opkg._fetchPackages('find', offset, limit, pattern, cb); + return _luci2.opkg._fetchPackages(_luci2.opkg._findPackages, offset, limit, pattern); }, - installPackage: function(name, cb) - { - return rcall('luci2.opkg', 'install', { 'package': name }, undefined, { }, cb); - }, + installPackage: _luci2.rpc.declare({ + object: 'luci2.opkg', + method: 'install', + params: [ 'package' ], + expect: { '': { } } + }), - removePackage: function(name, cb) - { - return rcall('luci2.opkg', 'remove', { 'package': name }, undefined, { }, cb); - }, + removePackage: _luci2.rpc.declare({ + object: 'luci2.opkg', + method: 'remove', + params: [ 'package' ], + expect: { '': { } } + }), - getConfig: function(cb) - { - return rcall('luci2.opkg', 'config_get', undefined, 'config', '', cb); - }, + getConfig: _luci2.rpc.declare({ + object: 'luci2.opkg', + method: 'config_get', + expect: { config: '' } + }), - setConfig: function(data, cb) - { - return rcall('luci2.opkg', 'config_set', { data: data }, undefined, undefined, cb); - }, + setConfig: _luci2.rpc.declare({ + object: 'luci2.opkg', + method: 'config_set', + params: [ 'data' ] + }) + }; - canInstallPackage: function(cb) + this.session = { + + login: _luci2.rpc.declare({ + object: 'session', + method: 'login', + params: [ 'username', 'password' ], + expect: { '': { } } + }), + + access: _luci2.rpc.declare({ + object: 'session', + method: 'access', + params: [ 'scope', 'object', 'function' ], + expect: { access: false } + }), + + isAlive: function() { - return _luci2.rpc.access('ubus', 'luci2.opkg', 'install', cb); + return _luci2.session.access('ubus', 'session', 'access'); }, - canRemovePackage: function(cb) + startHeartbeat: function() { - return _luci2.rpc.access('ubus', 'luci2.opkg', 'remove', cb); + this._hearbeatInterval = window.setInterval(function() { + _luci2.session.isAlive().then(function(alive) { + if (!alive) + { + _luci2.session.stopHeartbeat(); + _luci2.ui.login(true); + } + + }); + }, _luci2.globals.timeout * 2); }, - canSetConfig: function(cb) + stopHeartbeat: function() { - return _luci2.rpc.access('ubus', 'luci2.opkg', 'config_set', cb); + if (typeof(this._hearbeatInterval) != 'undefined') + { + window.clearInterval(this._hearbeatInterval); + delete this._hearbeatInterval; + } } }; this.ui = { + saveScrollTop: function() + { + this._scroll_top = $(document).scrollTop(); + }, + + restoreScrollTop: function() + { + if (typeof(this._scroll_top) == 'undefined') + return; + + $(document).scrollTop(this._scroll_top); + + delete this._scroll_top; + }, + loading: function(enable) { var win = $(window); var body = $('body'); - var div = _luci2._modal || ( - _luci2._modal = $('
') + + var state = _luci2.ui._loading || (_luci2.ui._loading = { + modal: $('
') .addClass('cbi-modal-loader') .append($('
').text(_luci2.tr('Loading data...'))) .appendTo(body) - ); + }); if (enable) { @@ -1429,13 +1664,13 @@ function LuCI2() body.css('padding', 0); body.css('width', win.width()); body.css('height', win.height()); - div.css('width', win.width()); - div.css('height', win.height()); - div.show(); + state.modal.css('width', win.width()); + state.modal.css('height', win.height()); + state.modal.show(); } else { - div.hide(); + state.modal.hide(); body.css('overflow', ''); body.css('padding', ''); body.css('width', ''); @@ -1447,8 +1682,9 @@ function LuCI2() { var win = $(window); var body = $('body'); - var div = _luci2._dialog || ( - _luci2._dialog = $('
') + + var state = _luci2.ui._dialog || (_luci2.ui._dialog = { + dialog: $('
') .addClass('cbi-modal-dialog') .append($('
') .append($('
') @@ -1470,7 +1706,7 @@ function LuCI2() $(this).parent().parent().parent().hide(); })))) .appendTo(body) - ); + }); if (typeof(options) != 'object') options = { }; @@ -1483,13 +1719,13 @@ function LuCI2() .css('width', '') .css('height', ''); - _luci2._dialog.hide(); + state.dialog.hide(); return; } - var cnt = div.children().children('div.cbi-modal-dialog-body'); - var ftr = div.children().children('div.cbi-modal-dialog-footer'); + var cnt = state.dialog.children().children('div.cbi-modal-dialog-body'); + var ftr = state.dialog.children().children('div.cbi-modal-dialog-footer'); ftr.empty(); @@ -1520,29 +1756,29 @@ function LuCI2() .attr('disabled', true)); } - div.find('div.cbi-modal-dialog-header').text(title); - div.show(); + state.dialog.find('div.cbi-modal-dialog-header').text(title); + state.dialog.show(); cnt .css('max-height', Math.floor(win.height() * 0.70) + 'px') .empty() .append(content); - div.children() - .css('margin-top', -Math.floor(div.children().height() / 2) + 'px'); + state.dialog.children() + .css('margin-top', -Math.floor(state.dialog.children().height() / 2) + 'px'); body.css('overflow', 'hidden'); body.css('padding', 0); body.css('width', win.width()); body.css('height', win.height()); - div.css('width', win.width()); - div.css('height', win.height()); + state.dialog.css('width', win.width()); + state.dialog.css('height', win.height()); }, upload: function(title, content, options) { - var form = _luci2._upload || ( - _luci2._upload = $('
') + var state = _luci2.ui._upload || (_luci2.ui._upload = { + form: $('') .attr('method', 'post') .attr('action', '/cgi-bin/luci-upload') .attr('enctype', 'multipart/form-data') @@ -1550,12 +1786,10 @@ function LuCI2() .append($('

')) .append($('') .attr('type', 'hidden') - .attr('name', 'sessionid') - .attr('value', _luci2.globals.sid)) + .attr('name', 'sessionid')) .append($('') .attr('type', 'hidden') - .attr('name', 'filename') - .attr('value', options.filename)) + .attr('name', 'filename')) .append($('') .attr('type', 'file') .attr('name', 'filedata') @@ -1570,11 +1804,9 @@ function LuCI2() .attr('name', 'cbi-fileupload-frame') .css('width', '1px') .css('height', '1px') - .css('visibility', 'hidden')) - ); + .css('visibility', 'hidden')), - var finish = _luci2._upload_finish_cb || ( - _luci2._upload_finish_cb = function(ev) { + finish_cb: function(ev) { $(this).off('load'); var body = (this.contentDocument || this.contentWindow.document).body; @@ -1599,41 +1831,43 @@ function LuCI2() $('

').text(_luci2.tr('In case of network problems try uploading the file again.')) ], { style: 'close' }); } - else if (typeof(ev.data.cb) == 'function') + else if (typeof(state.success_cb) == 'function') { - ev.data.cb(json); + state.success_cb(json); } - } - ); + }, - var confirm = _luci2._upload_confirm_cb || ( - _luci2._upload_confirm_cb = function() { - var d = _luci2._upload; - var f = d.find('.cbi-input-file'); - var b = d.find('.progressbar'); - var p = d.find('p'); + confirm_cb: function() { + var f = state.form.find('.cbi-input-file'); + var b = state.form.find('.progressbar'); + var p = state.form.find('p'); if (!f.val()) return; - d.find('iframe').on('load', { cb: options.success }, finish); - d.submit(); + state.form.find('iframe').on('load', state.finish_cb); + state.form.submit(); f.hide(); b.show(); p.text(_luci2.tr('File upload in progress …')); - _luci2._dialog.find('button').prop('disabled', true); + state.form.parent().parent().find('button').prop('disabled', true); } - ); + }); + + state.form.find('.progressbar').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('[name=sessionid]').val(_luci2.globals.sid); + state.form.find('[name=filename]').val(options.filename); - _luci2._upload.find('.progressbar').hide(); - _luci2._upload.find('.cbi-input-file').val('').show(); - _luci2._upload.find('p').text(content || _luci2.tr('Select the file to upload and press "%s" to proceed.').format(_luci2.tr('Ok'))); + state.success_cb = options.success; - _luci2.ui.dialog(title || _luci2.tr('File upload'), _luci2._upload, { + _luci2.ui.dialog(title || _luci2.tr('File upload'), state.form, { style: 'confirm', - confirm: confirm + confirm: state.confirm_cb }); }, @@ -1675,8 +1909,8 @@ function LuCI2() //}).then(function() { images.on('load', function() { var url = this.getAttribute('url'); - _luci2.rpc.access('ubus', 'session', 'access').then(function(response) { - if (response[0] == 0) + _luci2.session.isAlive().then(function(access) { + if (access) { window.clearTimeout(timeout); window.clearInterval(interval); @@ -1711,31 +1945,10 @@ function LuCI2() login: function(invalid) { - if (!_luci2._login_deferred || _luci2._login_deferred.state() != 'pending') - _luci2._login_deferred = $.Deferred(); - - /* try to find sid from hash */ - var sid = _luci2.getHash('id'); - if (sid && sid.match(/^[a-f0-9]{32}$/)) - { - _luci2.globals.sid = sid; - _luci2.rpc.access('ubus', 'session', 'access').then(function(response) { - if (response[0] == 0) - { - _luci2._login_deferred.resolve(); - } - else - { - _luci2.setHash('id', undefined); - _luci2.ui.login(); - } - }); - - return _luci2._login_deferred; - } - - var form = _luci2._login || ( - _luci2._login = $('

') + var state = _luci2.ui._login || (_luci2.ui._login = { + form: $('') + .attr('target', '') + .attr('method', 'post') .append($('

') .addClass('alert-message') .text(_luci2.tr('Wrong username or password given!'))) @@ -1747,7 +1960,11 @@ function LuCI2() .attr('type', 'text') .attr('name', 'username') .attr('value', 'root') - .addClass('cbi-input-text')))) + .addClass('cbi-input-text') + .keypress(function(ev) { + if (ev.which == 10 || ev.which == 13) + state.confirm_cb(); + })))) .append($('

') .append($('

') - .text(_luci2.tr('Enter your username and password above, then click "%s" to proceed.').format(_luci2.tr('Ok')))) - ); + .text(_luci2.tr('Enter your username and password above, then click "%s" to proceed.').format(_luci2.tr('Ok')))), - var response = _luci2._login_response_cb || ( - _luci2._login_response_cb = function(data) { - if (typeof(data) == 'object' && typeof(data.sessionid) == 'string') + response_cb: function(response) { + if (!response.ubus_rpc_session) { - _luci2.globals.sid = data.sessionid; - _luci2.setHash('id', _luci2.globals.sid); - - _luci2.ui.dialog(false); - _luci2._login_deferred.resolve(); + _luci2.ui.login(true); } else { - _luci2.ui.login(true); + _luci2.globals.sid = response.ubus_rpc_session; + _luci2.setHash('id', _luci2.globals.sid); + _luci2.session.startHeartbeat(); + _luci2.ui.dialog(false); + state.deferred.resolve(); } - } - ); + }, - var confirm = _luci2._login_confirm_cb || ( - _luci2._login_confirm_cb = function() { - var d = _luci2._login; - var u = d.find('[name=username]').val(); - var p = d.find('[name=password]').val(); + confirm_cb: function() { + var u = state.form.find('[name=username]').val(); + var p = state.form.find('[name=password]').val(); if (!u) return; @@ -1798,41 +2014,150 @@ function LuCI2() ], { style: 'wait' } ); - $.ajax('/cgi-bin/luci-login', { - type: 'POST', - cache: false, - data: { username: u, password: p }, - dataType: 'json', - success: response, - error: response - }); + _luci2.globals.sid = '00000000000000000000000000000000'; + _luci2.session.login(u, p).then(state.response_cb); } - ); + }); + + if (!state.deferred || state.deferred.state() != 'pending') + state.deferred = $.Deferred(); + + /* try to find sid from hash */ + var sid = _luci2.getHash('id'); + if (sid && sid.match(/^[a-f0-9]{32}$/)) + { + _luci2.globals.sid = sid; + _luci2.session.isAlive().then(function(access) { + if (access) + { + _luci2.session.startHeartbeat(); + state.deferred.resolve(); + } + else + { + _luci2.setHash('id', undefined); + _luci2.ui.login(); + } + }); + + return state.deferred; + } if (invalid) - form.find('.alert-message').show(); + state.form.find('.alert-message').show(); else - form.find('.alert-message').hide(); + state.form.find('.alert-message').hide(); - _luci2.ui.dialog(_luci2.tr('Authorization Required'), form, { + _luci2.ui.dialog(_luci2.tr('Authorization Required'), state.form, { style: 'confirm', - confirm: confirm + confirm: state.confirm_cb }); - return _luci2._login_deferred; + state.form.find('[name=password]').focus(); + + return state.deferred; + }, + + cryptPassword: _luci2.rpc.declare({ + object: 'luci2.ui', + method: 'crypt', + params: [ 'data' ], + expect: { crypt: '' } + }), + + + _acl_merge_scope: function(acl_scope, scope) + { + if ($.isArray(scope)) + { + for (var i = 0; i < scope.length; i++) + acl_scope[scope[i]] = true; + } + else if ($.isPlainObject(scope)) + { + for (var object_name in scope) + { + if (!$.isArray(scope[object_name])) + continue; + + var acl_object = acl_scope[object_name] || (acl_scope[object_name] = { }); + + for (var i = 0; i < scope[object_name].length; i++) + acl_object[scope[object_name][i]] = true; + } + } + }, + + _acl_merge_permission: 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]); + } + } + }, + + _acl_merge_group: function(acl_group, group) + { + if ($.isPlainObject(group)) + { + if (!acl_group.description) + acl_group.description = group.description; + + if (group.read) + { + var acl_perm = acl_group.read || (acl_group.read = { }); + this._acl_merge_permission(acl_perm, group.read); + } + + if (group.write) + { + var acl_perm = acl_group.write || (acl_group.write = { }); + this._acl_merge_permission(acl_perm, group.write); + } + } }, - renderMainMenu: function() + _acl_merge_tree: function(acl_tree, tree) { - return rcall('luci2.ui', 'menu', undefined, 'menu', { }, function(entries) { + if ($.isPlainObject(tree)) + { + for (var group_name in tree) + { + var acl_group = acl_tree[group_name] || (acl_tree[group_name] = { }); + this._acl_merge_group(acl_group, tree[group_name]); + } + } + }, + + listAvailableACLs: _luci2.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; + } + }), + + renderMainMenu: _luci2.rpc.declare({ + object: 'luci2.ui', + method: 'menu', + expect: { menu: { } }, + filter: function(entries) { _luci2.globals.mainMenu = new _luci2.ui.menu(); _luci2.globals.mainMenu.entries(entries); $('#mainmenu') .empty() .append(_luci2.globals.mainMenu.render(0, 1)); - }); - }, + } + }), renderViewMenu: function() { @@ -1841,9 +2166,17 @@ function LuCI2() .append(_luci2.globals.mainMenu.render(2, 900)); }, - renderView: function(node) + renderView: function() { + var node = arguments[0]; var name = node.view.split(/\//).join('.'); + var args = [ ]; + + for (var i = 1; i < arguments.length; i++) + args.push(arguments[i]); + + if (_luci2.globals.currentView) + _luci2.globals.currentView.finish(); _luci2.ui.renderViewMenu(); @@ -1853,34 +2186,136 @@ function LuCI2() _luci2.setHash('view', node.view); if (_luci2._views[name] instanceof _luci2.ui.view) - return _luci2._views[name].render(); + { + _luci2.globals.currentView = _luci2._views[name]; + return _luci2._views[name].render.apply(_luci2._views[name], args); + } + + var url = _luci2.globals.resource + '/view/' + name + '.js'; - return $.ajax(_luci2.globals.resource + '/view/' + name + '.js', { + return $.ajax(url, { method: 'GET', cache: true, dataType: 'text' }).then(function(data) { try { - var viewConstructor = (new Function(['L', '$'], 'return ' + data))(_luci2, $); + var viewConstructorSource = ( + '(function(L, $) { ' + + 'return %s' + + '})(_luci2, $);\n\n' + + '//@ sourceURL=%s' + ).format(data, url); + + var viewConstructor = eval(viewConstructorSource); _luci2._views[name] = new viewConstructor({ name: name, acls: node.write || { } }); - return _luci2._views[name].render(); + _luci2.globals.currentView = _luci2._views[name]; + return _luci2._views[name].render.apply(_luci2._views[name], args); } - catch(e) { }; + catch(e) { + alert('Unable to instantiate view "%s": %s'.format(url, e)); + }; return $.Deferred().resolve(); }); }, + updateHostname: function() + { + return _luci2.system.getBoardInfo().then(function(info) { + if (info.hostname) + $('#hostname').text(info.hostname); + }); + }, + + updateChanges: function() + { + return _luci2.uci.changes().then(function(changes) { + var n = 0; + var html = ''; + + for (var config in changes) + { + var log = [ ]; + + for (var i = 0; i < changes[config].length; i++) + { + var c = changes[config][i]; + + switch (c[0]) + { + case 'order': + break; + + case 'remove': + if (c.length < 3) + log.push('uci delete %s.%s'.format(config, c[1])); + else + log.push('uci delete %s.%s.%s'.format(config, c[1], c[2])); + break; + + case 'rename': + if (c.length < 4) + log.push('uci rename %s.%s=%s'.format(config, c[1], c[2], c[3])); + else + log.push('uci rename %s.%s.%s=%s'.format(config, c[1], c[2], c[3], c[4])); + break; + + case 'add': + log.push('uci add %s %s (= %s)'.format(config, c[2], c[1])); + break; + + case 'list-add': + log.push('uci add_list %s.%s.%s=%s'.format(config, c[1], c[2], c[3], c[4])); + break; + + case 'list-del': + log.push('uci del_list %s.%s.%s=%s'.format(config, c[1], c[2], c[3], c[4])); + break; + + case 'set': + if (c.length < 4) + log.push('uci set %s.%s=%s'.format(config, c[1], c[2])); + else + log.push('uci set %s.%s.%s=%s'.format(config, c[1], c[2], c[3], c[4])); + break; + } + } + + html += '/etc/config/%s

%s
'.format(config, log.join('\n')); + n += changes[config].length; + } + + if (n > 0) + $('#changes') + .empty() + .show() + .append($('') + .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(); + })); + else + $('#changes') + .hide(); + }); + }, + init: function() { _luci2.ui.loading(true); $.when( + _luci2.ui.updateHostname(), + _luci2.ui.updateChanges(), _luci2.ui.renderMainMenu() ).then(function() { _luci2.ui.renderView(_luci2.globals.defaultNode).then(function() { @@ -1904,6 +2339,10 @@ function LuCI2() insertInto: function(id) { return $(id).empty().append(this.render()); + }, + + appendTo: function(id) { + return $(id).append(this.render()); } }); @@ -1956,9 +2395,50 @@ function LuCI2() container.append($('
').addClass('cbi-map-descr').append(this.description)); var self = this; + var args = [ ]; + + for (var i = 0; i < arguments.length; i++) + args.push(arguments[i]); + return this._fetch_template().then(function() { - return _luci2.deferrable(self.execute()); + return _luci2.deferrable(self.execute.apply(self, args)); }); + }, + + repeat: function(func, interval) + { + var self = this; + + if (!self._timeouts) + self._timeouts = [ ]; + + var index = self._timeouts.length; + + if (typeof(interval) != 'number') + interval = 5000; + + var setTimer, runTimer; + + setTimer = function() { + self._timeouts[index] = window.setTimeout(runTimer, interval); + }; + + runTimer = function() { + _luci2.deferrable(func.call(self)).then(setTimer); + }; + + runTimer(); + }, + + finish: function() + { + if ($.isArray(this._timeouts)) + { + for (var i = 0; i < this._timeouts.length; i++) + window.clearTimeout(this._timeouts[i]); + + delete this._timeouts; + } } }); @@ -2012,7 +2492,10 @@ function LuCI2() var child = this.firstChildView(nodes[i]); if (child) { - $.extend(node, child); + for (var key in child) + if (!node.hasOwnProperty(key) && child.hasOwnProperty(key)) + node[key] = child[key]; + return node; } } @@ -2111,11 +2594,11 @@ function LuCI2() row: function(values) { - if (isa(values, 'Array')) + if ($.isArray(values)) { this._rows.push(values); } - else if (isa(values, 'Object')) + else if ($.isPlainObject(values)) { var v = [ ]; for (var i = 0; i < this.options.columns.length; i++) @@ -2297,7 +2780,9 @@ function LuCI2() this.ui.devicebadge = AbstractWidget.extend({ render: function() { - var dev = this.options.l3_device || this.options.device || '?'; + var l2dev = this.options.l2_device || this.options.device; + var l3dev = this.options.l3_device; + var dev = l3dev || l2dev || '?'; var span = document.createElement('span'); span.className = 'ifacebadge'; @@ -2338,7 +2823,7 @@ function LuCI2() var type = 'ethernet'; var desc = _luci2.tr('Ethernet device'); - if (this.options.l3_device != this.options.device) + if (l3dev != l2dev) { type = 'tunnel'; desc = _luci2.tr('Tunnel interface'); @@ -2588,6 +3073,7 @@ function LuCI2() } } + validation.i18n('Must be a valid IPv6 address'); return false; }, @@ -2860,7 +3346,7 @@ function LuCI2() }; - var AbstractValue = AbstractWidget.extend({ + this.cbi.AbstractValue = AbstractWidget.extend({ init: function(name, options) { this.name = name; @@ -3126,11 +3612,11 @@ function LuCI2() { if (typeof(d[i]) == 'string') dep[d[i]] = true; - else if (d[i] instanceof AbstractValue) + else if (d[i] instanceof _luci2.cbi.AbstractValue) dep[d[i].name] = true; } } - else if (d instanceof AbstractValue) + else if (d instanceof _luci2.cbi.AbstractValue) { dep = { }; dep[d.name] = (typeof(v) == 'undefined') ? true : v; @@ -3238,7 +3724,7 @@ function LuCI2() } }); - this.cbi.CheckboxValue = AbstractValue.extend({ + this.cbi.CheckboxValue = this.cbi.AbstractValue.extend({ widget: function(sid) { var o = this.options; @@ -3291,19 +3777,17 @@ function LuCI2() if (chg) { - val = val ? this.options.enabled : this.options.disabled; - if (this.options.optional && val == this.options.initial) this.map.set(uci.config, uci.section, uci.option, undefined); else - this.map.set(uci.config, uci.section, uci.option, val); + this.map.set(uci.config, uci.section, uci.option, val ? this.options.enabled : this.options.disabled); } return chg; } }); - this.cbi.InputValue = AbstractValue.extend({ + this.cbi.InputValue = this.cbi.AbstractValue.extend({ widget: function(sid) { var i = $('') @@ -3316,7 +3800,7 @@ function LuCI2() } }); - this.cbi.PasswordValue = AbstractValue.extend({ + this.cbi.PasswordValue = this.cbi.AbstractValue.extend({ widget: function(sid) { var i = $('') @@ -3345,7 +3829,7 @@ function LuCI2() } }); - this.cbi.ListValue = AbstractValue.extend({ + this.cbi.ListValue = this.cbi.AbstractValue.extend({ widget: function(sid) { var s = $('') .attr('name', itype + id) .attr('type', itype) - .attr('value', iface.name) - .prop('checked', !!check[iface.name]) + .attr('value', iface['interface']) + .prop('checked', !!check[iface['interface']]) .addClass('cbi-input-' + itype)) .append(badge)) .appendTo(ul); @@ -3987,7 +4471,7 @@ function LuCI2() }); - var AbstractSection = AbstractWidget.extend({ + this.cbi.AbstractSection = AbstractWidget.extend({ id: function() { var s = [ arguments[0], this.map.uci_package, this.uci_type ]; @@ -4037,7 +4521,7 @@ function LuCI2() var w = widget ? new widget(name, options) : null; - if (!(w instanceof AbstractValue)) + if (!(w instanceof _luci2.cbi.AbstractValue)) throw 'Widget must be an instance of AbstractValue'; w.section = this; @@ -4133,7 +4617,7 @@ function LuCI2() } }); - this.cbi.TypedSection = AbstractSection.extend({ + this.cbi.TypedSection = this.cbi.AbstractSection.extend({ init: function(uci_type, options) { this.uci_type = uci_type; @@ -4188,10 +4672,14 @@ function LuCI2() if (addb.prop('disabled') || name === '') return; + _luci2.ui.saveScrollTop(); + self.active_panel = -1; self.map.save(); self.add(name); self.map.redraw(); + + _luci2.ui.restoreScrollTop(); }, _remove: function(ev) @@ -4199,10 +4687,17 @@ function LuCI2() var self = ev.data.self; var sid = ev.data.sid; + if (ev.data.index == (self.sections().length - 1)) + self.active_panel = -1; + + _luci2.ui.saveScrollTop(); + self.map.save(); self.remove(sid); self.map.redraw(); + _luci2.ui.restoreScrollTop(); + ev.stopPropagation(); }, @@ -4264,9 +4759,9 @@ function LuCI2() for (var i = 0; i < this.options.teasers.length; i++) { var f = this.options.teasers[i]; - if (f instanceof AbstractValue) + if (f instanceof _luci2.cbi.AbstractValue) tf.push(f); - else if (typeof(f) == 'string' && this.fields[f] instanceof AbstractValue) + else if (typeof(f) == 'string' && this.fields[f] instanceof _luci2.cbi.AbstractValue) tf.push(this.fields[f]); } } @@ -4345,7 +4840,7 @@ function LuCI2() return add; }, - _render_remove: function(sid) + _render_remove: function(sid, index) { var text = _luci2.tr('Remove'); var ttip = _luci2.tr('Remove this section'); @@ -4360,7 +4855,7 @@ function LuCI2() .addClass('cbi-button') .addClass('cbi-button-remove') .val(text).attr('title', ttip) - .click({ self: this, sid: sid }, this._remove); + .click({ self: this, sid: sid, index: index }, this._remove); }, _render_caption: function(sid) @@ -4444,7 +4939,7 @@ function LuCI2() $('
') .addClass('cbi-section-remove') .addClass('right') - .append(this._render_remove(sid)) + .append(this._render_remove(sid, panel_index)) .appendTo(head); var body = $('
') @@ -4787,7 +5282,8 @@ function LuCI2() deletes: { } }; - this.active_panel = 0; + if (typeof(this.active_panel) == 'undefined') + this.active_panel = 0; var packages = { }; @@ -4796,16 +5292,11 @@ function LuCI2() packages[this.uci_package] = true; - for (var p in packages) - packages[p] = ['uci', 'get', { config: p }]; - - var load_cb = this._load_cb || (this._load_cb = $.proxy(function(responses) { - for (var p in responses) + var load_cb = this._load_cb || (this._load_cb = $.proxy(function(packages) { + for (var i = 0; i < packages.length; i++) { - if (responses[p][0] != 0 || !responses[p][1] || !responses[p][1].values) - continue; - - this.uci.values[p] = responses[p][1].values; + this.uci.values[packages[i]['.package']] = packages[i]; + delete packages[i]['.package']; } var deferreds = [ _luci2.deferrable(this.options.prepare()) ]; @@ -4821,7 +5312,7 @@ function LuCI2() for (var j = 0; j < s.length; j++) { var rv = this.sections[i].fields[f].load(s[j]['.name']); - if (_luci2.deferred(rv)) + if (_luci2.isDeferred(rv)) deferreds.push(rv); } } @@ -4830,7 +5321,12 @@ function LuCI2() return $.when.apply($, deferreds); }, this)); - return _luci2.rpc.call(packages).then(load_cb); + _luci2.rpc.batch(); + + for (var pkg in packages) + _luci2.uci.get_all(pkg); + + return _luci2.rpc.flush().then(load_cb); }, render: function() @@ -4937,7 +5433,7 @@ function LuCI2() { var w = widget ? new widget(uci_type, options) : null; - if (!(w instanceof AbstractSection)) + if (!(w instanceof _luci2.cbi.AbstractSection)) throw 'Widget must be an instance of AbstractSection'; w.map = this; @@ -5154,7 +5650,7 @@ function LuCI2() for (var j = 0; j < s.length; j++) { var rv = this.sections[i].fields[f].save(s[j]['.name']); - if (_luci2.deferred(rv)) + if (_luci2.isDeferred(rv)) deferreds.push(rv); } } @@ -5169,7 +5665,7 @@ function LuCI2() return _luci2.deferrable(); var send_cb = this._send_cb || (this._send_cb = $.proxy(function() { - var requests = [ ]; + _luci2.rpc.batch(); if (this.uci.creates) for (var c in this.uci.creates) @@ -5183,41 +5679,37 @@ function LuCI2() for (var k in this.uci.creates[c][s]) { if (k == '.type') - r.type = this.uci.creates[i][k]; + r.type = this.uci.creates[c][s][k]; else if (k == '.create') - r.name = this.uci.creates[i][k]; + r.name = this.uci.creates[c][s][k]; else if (k.charAt(0) != '.') - r.values[k] = this.uci.creates[i][k]; + r.values[k] = this.uci.creates[c][s][k]; } - requests.push(['uci', 'add', r]); + + _luci2.uci.add(r.config, r.type, r.name, r.values); } if (this.uci.changes) for (var c in this.uci.changes) for (var s in this.uci.changes[c]) - requests.push(['uci', 'set', { - config: c, - section: s, - values: this.uci.changes[c][s] - }]); + _luci2.uci.set(c, s, this.uci.changes[c][s]); if (this.uci.deletes) for (var c in this.uci.deletes) for (var s in this.uci.deletes[c]) { var o = this.uci.deletes[c][s]; - requests.push(['uci', 'delete', { - config: c, - section: s, - options: (o === true) ? undefined : o - }]); + _luci2.uci['delete'](c, s, (o === true) ? undefined : o); } - return _luci2.rpc.call(requests); + return _luci2.rpc.flush().then(function() { + return _luci2.ui.updateChanges(); + }); }, this)); var self = this; + _luci2.ui.saveScrollTop(); _luci2.ui.loading(true); return this.save().then(send_cb).then(function() { @@ -5227,33 +5719,7 @@ function LuCI2() self = null; _luci2.ui.loading(false); - }); - }, - - dialog: function(id) - { - var d = $('
'); - var p = $('

'); - - $('') - .attr('src', _luci2.globals.resource + '/icons/loading.gif') - .css('vertical-align', 'middle') - .css('padding-right', '10px') - .appendTo(p); - - p.append(_luci2.tr('Loading data...')); - - p.appendTo(d); - d.appendTo(id); - - return d.dialog({ - modal: true, - draggable: false, - resizable: false, - height: 90, - open: function() { - $(this).parent().children('.ui-dialog-titlebar').hide(); - } + _luci2.ui.restoreScrollTop(); }); },