1 (function(window
, document
, undefined) {
2 /* Object.assign polyfill for IE */
3 if (typeof Object
.assign
!== 'function') {
4 Object
.defineProperty(Object
, 'assign', {
5 value
: function assign(target
, varArgs
) {
7 throw new TypeError('Cannot convert undefined or null to object');
9 var to
= Object(target
);
11 for (var index
= 1; index
< arguments
.length
; index
++)
12 if (arguments
[index
] != null)
13 for (var nextKey
in arguments
[index
])
14 if (Object
.prototype.hasOwnProperty
.call(arguments
[index
], nextKey
))
15 to
[nextKey
] = arguments
[index
][nextKey
];
25 * Class declaration and inheritance helper
28 var toCamelCase = function(s
) {
29 return s
.replace(/(?:^|[\. -])(.)/g, function(m0
, m1
) { return m1
.toUpperCase() });
32 var superContext
= null, Class
= Object
.assign(function() {}, {
33 extend: function(properties
) {
35 __base__
: { value
: this.prototype },
36 __name__
: { value
: properties
.__name__
|| 'anonymous' }
39 var ClassConstructor = function() {
40 if (!(this instanceof ClassConstructor
))
41 throw new TypeError('Constructor must not be called without "new"');
43 if (Object
.getPrototypeOf(this).hasOwnProperty('__init__')) {
44 if (typeof(this.__init__
) != 'function')
45 throw new TypeError('Class __init__ member is not a function');
47 this.__init__
.apply(this, arguments
)
50 this.super('__init__', arguments
);
54 for (var key
in properties
)
55 if (!props
[key
] && properties
.hasOwnProperty(key
))
56 props
[key
] = { value
: properties
[key
], writable
: true };
58 ClassConstructor
.prototype = Object
.create(this.prototype, props
);
59 ClassConstructor
.prototype.constructor = ClassConstructor
;
60 Object
.assign(ClassConstructor
, this);
61 ClassConstructor
.displayName
= toCamelCase(props
.__name__
.value
+ 'Class');
63 return ClassConstructor
;
66 singleton: function(properties
/*, ... */) {
67 return Class
.extend(properties
)
68 .instantiate(Class
.prototype.varargs(arguments
, 1));
71 instantiate: function(args
) {
72 return new (Function
.prototype.bind
.apply(this,
73 Class
.prototype.varargs(args
, 0, null)))();
76 call: function(self
, method
) {
77 if (typeof(this.prototype[method
]) != 'function')
78 throw new ReferenceError(method
+ ' is not defined in class');
80 return this.prototype[method
].apply(self
, self
.varargs(arguments
, 1));
83 isSubclass: function(_class
) {
84 return (_class
!= null &&
85 typeof(_class
) == 'function' &&
86 _class
.prototype instanceof this);
90 varargs: function(args
, offset
/*, ... */) {
91 return Array
.prototype.slice
.call(arguments
, 2)
92 .concat(Array
.prototype.slice
.call(args
, offset
));
95 super: function(key
, callArgs
) {
96 for (superContext
= Object
.getPrototypeOf(superContext
||
97 Object
.getPrototypeOf(this));
98 superContext
&& !superContext
.hasOwnProperty(key
);
99 superContext
= Object
.getPrototypeOf(superContext
)) { }
104 var res
= superContext
[key
];
106 if (arguments
.length
> 1) {
107 if (typeof(res
) != 'function')
108 throw new ReferenceError(key
+ ' is not a function in base class');
110 if (typeof(callArgs
) != 'object')
111 callArgs
= this.varargs(arguments
, 1);
113 res
= res
.apply(this, callArgs
);
121 toString: function() {
122 var s
= '[' + this.constructor.displayName
+ ']', f
= true;
123 for (var k
in this) {
124 if (this.hasOwnProperty(k
)) {
125 s
+= (f
? ' {\n' : '') + ' ' + k
+ ': ' + typeof(this[k
]) + '\n';
129 return s
+ (f
? '' : '}');
136 * HTTP Request helper
139 Headers
= Class
.extend({
140 __name__
: 'LuCI.XHR.Headers',
141 __init__: function(xhr
) {
142 var hdrs
= this.headers
= {};
143 xhr
.getAllResponseHeaders().split(/\r\n/).forEach(function(line
) {
144 var m
= /^([^:]+):(.*)$/.exec(line
);
146 hdrs
[m
[1].trim().toLowerCase()] = m
[2].trim();
150 has: function(name
) {
151 return this.headers
.hasOwnProperty(String(name
).toLowerCase());
154 get: function(name
) {
155 var key
= String(name
).toLowerCase();
156 return this.headers
.hasOwnProperty(key
) ? this.headers
[key
] : null;
160 Response
= Class
.extend({
161 __name__
: 'LuCI.XHR.Response',
162 __init__: function(xhr
, url
, duration
) {
163 this.ok
= (xhr
.status
>= 200 && xhr
.status
<= 299);
164 this.status
= xhr
.status
;
165 this.statusText
= xhr
.statusText
;
166 this.responseText
= xhr
.responseText
;
167 this.headers
= new Headers(xhr
);
168 this.duration
= duration
;
174 return JSON
.parse(this.responseText
);
178 return this.responseText
;
182 Request
= Class
.singleton({
183 __name__
: 'LuCI.Request',
187 request: function(target
, options
) {
188 var state
= { xhr
: new XMLHttpRequest(), url
: target
, start
: Date
.now() },
189 opt
= Object
.assign({}, options
, state
),
192 callback
= this.handleReadyStateChange
;
194 return new Promise(function(resolveFn
, rejectFn
) {
195 opt
.xhr
.onreadystatechange
= callback
.bind(opt
, resolveFn
, rejectFn
);
196 opt
.method
= String(opt
.method
|| 'GET').toUpperCase();
198 if ('query' in opt
) {
199 var q
= (opt
.query
!= null) ? Object
.keys(opt
.query
).map(function(k
) {
200 if (opt
.query
[k
] != null) {
201 var v
= (typeof(opt
.query
[k
]) == 'object')
202 ? JSON
.stringify(opt
.query
[k
])
203 : String(opt
.query
[k
]);
205 return '%s=%s'.format(encodeURIComponent(k
), encodeURIComponent(v
));
208 return encodeURIComponent(k
);
213 switch (opt
.method
) {
217 opt
.url
+= ((/\?/).test(opt
.url
) ? '&' : '?') + q
;
221 if (content
== null) {
223 contenttype
= 'application/x-www-form-urlencoded';
230 opt
.url
+= ((/\?/).test(opt
.url
) ? '&' : '?') + (new Date()).getTime();
232 if (!/^(?:[^/]+:)?\/\//.test(opt
.url
))
233 opt
.url
= location
.protocol
+ '//' + location
.host
+ opt
.url
;
235 if ('username' in opt
&& 'password' in opt
)
236 opt
.xhr
.open(opt
.method
, opt
.url
, true, opt
.username
, opt
.password
);
238 opt
.xhr
.open(opt
.method
, opt
.url
, true);
240 opt
.xhr
.responseType
= 'text';
241 opt
.xhr
.overrideMimeType('application/octet-stream');
243 if ('timeout' in opt
)
244 opt
.xhr
.timeout
= +opt
.timeout
;
246 if ('credentials' in opt
)
247 opt
.xhr
.withCredentials
= !!opt
.credentials
;
249 if (opt
.content
!= null) {
250 switch (typeof(opt
.content
)) {
252 content
= opt
.content(xhr
);
256 content
= JSON
.stringify(opt
.content
);
257 contenttype
= 'application/json';
261 content
= String(opt
.content
);
265 if ('headers' in opt
)
266 for (var header
in opt
.headers
)
267 if (opt
.headers
.hasOwnProperty(header
)) {
268 if (header
.toLowerCase() != 'content-type')
269 opt
.xhr
.setRequestHeader(header
, opt
.headers
[header
]);
271 contenttype
= opt
.headers
[header
];
274 if (contenttype
!= null)
275 opt
.xhr
.setRequestHeader('Content-Type', contenttype
);
278 opt
.xhr
.send(content
);
281 rejectFn
.call(opt
, e
);
286 handleReadyStateChange: function(resolveFn
, rejectFn
, ev
) {
289 if (xhr
.readyState
!== 4)
292 if (xhr
.status
=== 0 && xhr
.statusText
=== '') {
293 rejectFn
.call(this, new Error('XHR request aborted by browser'));
296 var response
= new Response(
297 xhr
, xhr
.responseURL
|| this.url
, Date
.now() - this.start
);
299 Promise
.all(Request
.interceptors
.map(function(fn
) { return fn(response
) }))
300 .then(resolveFn
.bind(this, response
))
301 .catch(rejectFn
.bind(this));
310 get: function(url
, options
) {
311 return this.request(url
, Object
.assign({ method
: 'GET' }, options
));
314 post: function(url
, data
, options
) {
315 return this.request(url
, Object
.assign({ method
: 'POST', content
: data
}, options
));
318 addInterceptor: function(interceptorFn
) {
319 if (typeof(interceptorFn
) == 'function')
320 this.interceptors
.push(interceptorFn
);
321 return interceptorFn
;
324 removeInterceptor: function(interceptorFn
) {
325 var oldlen
= this.interceptors
.length
, i
= oldlen
;
327 if (this.interceptors
[i
] === interceptorFn
)
328 this.interceptors
.splice(i
, 1);
329 return (this.interceptors
.length
< oldlen
);
332 poll
: Class
.singleton({
333 __name__
: 'LuCI.Request.Poll',
337 add: function(interval
, url
, options
, callback
) {
338 if (isNaN(interval
) || interval
<= 0)
339 throw new TypeError('Invalid poll interval');
352 remove: function(entry
) {
353 var oldlen
= this.queue
.length
, i
= oldlen
;
356 if (this.queue
[i
] === entry
) {
357 delete this.queue
[i
].running
;
358 this.queue
.splice(i
, 1);
361 if (!this.queue
.length
)
364 return (this.queue
.length
< oldlen
);
368 if (!this.queue
.length
|| this.active())
372 this.timer
= window
.setInterval(this.step
, 1000);
374 document
.dispatchEvent(new CustomEvent('poll-start'));
382 document
.dispatchEvent(new CustomEvent('poll-stop'));
383 window
.clearInterval(this.timer
);
390 Request
.poll
.queue
.forEach(function(e
) {
391 if ((Request
.poll
.tick
% e
.interval
) != 0)
397 var opts
= Object
.assign({}, e
.options
,
398 { timeout
: e
.interval
* 1000 - 5 });
401 Request
.request(e
.url
, opts
)
402 .then(function(res
) {
403 if (!e
.running
|| !Request
.poll
.active())
407 e
.callback(res
, res
.json(), res
.duration
);
410 e
.callback(res
, null, res
.duration
);
413 .finally(function() { delete e
.running
});
416 Request
.poll
.tick
= (Request
.poll
.tick
+ 1) % Math
.pow(2, 32);
420 return (this.timer
!= null);
426 var dummyElem
= null,
428 originalCBIInit
= null,
431 LuCI
= Class
.extend({
433 __init__: function(env
) {
435 document
.querySelectorAll('script[src*="/luci.js"]').forEach(function(s
) {
436 if (env
.base_url
== null || env
.base_url
== '')
437 env
.base_url
= s
.getAttribute('src').replace(/\/luci\.js(?:\?v=[^?]+)?$/, '');
440 if (env
.base_url
== null)
441 this.error('InternalError', 'Cannot find url of luci.js');
443 Object
.assign(this.env
, env
);
445 document
.addEventListener('poll-start', function(ev
) {
446 document
.querySelectorAll('[id^="xhr_poll_status"]').forEach(function(e
) {
447 e
.style
.display
= (e
.id
== 'xhr_poll_status_off') ? 'none' : '';
451 document
.addEventListener('poll-stop', function(ev
) {
452 document
.querySelectorAll('[id^="xhr_poll_status"]').forEach(function(e
) {
453 e
.style
.display
= (e
.id
== 'xhr_poll_status_on') ? 'none' : '';
457 var domReady
= new Promise(function(resolveFn
, rejectFn
) {
458 document
.addEventListener('DOMContentLoaded', resolveFn
);
465 ]).then(this.setupDOM
.bind(this)).catch(this.error
);
467 originalCBIInit
= window
.cbi_init
;
468 window
.cbi_init = function() {};
471 error: function(type
, fmt
/*, ...*/) {
473 msg
= fmt
? String
.prototype.format
.apply(fmt
, this.varargs(arguments
, 2)) : null,
476 if (type
instanceof Error
) {
478 stack
= (e
.stack
|| '').split(/\n/);
481 e
.message
= msg
+ ': ' + e
.message
;
484 e
= new (window
[type
|| 'Error'] || Error
)(msg
|| 'Unspecified error');
485 e
.name
= type
|| 'Error';
487 try { throw new Error('stacktrace') }
488 catch (e2
) { stack
= (e2
.stack
|| '').split(/\n/) }
490 /* IE puts the exception message into the first line */
491 if (stack
[0] == 'Error: stacktrace')
494 /* Pop L.error() invocation from stack */
498 /* Append shortened & beautified stacktrace to message */
499 var trace
= stack
.join('\n')
500 .replace(/(.*?)@(.+):(\d+):(\d+)/g, ' at $1 ($2:$3:$4)');
502 if (e
.message
.indexOf(trace
) == -1)
503 e
.message
+= '\n' + trace
;
505 if (window
.console
&& console
.debug
)
509 this.ui
.showModal(_('Runtime error'),
510 E('pre', { 'class': 'alert-message error' }, e
));
512 L
.dom
.content(document
.querySelector('#maincontent'),
513 E('pre', { 'class': 'alert-message error' }, e
));
518 bind: function(fn
, self
/*, ... */) {
519 return Function
.prototype.bind
.apply(fn
, this.varargs(arguments
, 2, self
));
523 require: function(name
, from) {
524 var L
= this, url
= null, from = from || [];
526 /* Class already loaded */
527 if (classes
[name
] != null) {
528 /* Circular dependency */
529 if (from.indexOf(name
) != -1)
530 L
.error('DependencyError',
531 'Circular dependency: class "%s" depends on "%s"',
532 name
, from.join('" which depends on "'));
534 return classes
[name
];
537 url
= '%s/%s.js'.format(L
.env
.base_url
, name
.replace(/\./g, '/'));
538 from = [ name ].concat(from);
540 var compileClass = function(res) {
542 L.error('NetworkError
',
543 'HTTP error
%d
while loading
class file
"%s"', res.status, url);
545 var source = res.text(),
546 requirematch = /^require[ \t]+(\S+)(?:[ \t]+as[ \t]+([a-zA-Z_]\S*))?$/,
547 strictmatch = /^use[ \t]+strict$/,
551 /* find require statements in source */
552 for (var i = 0, off = -1, quote = -1, esc = false; i < source.length; i++) {
553 var chr = source.charCodeAt(i);
558 else if (chr == 92) {
561 else if (chr == quote) {
562 var s = source.substring(off, i),
563 m = requirematch.exec(s);
566 var dep = m[1], as = m[2] || dep.replace(/[^a-zA-Z0-9_]/g, '_
');
567 depends.push(L.require(dep, from));
570 else if (!strictmatch.exec(s)) {
577 else if (quote == -1 && (chr == 34 || chr == 39)) {
583 /* load dependencies and instantiate class */
584 return Promise.all(depends).then(function(instances) {
587 '(function(window
, document
, L
%s
) { %s
})\n\n//# sourceURL=%s\n'
588 .format(args
, source
, res
.url
));
591 L
.error('SyntaxError', '%s\n in %s:%s',
592 error
.message
, res
.url
, error
.lineNumber
|| '?');
595 _factory
.displayName
= toCamelCase(name
+ 'ClassFactory');
596 _class
= _factory
.apply(_factory
, [window
, document
, L
].concat(instances
));
598 if (!Class
.isSubclass(_class
))
599 L
.error('TypeError', '"%s" factory yields invalid constructor', name
);
601 if (_class
.displayName
== 'AnonymousClass')
602 _class
.displayName
= toCamelCase(name
+ 'Class');
604 var ptr
= Object
.getPrototypeOf(L
),
605 parts
= name
.split(/\./),
606 instance
= new _class();
608 for (var i
= 0; ptr
&& i
< parts
.length
- 1; i
++)
612 ptr
[parts
[i
]] = instance
;
614 classes
[name
] = instance
;
620 /* Request class file */
621 classes
[name
] = Request
.get(url
, { cache
: true })
625 return classes
[name
];
629 setupDOM: function(ev
) {
630 Request
.addInterceptor(function(res
) {
631 if (res
.status
!= 403 || res
.headers
.get('X-LuCI-Login-Required') != 'yes')
636 L
.ui
.showModal(_('Session expired'), [
637 E('div', { class: 'alert-message warning' },
638 _('A new login is required since the authentication session expired.')),
639 E('div', { class: 'right' },
641 class: 'btn primary',
643 var loc
= window
.location
;
644 window
.location
= loc
.protocol
+ '//' + loc
.host
+ loc
.pathname
+ loc
.search
;
649 throw 'Session expired';
653 Request
.poll
.start();
655 document
.dispatchEvent(new CustomEvent('luci-loaded'));
660 /* URL construction helpers */
661 path: function(prefix
, parts
) {
662 var url
= [ prefix
|| '' ];
664 for (var i
= 0; i
< parts
.length
; i
++)
665 if (/^(?:[a-zA-Z0-9_.%,;-]+\/)*[a-zA-Z0-9_.%,;-]+$/.test(parts
[i
]))
666 url
.push('/', parts
[i
]);
668 if (url
.length
=== 1)
675 return this.path(this.env
.scriptname
, arguments
);
678 resource: function() {
679 return this.path(this.env
.resource
, arguments
);
682 location: function() {
683 return this.path(this.env
.scriptname
, this.env
.requestpath
);
687 /* HTTP resource fetching */
688 get: function(url
, args
, cb
) {
689 return this.poll(null, url
, args
, cb
, false);
692 post: function(url
, args
, cb
) {
693 return this.poll(null, url
, args
, cb
, true);
696 poll: function(interval
, url
, args
, cb
, post
) {
697 if (interval
!== null && interval
<= 0)
698 interval
= this.env
.pollinterval
;
700 var data
= post
? { token
: this.env
.token
} : null,
701 method
= post
? 'POST' : 'GET';
703 if (!/^(?:\/|\S+:\/\/)/.test(url
))
707 data
= Object
.assign(data
|| {}, args
);
709 if (interval
!== null)
710 return Request
.poll
.add(interval
, url
, { method
: method
, query
: data
}, cb
);
712 return Request
.request(url
, { method
: method
, query
: data
})
713 .then(function(res
) {
715 if (/^application\/json\b/.test(res
.headers
.get('Content-Type')))
716 try { json
= res
.json() } catch(e
) {}
717 cb(res
.xhr
, json
, res
.duration
);
721 stop: function(entry
) { return Request
.poll
.remove(entry
) },
722 halt: function() { return Request
.poll
.stop() },
723 run: function() { return Request
.poll
.start() },
725 /* DOM manipulation */
726 dom
: Class
.singleton({
727 __name__
: 'LuCI.DOM',
730 return (e
!= null && typeof(e
) == 'object' && 'nodeType' in e
);
737 domParser
= domParser
|| new DOMParser();
738 elem
= domParser
.parseFromString(s
, 'text/html').body
.firstChild
;
744 dummyElem
= dummyElem
|| document
.createElement('div');
745 dummyElem
.innerHTML
= s
;
746 elem
= dummyElem
.firstChild
;
754 matches: function(node
, selector
) {
755 var m
= this.elem(node
) ? node
.matches
|| node
.msMatchesSelector
: null;
756 return m
? m
.call(node
, selector
) : false;
759 parent: function(node
, selector
) {
760 if (this.elem(node
) && node
.closest
)
761 return node
.closest(selector
);
763 while (this.elem(node
))
764 if (this.matches(node
, selector
))
767 node
= node
.parentNode
;
772 append: function(node
, children
) {
773 if (!this.elem(node
))
776 if (Array
.isArray(children
)) {
777 for (var i
= 0; i
< children
.length
; i
++)
778 if (this.elem(children
[i
]))
779 node
.appendChild(children
[i
]);
780 else if (children
!== null && children
!== undefined)
781 node
.appendChild(document
.createTextNode('' + children
[i
]));
783 return node
.lastChild
;
785 else if (typeof(children
) === 'function') {
786 return this.append(node
, children(node
));
788 else if (this.elem(children
)) {
789 return node
.appendChild(children
);
791 else if (children
!== null && children
!== undefined) {
792 node
.innerHTML
= '' + children
;
793 return node
.lastChild
;
799 content: function(node
, children
) {
800 if (!this.elem(node
))
803 var dataNodes
= node
.querySelectorAll('[data-idref]');
805 for (var i
= 0; i
< dataNodes
.length
; i
++)
806 delete this.registry
[dataNodes
[i
].getAttribute('data-idref')];
808 while (node
.firstChild
)
809 node
.removeChild(node
.firstChild
);
811 return this.append(node
, children
);
814 attr: function(node
, key
, val
) {
815 if (!this.elem(node
))
820 if (typeof(key
) === 'object' && key
!== null)
822 else if (typeof(key
) === 'string')
823 attr
= {}, attr
[key
] = val
;
826 if (!attr
.hasOwnProperty(key
) || attr
[key
] == null)
829 switch (typeof(attr
[key
])) {
831 node
.addEventListener(key
, attr
[key
]);
835 node
.setAttribute(key
, JSON
.stringify(attr
[key
]));
839 node
.setAttribute(key
, attr
[key
]);
845 var html
= arguments
[0],
850 if (!(attr
instanceof Object
) || Array
.isArray(attr
))
851 data
= attr
, attr
= null;
853 if (Array
.isArray(html
)) {
854 elem
= document
.createDocumentFragment();
855 for (var i
= 0; i
< html
.length
; i
++)
856 elem
.appendChild(this.create(html
[i
]));
858 else if (this.elem(html
)) {
861 else if (html
.charCodeAt(0) === 60) {
862 elem
= this.parse(html
);
865 elem
= document
.createElement(html
);
871 this.attr(elem
, attr
);
872 this.append(elem
, data
);
879 data: function(node
, key
, val
) {
880 var id
= node
.getAttribute('data-idref');
883 if (arguments
.length
> 1 && key
== null) {
885 node
.removeAttribute('data-idref');
886 val
= this.registry
[id
]
887 delete this.registry
[id
];
895 else if (arguments
.length
> 2 && key
!= null && val
== null) {
897 val
= this.registry
[id
][key
];
898 delete this.registry
[id
][key
];
906 else if (arguments
.length
> 2 && key
!= null && val
!= null) {
908 do { id
= Math
.floor(Math
.random() * 0xffffffff).toString(16) }
909 while (this.registry
.hasOwnProperty(id
));
911 node
.setAttribute('data-idref', id
);
912 this.registry
[id
] = {};
915 return (this.registry
[id
][key
] = val
);
919 else if (arguments
.length
== 1) {
921 return this.registry
[id
];
927 else if (arguments
.length
== 2) {
929 return this.registry
[id
][key
];
935 bindClassInstance: function(node
, inst
) {
936 if (!(inst
instanceof Class
))
937 L
.error('TypeError', 'Argument must be a class instance');
939 return this.data(node
, '_class', inst
);
942 findClassInstance: function(node
) {
946 inst
= this.data(node
, '_class');
947 node
= node
.parentNode
;
949 while (!(inst
instanceof Class
) && node
!= null);
954 callClassMethod: function(node
, method
/*, ... */) {
955 var inst
= this.findClassInstance(node
);
957 if (inst
== null || typeof(inst
[method
]) != 'function')
960 return inst
[method
].apply(inst
, inst
.varargs(arguments
, 2));
968 __name__
: 'LuCI.View',
970 __init__: function() {
971 var vp
= document
.getElementById('view');
973 L
.dom
.content(vp
, E('div', { 'class': 'spinning' }, _('Loading view…')));
975 return Promise
.resolve(this.load())
976 .then(L
.bind(this.render
, this))
977 .then(L
.bind(function(nodes
) {
978 var vp
= document
.getElementById('view');
980 L
.dom
.content(vp
, nodes
);
981 L
.dom
.append(vp
, this.addFooter());
982 }, this)).catch(L
.error
);
986 render: function() {},
988 handleSave: function(ev
) {
991 document
.getElementById('maincontent')
992 .querySelectorAll('.cbi-map').forEach(function(map
) {
993 tasks
.push(L
.dom
.callClassMethod(map
, 'save'));
996 return Promise
.all(tasks
);
999 handleSaveApply: function(ev
) {
1000 return this.handleSave(ev
).then(function() {
1001 L
.ui
.changes
.apply(true);
1005 handleReset: function(ev
) {
1008 document
.getElementById('maincontent')
1009 .querySelectorAll('.cbi-map').forEach(function(map
) {
1010 tasks
.push(L
.dom
.callClassMethod(map
, 'reset'));
1013 return Promise
.all(tasks
);
1016 addFooter: function() {
1018 mc
= document
.getElementById('maincontent');
1020 if (mc
.querySelector('.cbi-map')) {
1021 footer
.appendChild(E('div', { 'class': 'cbi-page-actions' }, [
1023 'class': 'cbi-button cbi-button-apply',
1025 'value': _('Save & Apply'),
1026 'click': L
.bind(this.handleSaveApply
, this)
1029 'class': 'cbi-button cbi-button-save',
1032 'click': L
.bind(this.handleSave
, this)
1035 'class': 'cbi-button cbi-button-reset',
1037 'value': _('Reset'),
1038 'click': L
.bind(this.handleReset
, this)
1048 XHR
= Class
.extend({
1049 __name__
: 'LuCI.XHR',
1050 __init__: function() {
1051 if (window
.console
&& console
.debug
)
1052 console
.debug('Direct use XHR() is deprecated, please use L.Request instead');
1055 _response: function(cb
, res
, json
, duration
) {
1057 cb(res
, json
, duration
);
1061 get: function(url
, data
, callback
, timeout
) {
1063 L
.get(url
, data
, this._response
.bind(this, callback
), timeout
);
1066 post: function(url
, data
, callback
, timeout
) {
1068 L
.post(url
, data
, this._response
.bind(this, callback
), timeout
);
1071 cancel: function() { delete this.active
},
1072 busy: function() { return (this.active
=== true) },
1073 abort: function() {},
1074 send_form: function() { L
.error('InternalError', 'Not implemented') },
1077 XHR
.get = function() { return window
.L
.get.apply(window
.L
, arguments
) };
1078 XHR
.post = function() { return window
.L
.post
.apply(window
.L
, arguments
) };
1079 XHR
.poll = function() { return window
.L
.poll
.apply(window
.L
, arguments
) };
1080 XHR
.stop
= Request
.poll
.remove
.bind(Request
.poll
);
1081 XHR
.halt
= Request
.poll
.stop
.bind(Request
.poll
);
1082 XHR
.run
= Request
.poll
.start
.bind(Request
.poll
);
1083 XHR
.running
= Request
.poll
.active
.bind(Request
.poll
);
1087 })(window
, document
);