Merge pull request #3024 from TDT-AG/pr/20190829-material-logo
[oweals/luci.git] / modules / luci-base / htdocs / luci-static / resources / luci.js
index 3e40b304b61c4cafebb36ce635dd55ab67bf087a..687ac0e678646b77cbe6eb27425ce2d17689fe87 100644 (file)
                });
        }
 
+       /* Promise.finally polyfill */
+       if (typeof Promise.prototype.finally !== 'function') {
+               Promise.prototype.finally = function(fn) {
+                       var onFinally = function(cb) {
+                               return Promise.resolve(fn.call(this)).then(cb);
+                       };
+
+                       return this.then(
+                               function(result) { return onFinally.call(this, function() { return result }) },
+                               function(reason) { return onFinally.call(this, function() { return Promise.reject(reason) }) }
+                       );
+               };
+       }
+
        /*
         * Class declaration and inheritance helper
         */
                                        opt.xhr.open(opt.method, opt.url, true);
 
                                opt.xhr.responseType = 'text';
-                               opt.xhr.overrideMimeType('application/octet-stream');
+
+                               if ('overrideMimeType' in opt.xhr)
+                                       opt.xhr.overrideMimeType('application/octet-stream');
 
                                if ('timeout' in opt)
                                        opt.xhr.timeout = +opt.timeout;
                                                break;
 
                                        case 'object':
-                                               content = JSON.stringify(opt.content);
-                                               contenttype = 'application/json';
+                                               if (!(opt.content instanceof FormData)) {
+                                                       content = JSON.stringify(opt.content);
+                                                       contenttype = 'application/json';
+                                               }
+                                               else {
+                                                       content = opt.content;
+                                               }
                                                break;
 
                                        default:
                                                                contenttype = opt.headers[header];
                                                }
 
+                               if ('progress' in opt && 'upload' in opt.xhr)
+                                       opt.xhr.upload.addEventListener('progress', opt.progress);
+
                                if (contenttype != null)
                                        opt.xhr.setRequestHeader('Content-Type', contenttype);
 
                        return true;
                },
 
