/* LuCI2 - OpenWrt Web Interface Copyright 2013 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. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 */ String.prototype.format = function() { var html_esc = [/&/g, '&', /"/g, '"', /'/g, ''', //g, '>']; var quot_esc = [/"/g, '"', /'/g, ''']; function esc(s, r) { for( var i = 0; i < r.length; i += 2 ) s = s.replace(r[i], r[i+1]); return s; } var str = this; var out = ''; var re = /^(([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X|q|h|j|t|m))/; var a = b = [], numSubstitutions = 0, numMatches = 0; while ((a = re.exec(str)) != null) { var m = a[1]; var leftpart = a[2], pPad = a[3], pJustify = a[4], pMinLength = a[5]; var pPrecision = a[6], pType = a[7]; numMatches++; if (pType == '%') { subst = '%'; } else { if (numSubstitutions < arguments.length) { var param = arguments[numSubstitutions++]; var pad = ''; if (pPad && pPad.substr(0,1) == "'") pad = leftpart.substr(1,1); else if (pPad) pad = pPad; var justifyRight = true; if (pJustify && pJustify === "-") justifyRight = false; var minLength = -1; if (pMinLength) minLength = parseInt(pMinLength); var precision = -1; if (pPrecision && pType == 'f') precision = parseInt(pPrecision.substring(1)); var subst = param; switch(pType) { case 'b': subst = (parseInt(param) || 0).toString(2); break; case 'c': subst = String.fromCharCode(parseInt(param) || 0); break; case 'd': subst = (parseInt(param) || 0); break; case 'u': subst = Math.abs(parseInt(param) || 0); break; case 'f': subst = (precision > -1) ? ((parseFloat(param) || 0.0)).toFixed(precision) : (parseFloat(param) || 0.0); break; case 'o': subst = (parseInt(param) || 0).toString(8); break; case 's': subst = param; break; case 'x': subst = ('' + (parseInt(param) || 0).toString(16)).toLowerCase(); break; case 'X': subst = ('' + (parseInt(param) || 0).toString(16)).toUpperCase(); break; case 'h': subst = esc(param, html_esc); break; case 'q': subst = esc(param, quot_esc); break; case 'j': subst = String.serialize(param); break; case 't': var td = 0; var th = 0; var tm = 0; var ts = (param || 0); if (ts > 60) { tm = Math.floor(ts / 60); ts = (ts % 60); } if (tm > 60) { th = Math.floor(tm / 60); tm = (tm % 60); } if (th > 24) { td = Math.floor(th / 24); th = (th % 24); } subst = (td > 0) ? '%dd %dh %dm %ds'.format(td, th, tm, ts) : '%dh %dm %ds'.format(th, tm, ts); break; case 'm': var mf = pMinLength ? parseInt(pMinLength) : 1000; var pr = pPrecision ? Math.floor(10*parseFloat('0'+pPrecision)) : 2; var i = 0; var val = parseFloat(param || 0); var units = [ '', 'K', 'M', 'G', 'T', 'P', 'E' ]; for (i = 0; (i < units.length) && (val > mf); i++) val /= mf; subst = val.toFixed(pr) + ' ' + units[i]; break; } subst = (typeof(subst) == 'undefined') ? '' : subst.toString(); if (minLength > 0 && pad.length > 0) for (var i = 0; i < (minLength - subst.length); i++) subst = justifyRight ? (pad + subst) : (subst + pad); } } out += leftpart + subst; str = str.substr(m.length); } return out + str; } function LuCI2() { var _luci2 = this; var Class = function() { }; Class.extend = function(properties) { Class.initializing = true; var prototype = new this(); var superprot = this.prototype; Class.initializing = false; $.extend(prototype, properties, { callSuper: function() { var args = [ ]; var meth = arguments[0]; if (typeof(superprot[meth]) != 'function') return undefined; for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); return superprot[meth].apply(this, args); } }); function _class() { this.options = arguments[0] || { }; if (!Class.initializing && typeof(this.init) == 'function') this.init.apply(this, arguments); } _class.prototype = prototype; _class.prototype.constructor = _class; _class.extend = arguments.callee; return _class; }; this.defaults = function(obj, def) { for (var key in def) if (typeof(obj[key]) == 'undefined') obj[key] = def[key]; return obj; }; this.isDeferred = function(x) { return (typeof(x) == 'object' && typeof(x.then) == 'function' && typeof(x.promise) == 'function'); }; this.deferrable = function() { if (this.isDeferred(arguments[0])) return arguments[0]; var d = $.Deferred(); d.resolve.apply(d, arguments); return d.promise(); }; this.i18n = { loaded: false, catalog: { }, plural: function(n) { return 0 + (n != 1) }, init: function() { if (_luci2.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]), { async: false, cache: true, dataType: 'json', success: function(data) { $.extend(_luci2.i18n.catalog, data); var pe = _luci2.i18n.catalog['']; if (pe) { delete _luci2.i18n.catalog['']; try { var pf = new Function('n', 'return 0 + (' + pe + ')'); _luci2.i18n.plural = pf; } catch (e) { }; } } }); _luci2.i18n.loaded = true; } }; this.tr = function(msgid) { _luci2.i18n.init(); var msgstr = _luci2.i18n.catalog[msgid]; if (typeof(msgstr) == 'undefined') return msgid; else if (typeof(msgstr) == 'string') return msgstr; else return msgstr[0]; }; this.trp = function(msgid, msgid_plural, count) { _luci2.i18n.init(); var msgstr = _luci2.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)]; }; this.trc = function(msgctx, msgid) { _luci2.i18n.init(); var msgstr = _luci2.i18n.catalog[msgid + '\u0004' + msgctx]; if (typeof(msgstr) == 'undefined') return msgid; else if (typeof(msgstr) == 'string') return msgstr; else return msgstr[0]; }; this.trcp = function(msgctx, msgid, msgid_plural, count) { _luci2.i18n.init(); var msgstr = _luci2.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)]; }; this.setHash = function(key, value) { var h = ''; var data = this.getHash(undefined); if (typeof(value) == 'undefined') delete data[key]; else data[key] = value; var keys = [ ]; for (var k in data) keys.push(k); keys.sort(); for (var i = 0; i < keys.length; i++) { if (i > 0) h += ','; h += keys[i] + ':' + data[keys[i]]; } if (h) location.hash = '#' + h; }; this.getHash = function(key) { var data = { }; var tuples = (location.hash || '#').substring(1).split(/,/); for (var i = 0; i < tuples.length; i++) { var tuple = tuples[i].split(/:/); if (tuple.length == 2) data[tuple[0]] = tuple[1]; } if (typeof(key) != 'undefined') return data[key]; return data; }; this.globals = { timeout: 3000, resource: '/luci2', sid: '00000000000000000000000000000000' }; this.rpc = { _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); }, _list_cb: function(msg) { /* verify message frame */ if (typeof(msg) != 'object' || msg.jsonrpc != '2.0' || !msg.id) throw 'Invalid JSON response'; return msg.result; }, _call_cb: function(msg) { var data = [ ]; var type = Object.prototype.toString; 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'; /* 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) { for (var key in req.expect) { if (typeof(ret) != 'undefined' && key != '') ret = ret[key]; if (type.call(ret) != type.call(req.expect[key])) ret = req.expect[key]; break; } } /* apply filter */ if (typeof(req.filter) == 'function') { req.priv[0] = ret; req.priv[1] = req.params; ret = req.filter.apply(_luci2.rpc, req.priv); } /* store response data */ if (typeof(req.index) == 'number') data[req.index] = ret; else data = ret; /* delete request object */ delete _luci2.rpc._requests[msg[i].id]; } return data; }, list: function() { 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); }, batch: function() { if (!$.isArray(this._batch)) this._batch = [ ]; }, flush: function() { if (!$.isArray(this._batch)) return _luci2.deferrable([ ]); var req = this._batch; delete this._batch; /* call rpc */ return this._call(req, this._call_cb); }, declare: function(options) { var _rpc = this; 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]; /* all remaining arguments are private args */ var priv = [ undefined, undefined ]; for (; p_off < arguments.length; p_off++) priv.push(arguments[p_off]); /* store request info */ var req = _rpc._requests[_rpc._id] = { expect: options.expect, filter: options.filter, params: params, priv: priv }; /* build message object */ var msg = { jsonrpc: '2.0', id: _rpc._id++, method: 'call', params: [ _luci2.globals.sid, options.object, options.method, params ] }; /* 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() { return _luci2.session.access('ubus', 'uci', 'commit'); }, add: _luci2.rpc.declare({ object: 'uci', method: 'add', params: [ 'config', 'type', 'name', 'values' ], expect: { section: '' } }), apply: function() { }, changes: _luci2.rpc.declare({ object: 'uci', method: 'changes', params: [ 'config' ], expect: { changes: [ ] } }), 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) { 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 this._foreach(config, type).then(function(sections) { for (var s in sections) cb(sections[s]); }); }, get: _luci2.rpc.declare({ object: 'uci', method: 'get', params: [ 'config', 'section', 'option' ], expect: { '': { } }, filter: function(data, params) { if (typeof(params.option) == 'undefined') return data.values ? data.values['.type'] : undefined; else return data.value; } }), 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) { 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 val; } return undefined; }); }, section: _luci2.rpc.declare({ object: 'uci', method: 'add', params: [ 'config', 'type', 'name', 'values' ], expect: { section: '' } }), _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 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 this._set(config, section, values); }, order: _luci2.rpc.declare({ object: 'uci', method: 'order', params: [ 'config', 'sections' ] }) }; this.network = { listNetworkNames: function() { return _luci2.rpc.list('network.interface.*').then(function(list) { var names = [ ]; for (var name in list) if (name != 'network.interface.loopback') names.push(name.substring(18)); names.sort(); return names; }); }, 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; } }), getNetworkStatus: function() { var nets = [ ]; var devs = { }; return this.listNetworkNames().then(function(names) { _luci2.rpc.batch(); for (var i = 0; i < names.length; i++) _luci2.network.getInterfaceStatus(names[i]); return _luci2.rpc.flush(); }).then(function(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] = { }; } _luci2.rpc.batch(); for (var dev in devs) _luci2.network.listDeviceNamestatus(dev); return _luci2.rpc.flush(); }).then(function(devices) { _luci2.rpc.batch(); for (var i = 0; i < devices.length; i++) { var brm = devices[i]['bridge-members']; delete devices[i]['bridge-members']; $.extend(devs[devices[i]['device']], devices[i]); if (!brm) continue; devs[devices[i]['device']].subdevices = [ ]; for (var j = 0; j < brm.length; j++) { if (!devs[brm[j]]) { devs[brm[j]] = { }; _luci2.network.listDeviceNamestatus(brm[j]); } devs[devices[i]['device']].subdevices[j] = devs[brm[j]]; } } return _luci2.rpc.flush(); }).then(function(subdevices) { for (var i = 0; i < subdevices.length; i++) $.extend(devs[subdevices[i]['device']], subdevices[i]); _luci2.rpc.batch(); for (var dev in devs) _luci2.wireless.getDeviceStatus(dev); 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]; nets.sort(function(a, b) { if (a['interface'] < b['interface']) return -1; else if (a['interface'] > b['interface']) return 1; else return 0; }); return nets; }); }, findWanInterfaces: function(cb) { return this.listNetworkNames().then(function(names) { _luci2.rpc.batch(); for (var i = 0; i < names.length; i++) _luci2.network.getInterfaceStatus(names[i]); return _luci2.rpc.flush(); }).then(function(interfaces) { var rv = [ undefined, undefined ]; for (var i = 0; i < interfaces.length; i++) { 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] = interfaces[i]; else if (rt.target == '::' && rt.mask == 0) rv[1] = interfaces[i]; } } return rv; }); }, 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; } }), listDeviceNamestatus: _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 } } }) }; this.wireless = { 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']; data.sort(function(a, b) { if (a.bssid < b.bssid) return -1; else if (a.bssid > b.bssid) return 1; else return 0; }); return data; } }), getWirelessStatus: function() { return this.listDeviceNames().then(function(names) { _luci2.rpc.batch(); for (var i = 0; i < names.length; i++) _luci2.wireless.getDeviceStatus(names[i]); return _luci2.rpc.flush(); }).then(function(networks) { var rv = { }; var phy_attrs = [ 'country', 'channel', 'frequency', 'frequency_offset', 'txpower', 'txpower_offset', 'hwmodes', 'hardware', 'phy' ]; var net_attrs = [ 'ssid', 'bssid', 'mode', 'quality', 'quality_max', 'signal', 'noise', 'bitrate', 'encryption' ]; for (var i = 0; i < networks.length; i++) { var phy = rv[networks[i].phy] || ( rv[networks[i].phy] = { networks: [ ] } ); var net = { device: networks[i].device }; for (var j = 0; j < phy_attrs.length; j++) phy[phy_attrs[j]] = networks[i][phy_attrs[j]]; for (var j = 0; j < net_attrs.length; j++) net[net_attrs[j]] = networks[i][net_attrs[j]]; phy.networks.push(net); } return rv; }); }, getAssocLists: function() { return this.listDeviceNames().then(function(names) { _luci2.rpc.batch(); for (var i = 0; i < names.length; i++) _luci2.wireless.getAssocList(names[i]); return _luci2.rpc.flush(); }).then(function(assoclists) { var rv = [ ]; for (var i = 0; i < assoclists.length; i++) for (var j = 0; j < assoclists[i].length; j++) rv.push(assoclists[i][j]); return rv; }); }, formatEncryption: function(enc) { var format_list = function(l, s) { var rv = [ ]; for (var i = 0; i < l.length; i++) rv.push(l[i].toUpperCase()); return rv.join(s ? s : ', '); } if (!enc || !enc.enabled) return _luci2.tr('None'); if (enc.wep) { if (enc.wep.length == 2) return _luci2.tr('WEP Open/Shared') + ' (%s)'.format(format_list(enc.ciphers, ', ')); else if (enc.wep[0] == 'shared') return _luci2.tr('WEP Shared Auth') + ' (%s)'.format(format_list(enc.ciphers, ', ')); else return _luci2.tr('WEP Open System') + ' (%s)'.format(format_list(enc.ciphers, ', ')); } else if (enc.wpa) { if (enc.wpa.length == 2) return _luci2.tr('mixed WPA/WPA2') + ' %s (%s)'.format( format_list(enc.authentication, '/'), format_list(enc.ciphers, ', ') ); else if (enc.wpa[0] == 2) return 'WPA2 %s (%s)'.format( format_list(enc.authentication, '/'), format_list(enc.ciphers, ', ') ); else return 'WPA %s (%s)'.format( format_list(enc.authentication, '/'), format_list(enc.ciphers, ', ') ); } return _luci2.tr('Unknown'); } }; this.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: { '': { } } }), getInfo: function(cb) { _luci2.rpc.batch(); this.getSystemInfo(); this.getBoardInfo(); this.getDiskInfo(); return _luci2.rpc.flush().then(function(info) { var rv = { }; $.extend(rv, info[0]); $.extend(rv, info[1]); $.extend(rv, info[2]); return rv; }); }, 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({ 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().then(function(list) { for (var i = 0; i < list.length; i++) if (list[i].name == init) return !!list[i].enabled; return false; }); }, 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) }, 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: [ ] } }), performReboot: _luci2.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({ 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.session.access('ubus', 'session', 'access'); }, startHeartbeat: function() { this._hearbeatInterval = window.setInterval(function() { _luci2.session.isAlive().then(function(alive) { if (!alive) { _luci2.session.stopHeartbeat(); _luci2.ui.login(true); } }); }, _luci2.globals.timeout * 2); }, stopHeartbeat: function() { if (typeof(this._hearbeatInterval) != 'undefined') { window.clearInterval(this._hearbeatInterval); delete this._hearbeatInterval; } } }; this.ui = { loading: function(enable) { var win = $(window); var body = $('body'); var div = _luci2._modal || ( _luci2._modal = $('
') .addClass('cbi-modal-loader') .append($('
').text(_luci2.tr('Loading data...'))) .appendTo(body) ); if (enable) { 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()); div.show(); } else { div.hide(); body.css('overflow', ''); body.css('padding', ''); body.css('width', ''); body.css('height', ''); } }, dialog: function(title, content, options) { var win = $(window); var body = $('body'); var div = _luci2._dialog || ( _luci2._dialog = $('
') .addClass('cbi-modal-dialog') .append($('
') .append($('
') .addClass('cbi-modal-dialog-header')) .append($('
') .addClass('cbi-modal-dialog-body')) .append($('
') .addClass('cbi-modal-dialog-footer') .append($('