X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fluci2%2Fui.git;a=blobdiff_plain;f=luci2%2Fhtdocs%2Fluci2%2Fluci2.js;h=58d38df249aad5efcb159b8249d36739ae45079f;hp=d2ad812b5549b2be20f9508c8a844b4ea9c00e8c;hb=5627975f94aaea2d2933bbbc4020a73d052c3ad0;hpb=dce206d8191a33863ec36553f761e611d42b8762 diff --git a/luci2/htdocs/luci2/luci2.js b/luci2/htdocs/luci2/luci2.js index d2ad812..58d38df 100644 --- a/luci2/htdocs/luci2/luci2.js +++ b/luci2/htdocs/luci2/luci2.js @@ -214,7 +214,7 @@ function LuCI2() _class.prototype = prototype; _class.prototype.constructor = _class; - _class.extend = arguments.callee; + _class.extend = Class.extend; return _class; }; @@ -364,8 +364,10 @@ function LuCI2() h += keys[i] + ':' + data[keys[i]]; } - if (h) + if (h.length) location.hash = '#' + h; + else + location.hash = ''; }; this.getHash = function(key) @@ -386,6 +388,103 @@ function LuCI2() return data; }; + this.toArray = function(x) + { + switch (typeof(x)) + { + case 'number': + case 'boolean': + return [ x ]; + + case 'string': + var r = [ ]; + var l = x.split(/\s+/); + for (var i = 0; i < l.length; i++) + if (l[i].length > 0) + r.push(l[i]); + return r; + + case 'object': + if ($.isArray(x)) + { + var r = [ ]; + for (var i = 0; i < x.length; i++) + r.push(x[i]); + return r; + } + else if ($.isPlainObject(x)) + { + var r = [ ]; + for (var k in x) + if (x.hasOwnProperty(k)) + r.push(k); + return r.sort(); + } + } + + return [ ]; + }; + + this.toObject = function(x) + { + switch (typeof(x)) + { + case 'number': + case 'boolean': + return { x: true }; + + case 'string': + var r = { }; + var l = x.split(/\x+/); + for (var i = 0; i < l.length; i++) + if (l[i].length > 0) + r[l[i]] = true; + return r; + + case 'object': + if ($.isArray(x)) + { + var r = { }; + for (var i = 0; i < x.length; i++) + r[x[i]] = true; + return r; + } + else if ($.isPlainObject(x)) + { + return x; + } + } + + return { }; + }; + + this.filterArray = function(array, item) + { + if (!$.isArray(array)) + return [ ]; + + for (var i = 0; i < array.length; i++) + if (array[i] === item) + array.splice(i--, 1); + + return array; + }; + + this.toClassName = function(str, suffix) + { + var n = ''; + var l = str.split(/[\/.]/); + + for (var i = 0; i < l.length; i++) + if (l[i].length > 0) + n += l[i].charAt(0).toUpperCase() + l[i].substr(1).toLowerCase(); + + if (typeof(suffix) == 'string') + n += suffix; + + return n; + }; + this.globals = { timeout: 15000, resource: '/luci2', @@ -406,43 +505,48 @@ function LuCI2() data: JSON.stringify(req), dataType: 'json', type: 'POST', - timeout: _luci2.globals.timeout - }).then(cb); + timeout: _luci2.globals.timeout, + _rpc_req: req + }).then(cb, cb); }, _list_cb: function(msg) { + var list = msg.result; + /* verify message frame */ - if (typeof(msg) != 'object' || msg.jsonrpc != '2.0' || !msg.id) - throw 'Invalid JSON response'; + if (typeof(msg) != 'object' || msg.jsonrpc != '2.0' || !msg.id || !$.isArray(list)) + list = [ ]; - return msg.result; + return $.Deferred().resolveWith(this, [ list ]); }, _call_cb: function(msg) { var data = [ ]; var type = Object.prototype.toString; + var reqs = this._rpc_req; - if (!$.isArray(msg)) + if (!$.isArray(reqs)) + { msg = [ msg ]; + reqs = [ reqs ]; + } 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]; + var req = _luci2.rpc._requests[reqs[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]; + /* verify message frame */ + if (typeof(msg[i]) == 'object' && msg[i].jsonrpc == '2.0') + 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) { @@ -451,7 +555,7 @@ function LuCI2() if (typeof(ret) != 'undefined' && key != '') ret = ret[key]; - if (type.call(ret) != type.call(req.expect[key])) + if (typeof(ret) == 'undefined' || type.call(ret) != type.call(req.expect[key])) ret = req.expect[key]; break; @@ -473,10 +577,10 @@ function LuCI2() data = ret; /* delete request object */ - delete _luci2.rpc._requests[msg[i].id]; + delete _luci2.rpc._requests[reqs[i].id]; } - return data; + return $.Deferred().resolveWith(this, [ data ]); }, list: function() @@ -565,6 +669,481 @@ function LuCI2() } }; + this.UCIContext = Class.extend({ + + init: function() + { + this.state = { + newid: 0, + values: { }, + creates: { }, + changes: { }, + deletes: { }, + reorder: { } + }; + }, + + _load: _luci2.rpc.declare({ + object: 'uci', + method: 'get', + params: [ 'config' ], + expect: { values: { } } + }), + + _order: _luci2.rpc.declare({ + object: 'uci', + method: 'order', + params: [ 'config', 'sections' ] + }), + + _add: _luci2.rpc.declare({ + object: 'uci', + method: 'add', + params: [ 'config', 'type', 'name', 'values' ], + expect: { section: '' } + }), + + _set: _luci2.rpc.declare({ + object: 'uci', + method: 'set', + params: [ 'config', 'section', 'values' ] + }), + + _delete: _luci2.rpc.declare({ + object: 'uci', + method: 'delete', + params: [ 'config', 'section', 'options' ] + }), + + load: function(packages) + { + var self = this; + var seen = { }; + var pkgs = [ ]; + + if (!$.isArray(packages)) + packages = [ packages ]; + + _luci2.rpc.batch(); + + for (var i = 0; i < packages.length; i++) + if (!seen[packages[i]]) + { + pkgs.push(packages[i]); + seen[packages[i]] = true; + self._load(packages[i]); + } + + return _luci2.rpc.flush().then(function(responses) { + for (var i = 0; i < responses.length; i++) + self.state.values[pkgs[i]] = responses[i]; + + return pkgs; + }); + }, + + unload: function(packages) + { + if (!$.isArray(packages)) + packages = [ packages ]; + + for (var i = 0; i < packages.length; i++) + { + delete this.state.values[packages[i]]; + delete this.state.creates[packages[i]]; + delete this.state.changes[packages[i]]; + delete this.state.deletes[packages[i]]; + } + }, + + add: function(conf, type, name) + { + var c = this.state.creates; + var s = '.new.%d'.format(this.state.newid++); + + if (!c[conf]) + c[conf] = { }; + + c[conf][s] = { + '.type': type, + '.name': s, + '.create': name, + '.anonymous': !name, + '.index': 1000 + this.state.newid + }; + + return s; + }, + + remove: function(conf, sid) + { + var n = this.state.creates; + var c = this.state.changes; + var d = this.state.deletes; + + /* requested deletion of a just created section */ + if (sid.indexOf('.new.') == 0) + { + if (n[conf]) + delete n[conf][sid]; + } + else + { + if (c[conf]) + delete c[conf][sid]; + + if (!d[conf]) + d[conf] = { }; + + d[conf][sid] = true; + } + }, + + sections: function(conf, type, cb) + { + var sa = [ ]; + var v = this.state.values[conf]; + var n = this.state.creates[conf]; + var c = this.state.changes[conf]; + var d = this.state.deletes[conf]; + + if (!v) + return sa; + + for (var s in v) + if (!d || d[s] !== true) + if (!type || v[s]['.type'] == type) + sa.push($.extend({ }, v[s], c ? c[s] : undefined)); + + if (n) + for (var s in n) + if (!type || n[s]['.type'] == type) + sa.push(n[s]); + + sa.sort(function(a, b) { + return a['.index'] - b['.index']; + }); + + for (var i = 0; i < sa.length; i++) + sa[i]['.index'] = i; + + if (typeof(cb) == 'function') + for (var i = 0; i < sa.length; i++) + cb.call(this, sa[i], sa[i]['.name']); + + return sa; + }, + + get: function(conf, sid, opt) + { + var v = this.state.values; + var n = this.state.creates; + var c = this.state.changes; + var d = this.state.deletes; + + if (typeof(sid) == 'undefined') + return undefined; + + /* requested option in a just created section */ + if (sid.indexOf('.new.') == 0) + { + if (!n[conf]) + return undefined; + + if (typeof(opt) == 'undefined') + return n[conf][sid]; + + return n[conf][sid][opt]; + } + + /* requested an option value */ + if (typeof(opt) != 'undefined') + { + /* check whether option was deleted */ + if (d[conf] && d[conf][sid]) + { + if (d[conf][sid] === true) + return undefined; + + for (var i = 0; i < d[conf][sid].length; i++) + if (d[conf][sid][i] == opt) + return undefined; + } + + /* check whether option was changed */ + if (c[conf] && c[conf][sid] && typeof(c[conf][sid][opt]) != 'undefined') + return c[conf][sid][opt]; + + /* return base value */ + if (v[conf] && v[conf][sid]) + return v[conf][sid][opt]; + + return undefined; + } + + /* requested an entire section */ + if (v[conf]) + return v[conf][sid]; + + return undefined; + }, + + set: function(conf, sid, opt, val) + { + var n = this.state.creates; + var c = this.state.changes; + var d = this.state.deletes; + + if (typeof(sid) == 'undefined' || + typeof(opt) == 'undefined' || + opt.charAt(0) == '.') + return; + + if (sid.indexOf('.new.') == 0) + { + if (n[conf] && n[conf][sid]) + { + if (typeof(val) != 'undefined') + n[conf][sid][opt] = val; + else + delete n[conf][sid][opt]; + } + } + else if (typeof(val) != 'undefined') + { + /* do not set within deleted section */ + if (d[conf] && d[conf][sid] === true) + return; + + if (!c[conf]) + c[conf] = { }; + + if (!c[conf][sid]) + c[conf][sid] = { }; + + /* undelete option */ + if (d[conf] && d[conf][sid]) + d[conf][sid] = _luci2.filterArray(d[conf][sid], opt); + + c[conf][sid][opt] = val; + } + else + { + if (!d[conf]) + d[conf] = { }; + + if (!d[conf][sid]) + d[conf][sid] = [ ]; + + if (d[conf][sid] !== true) + d[conf][sid].push(opt); + } + }, + + unset: function(conf, sid, opt) + { + return this.set(conf, sid, opt, undefined); + }, + + _reload: function() + { + var pkgs = [ ]; + + for (var pkg in this.state.values) + pkgs.push(pkg); + + this.init(); + + return this.load(pkgs); + }, + + _reorder: function() + { + var v = this.state.values; + var n = this.state.creates; + var r = this.state.reorder; + + if ($.isEmptyObject(r)) + return _luci2.deferrable(); + + _luci2.rpc.batch(); + + /* + gather all created and existing sections, sort them according + to their index value and issue an uci order call + */ + for (var c in r) + { + var o = [ ]; + + if (n && n[c]) + for (var s in n[c]) + o.push(n[c][s]); + + for (var s in v[c]) + o.push(v[c][s]); + + if (o.length > 0) + { + o.sort(function(a, b) { + return (a['.index'] - b['.index']); + }); + + var sids = [ ]; + + for (var i = 0; i < o.length; i++) + sids.push(o[i]['.name']); + + this._order(c, sids); + } + } + + this.state.reorder = { }; + return _luci2.rpc.flush(); + }, + + swap: function(conf, sid1, sid2) + { + var s1 = this.get(conf, sid1); + var s2 = this.get(conf, sid2); + var n1 = s1 ? s1['.index'] : NaN; + var n2 = s2 ? s2['.index'] : NaN; + + if (isNaN(n1) || isNaN(n2)) + return false; + + s1['.index'] = n2; + s2['.index'] = n1; + + this.state.reorder[conf] = true; + + return true; + }, + + save: function() + { + _luci2.rpc.batch(); + + var self = this; + var snew = [ ]; + + if (self.state.creates) + for (var c in self.state.creates) + for (var s in self.state.creates[c]) + { + var r = { + config: c, + values: { } + }; + + for (var k in self.state.creates[c][s]) + { + if (k == '.type') + r.type = self.state.creates[c][s][k]; + else if (k == '.create') + r.name = self.state.creates[c][s][k]; + else if (k.charAt(0) != '.') + r.values[k] = self.state.creates[c][s][k]; + } + + snew.push(self.state.creates[c][s]); + + self._add(r.config, r.type, r.name, r.values); + } + + if (self.state.changes) + for (var c in self.state.changes) + for (var s in self.state.changes[c]) + self._set(c, s, self.state.changes[c][s]); + + if (self.state.deletes) + for (var c in self.state.deletes) + for (var s in self.state.deletes[c]) + { + var o = self.state.deletes[c][s]; + self._delete(c, s, (o === true) ? undefined : o); + } + + return _luci2.rpc.flush().then(function(responses) { + /* + array "snew" holds references to the created uci sections, + use it to assign the returned names of the new sections + */ + for (var i = 0; i < snew.length; i++) + snew[i]['.name'] = responses[i]; + + return self._reorder(); + }); + }, + + _apply: _luci2.rpc.declare({ + object: 'uci', + method: 'apply', + params: [ 'timeout', 'rollback' ] + }), + + _confirm: _luci2.rpc.declare({ + object: 'uci', + method: 'confirm' + }), + + apply: function(timeout) + { + var self = this; + var date = new Date(); + var deferred = $.Deferred(); + + if (typeof(timeout) != 'number' || timeout < 1) + timeout = 10; + + self._apply(timeout, true).then(function(rv) { + if (rv != 0) + { + deferred.rejectWith(self, [ rv ]); + return; + } + + var try_deadline = date.getTime() + 1000 * timeout; + var try_confirm = function() + { + return self._confirm().then(function(rv) { + if (rv != 0) + { + if (date.getTime() < try_deadline) + window.setTimeout(try_confirm, 250); + else + deferred.rejectWith(self, [ rv ]); + + return; + } + + deferred.resolveWith(self, [ rv ]); + }); + }; + + window.setTimeout(try_confirm, 1000); + }); + + return deferred; + }, + + changes: _luci2.rpc.declare({ + object: 'uci', + method: 'changes', + expect: { changes: { } } + }), + + readable: function(conf) + { + return _luci2.session.hasACL('uci', conf, 'read'); + }, + + writable: function(conf) + { + return _luci2.session.hasACL('uci', conf, 'write'); + } + }); + this.uci = { writable: function() @@ -1184,7 +1763,58 @@ function LuCI2() ); } - return _luci2.tr('Unknown'); + return _luci2.tr('Unknown'); + } + }; + + this.firewall = { + getZoneColor: function(zone) + { + if ($.isPlainObject(zone)) + zone = zone.name; + + if (zone == 'lan') + return '#90f090'; + else if (zone == 'wan') + return '#f09090'; + + for (var i = 0, hash = 0; + i < zone.length; + hash = zone.charCodeAt(i++) + ((hash << 5) - hash)); + + for (var i = 0, color = '#'; + i < 3; + color += ('00' + ((hash >> i++ * 8) & 0xFF).tozoneing(16)).slice(-2)); + + return color; + }, + + findZoneByNetwork: function(network) + { + 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; + }); } }; @@ -1575,6 +2205,41 @@ function LuCI2() window.clearInterval(this._hearbeatInterval); delete this._hearbeatInterval; } + }, + + + _acls: { }, + + _fetch_acls: _luci2.rpc.declare({ + object: 'session', + method: 'access', + expect: { '': { } } + }), + + _fetch_acls_cb: function(acls) + { + _luci2.session._acls = acls; + }, + + updateACLs: function() + { + return _luci2.session._fetch_acls() + .then(_luci2.session._fetch_acls_cb); + }, + + hasACL: function(scope, object, func) + { + var acls = _luci2.session._acls; + + if (typeof(func) == 'undefined') + return (acls && acls[scope] && acls[scope][object]); + + if (acls && acls[scope] && acls[scope][object]) + for (var i = 0; i < acls[scope][object].length; i++) + if (acls[scope][object][i] == func) + return true; + + return false; } }; @@ -1602,29 +2267,22 @@ function LuCI2() var state = _luci2.ui._loading || (_luci2.ui._loading = { modal: $('
') - .addClass('cbi-modal-loader') - .append($('
').text(_luci2.tr('Loading data...'))) + .addClass('modal fade') + .append($('
') + .addClass('modal-dialog') + .append($('
') + .addClass('modal-content luci2-modal-loader') + .append($('
') + .addClass('modal-body') + .text(_luci2.tr('Loading data…'))))) .appendTo(body) + .modal({ + backdrop: 'static', + keyboard: false + }) }); - if (enable) - { - body.css('overflow', 'hidden'); - body.css('padding', 0); - body.css('width', win.width()); - body.css('height', win.height()); - state.modal.css('width', win.width()); - state.modal.css('height', win.height()); - state.modal.show(); - } - else - { - state.modal.hide(); - body.css('overflow', ''); - body.css('padding', ''); - body.css('width', ''); - body.css('height', ''); - } + state.modal.modal(enable ? 'show' : 'hide'); }, dialog: function(title, content, options) @@ -1634,26 +2292,23 @@ function LuCI2() var state = _luci2.ui._dialog || (_luci2.ui._dialog = { dialog: $('
') - .addClass('cbi-modal-dialog') + .addClass('modal fade') .append($('
') + .addClass('modal-dialog') .append($('
') - .addClass('cbi-modal-dialog-header')) - .append($('
') - .addClass('cbi-modal-dialog-body')) - .append($('
') - .addClass('cbi-modal-dialog-footer') - .append($('