-               remove: function(entry) {
+               remove: function(fn) {
                        if (typeof(fn) != 'function')
                                throw new TypeError('Invalid argument to LuCI.Poll.remove()');
 
            domParser = null,
            originalCBIInit = null,
            rpcBaseURL = null,
+           sysFeatures = null,
            classes = {};
 
        var LuCI = Class.extend({
 
                        if (type instanceof Error) {
                                e = type;
-                               stack = (e.stack || '').split(/\n/);
 
                                if (msg)
                                        e.message = msg + ': ' + e.message;
                        }
                        else {
+                               try { throw new Error('stacktrace') }
+                               catch (e2) { stack = (e2.stack || '').split(/\n/) }
+
                                e = new (window[type || 'Error'] || Error)(msg || 'Unspecified error');
                                e.name = type || 'Error';
                        }
 
+                       stack = (stack || []).map(function(frame) {
+                               frame = frame.replace(/(.*?)@(.+):(\d+):(\d+)/g, 'at $1 ($2:$3:$4)').trim();
+                               return frame ? '  ' + frame : '';
+                       });
+
+                       if (!/^  at /.test(stack[0]))
+                               stack.shift();
+
+                       if (/\braise /.test(stack[0]))
+                               stack.shift();
+
+                       if (/\berror /.test(stack[0]))
+                               stack.shift();
+
+                       if (stack.length)
+                               e.message += '\n' + stack.join('\n');
+
                        if (window.console && console.debug)
                                console.debug(e);
 
                                L.raise.apply(L, Array.prototype.slice.call(arguments));
                        }
                        catch (e) {
-                               var stack = (e.stack || '').split(/\n/).map(function(frame) {
-                                       frame = frame.replace(/(.*?)@(.+):(\d+):(\d+)/g, 'at $1 ($2:$3:$4)').trim();
-                                       return frame ? '  ' + frame : '';
-                               });
-
-                               if (!/^  at /.test(stack[0]))
-                                       stack.shift();
-
-                               if (/\braise /.test(stack[0]))
-                                       stack.shift();
-
-                               if (/\berror /.test(stack[0]))
-                                       stack.shift();
-
-                               stack = stack.length ? '\n' + stack.join('\n') : '';
+                               if (!e.reported) {
+                                       if (L.ui)
+                                               L.ui.addNotification(e.name || _('Runtime error'),
+                                                       E('pre', {}, e.message), 'danger');
+                                       else
+                                               L.dom.content(document.querySelector('#maincontent'),
+                                                       E('pre', { 'class': 'alert-message error' }, e.message));
 
-                               if (L.ui)
-                                       L.ui.showModal(e.name || _('Runtime error'),
-                                               E('pre', { 'class': 'alert-message error' }, e.message + stack));
-                               else
-                                       L.dom.content(document.querySelector('#maincontent'),
-                                               E('pre', { 'class': 'alert-message error' }, e + stack));
+                                       e.reported = true;
+                               }
 
                                throw e;
                        }
                        }
 
                        if (rpcBaseURL == null) {
+                               var rpcFallbackURL = this.url('admin/ubus');
+
                                rpcBaseURL = Request.get('/ubus/').then(function(res) {
-                                       return (rpcBaseURL = (res.status == 400) ? '/ubus/' : this.url('admin/ubus'));
+                                       return (rpcBaseURL = (res.status == 400) ? '/ubus/' : rpcFallbackURL);
                                }, function() {
-                                       return (rpcBaseURL = L.url('admin/ubus'));
+                                       return (rpcBaseURL = rpcFallbackURL);
                                }).then(function(url) {
                                        try {
                                                window.sessionStorage.setItem('rpcBaseURL', url);
                        return Promise.resolve(rpcBaseURL);
                },
 
+               probeSystemFeatures: function() {
+                       if (sysFeatures == null) {
+                               try {
+                                       sysFeatures = JSON.parse(window.sessionStorage.getItem('sysFeatures'));
+                               }
+                               catch (e) {}
+                       }
+
+                       if (!this.isObject(sysFeatures)) {
+                               sysFeatures = classes.rpc.declare({
+                                       object: 'luci',
+                                       method: 'getFeatures',
+                                       expect: { '': {} }
+                               })().then(function(features) {
+                                       try {
+                                               window.sessionStorage.setItem('sysFeatures', JSON.stringify(features));
+                                       }
+                                       catch (e) {}
+
+                                       sysFeatures = features;
+
+                                       return features;
+                               });
+                       }
+
+                       return Promise.resolve(sysFeatures);
+               },
+
+               hasSystemFeature: function() {
+                       var ft = sysFeatures[arguments[0]];
+
+                       if (arguments.length == 2)
+                               return this.isObject(ft) ? ft[arguments[1]] : null;
+
+                       return (ft != null && ft != false);
+               },
+
+               notifySessionExpiry: function() {
+                       Poll.stop();
+
+                       L.ui.showModal(_('Session expired'), [
+                               E('div', { class: 'alert-message warning' },
+                                       _('A new login is required since the authentication session expired.')),
+                               E('div', { class: 'right' },
+                                       E('div', {
+                                               class: 'btn primary',
+                                               click: function() {
+                                                       var loc = window.location;
+                                                       window.location = loc.protocol + '//' + loc.host + loc.pathname + loc.search;
+                                               }
+                                       }, _('To login…')))
+                       ]);
+
+                       L.raise('SessionError', 'Login session is expired');
+               },
+
                setupDOM: function(res) {
                        var domEv = res[0],
                            uiClass = res[1],
 
                        rpcClass.setBaseURL(rpcBaseURL);
 
-                       Request.addInterceptor(function(res) {
-                               if (res.status != 403 || res.headers.get('X-LuCI-Login-Required') != 'yes')
+                       rpcClass.addInterceptor(function(msg, req) {
+                               if (!L.isObject(msg) || !L.isObject(msg.error) || msg.error.code != -32002)
                                        return;
 
-                               Poll.stop();
-
-                               L.ui.showModal(_('Session expired'), [
-                                       E('div', { class: 'alert-message warning' },
-                                               _('A new login is required since the authentication session expired.')),
-                                       E('div', { class: 'right' },
-                                               E('div', {
-                                                       class: 'btn primary',
-                                                       click: function() {
-                                                               var loc = window.location;
-                                                               window.location = loc.protocol + '//' + loc.host + loc.pathname + loc.search;
-                                                       }
-                                               }, _('To login…')))
-                               ]);
+                               if (!L.isObject(req) || (req.object == 'session' && req.method == 'access'))
+                                       return;
 
-                               throw 'Session expired';
+                               return rpcClass.declare({
+                                       'object': 'session',
+                                       'method': 'access',
+                                       'params': [ 'scope', 'object', 'function' ],
+                                       'expect': { access: true }
+                               })('uci', 'luci', 'read').catch(L.notifySessionExpiry);
                        });
 
-                       originalCBIInit();
+                       Request.addInterceptor(function(res) {
+                               var isDenied = false;
 
-                       Poll.start();
+                               if (res.status == 403 && res.headers.get('X-LuCI-Login-Required') == 'yes')
+                                       isDenied = true;
+
+                               if (!isDenied)
+                                       return;
+
+                               L.notifySessionExpiry();
+                       });
+
+                       return this.probeSystemFeatures().finally(this.initDOM);
+               },
 
+               initDOM: function() {
+                       originalCBIInit();
+                       Poll.start();
                        document.dispatchEvent(new CustomEvent('luci-loaded'));
                },
 
                                        return null;
 
                                return inst[method].apply(inst, inst.varargs(arguments, 2));
+                       },
+
+                       isEmpty: function(node, ignoreFn) {
+                               for (var child = node.firstElementChild; child != null; child = child.nextElementSibling)
+                                       if (!child.classList.contains('hidden') && (!ignoreFn || !ignoreFn(child)))
+                                               return false;
+
+                               return true;
                        }
                }),
 
 
                                if (mc.querySelector('.cbi-map')) {
                                        footer.appendChild(E('div', { 'class': 'cbi-page-actions' }, [
-                                               E('input', {
+                                               E('button', {
                                                        'class': 'cbi-button cbi-button-apply',
-                                                       'type': 'button',
-                                                       'value': _('Save & Apply'),
-                                                       'click': L.bind(this.handleSaveApply, this)
-                                               }), ' ',
-                                               E('input', {
+                                                       'click': L.ui.createHandlerFn(this, 'handleSaveApply')
+                                               }, _('Save & Apply')), ' ',
+                                               E('button', {
                                                        'class': 'cbi-button cbi-button-save',
-                                                       'type': 'submit',
-                                                       'value': _('Save'),
-                                                       'click': L.bind(this.handleSave, this)
-                                               }), ' ',
-                                               E('input', {
+                                                       'click': L.ui.createHandlerFn(this, 'handleSave')
+                                               }, _('Save')), ' ',
+                                               E('button', {
                                                        'class': 'cbi-button cbi-button-reset',
-                                                       'type': 'button',
-                                                       'value': _('Reset'),
-                                                       'click': L.bind(this.handleReset, this)
-                                               })
+                                                       'click': L.ui.createHandlerFn(this, 'handleReset')
+                                               }, _('Reset'))
                                        ]));
                                }