3 saveScrollTop: function()
5 this._scroll_top
= $(document
).scrollTop();
8 restoreScrollTop: function()
10 if (typeof(this._scroll_top
) == 'undefined')
13 $(document
).scrollTop(this._scroll_top
);
15 delete this._scroll_top
;
18 loading: function(enable
)
23 var state
= this._loading
|| (this._loading
= {
26 .addClass('modal fade')
28 .addClass('modal-dialog')
30 .addClass('modal-content luci2-modal-loader')
32 .addClass('modal-body')
33 .text(L
.tr('Loading data…')))))
41 state
.modal
.modal(enable
? 'show' : 'hide');
44 dialog: function(title
, content
, options
)
50 var state
= this._dialog
|| (this._dialog
= {
52 .addClass('modal fade')
54 .addClass('modal-dialog')
56 .addClass('modal-content')
58 .addClass('modal-header')
60 .addClass('modal-title'))
62 .addClass('modal-body'))
64 .addClass('modal-footer')
65 .append(self
.button(L
.tr('Close'), 'primary')
67 $(this).parents('div.modal').modal('hide');
72 if (typeof(options
) != 'object')
77 state
.dialog
.modal('hide');
82 var cnt
= state
.dialog
.children().children().children('div.modal-body');
83 var ftr
= state
.dialog
.children().children().children('div.modal-footer');
87 if (options
.style
== 'confirm')
89 ftr
.append(L
.ui
.button(L
.tr('Ok'), 'primary')
90 .click(options
.confirm
|| function() { L
.ui
.dialog(false) }));
92 ftr
.append(L
.ui
.button(L
.tr('Cancel'), 'default')
93 .click(options
.cancel
|| function() { L
.ui
.dialog(false) }));
95 else if (options
.style
== 'close')
97 ftr
.append(L
.ui
.button(L
.tr('Close'), 'primary')
98 .click(options
.close
|| function() { L
.ui
.dialog(false) }));
100 else if (options
.style
== 'wait')
102 ftr
.append(L
.ui
.button(L
.tr('Close'), 'primary')
103 .attr('disabled', true));
108 state
.dialog
.addClass('wide');
112 state
.dialog
.removeClass('wide');
115 state
.dialog
.find('h4:first').text(title
);
116 state
.dialog
.modal('show');
118 cnt
.empty().append(content
);
123 upload: function(title
, content
, options
)
125 var state
= L
.ui
._upload
|| (L
.ui
._upload
= {
127 .attr('method', 'post')
128 .attr('action', '/cgi-bin/luci-upload')
129 .attr('enctype', 'multipart/form-data')
130 .attr('target', 'cbi-fileupload-frame')
132 .append($('<input />')
133 .attr('type', 'hidden')
134 .attr('name', 'sessionid'))
135 .append($('<input />')
136 .attr('type', 'hidden')
137 .attr('name', 'filename'))
138 .append($('<input />')
139 .attr('type', 'file')
140 .attr('name', 'filedata')
141 .addClass('cbi-input-file'))
143 .css('width', '100%')
144 .addClass('progress progress-striped active')
146 .addClass('progress-bar')
147 .css('width', '100%')))
148 .append($('<iframe />')
149 .addClass('pull-right')
150 .attr('name', 'cbi-fileupload-frame')
152 .css('height', '1px')
153 .css('visibility', 'hidden')),
155 finish_cb: function(ev
) {
158 var body
= (this.contentDocument
|| this.contentWindow
.document
).body
;
159 if (body
.firstChild
.tagName
.toLowerCase() == 'pre')
160 body
= body
.firstChild
;
164 json
= $.parseJSON(body
.innerHTML
);
167 message
: L
.tr('Invalid server response received'),
168 error
: [ -1, L
.tr('Invalid data') ]
174 L
.ui
.dialog(L
.tr('File upload'), [
175 $('<p />').text(L
.tr('The file upload failed with the server response below:')),
176 $('<pre />').addClass('alert-message').text(json
.message
|| json
.error
[1]),
177 $('<p />').text(L
.tr('In case of network problems try uploading the file again.'))
178 ], { style
: 'close' });
180 else if (typeof(state
.success_cb
) == 'function')
182 state
.success_cb(json
);
186 confirm_cb: function() {
187 var f
= state
.form
.find('.cbi-input-file');
188 var b
= state
.form
.find('.progress');
189 var p
= state
.form
.find('p');
194 state
.form
.find('iframe').on('load', state
.finish_cb
);
199 p
.text(L
.tr('File upload in progress …'));
201 state
.form
.parent().parent().find('button').prop('disabled', true);
205 state
.form
.find('.progress').hide();
206 state
.form
.find('.cbi-input-file').val('').show();
207 state
.form
.find('p').text(content
|| L
.tr('Select the file to upload and press "%s" to proceed.').format(L
.tr('Ok')));
209 state
.form
.find('[name=sessionid]').val(L
.globals
.sid
);
210 state
.form
.find('[name=filename]').val(options
.filename
);
212 state
.success_cb
= options
.success
;
214 L
.ui
.dialog(title
|| L
.tr('File upload'), state
.form
, {
216 confirm
: state
.confirm_cb
220 reconnect: function()
222 var protocols
= (location
.protocol
== 'https:') ? [ 'http', 'https' ] : [ 'http' ];
223 var ports
= (location
.protocol
== 'https:') ? [ 80, location
.port
|| 443 ] : [ location
.port
|| 80 ];
224 var address
= location
.hostname
.match(/^[A-Fa-f0-9]*:[A-Fa-f0-9:]+$/) ? '[' + location
.hostname
+ ']' : location
.hostname
;
226 var interval
, timeout
;
229 L
.tr('Waiting for device'), [
230 $('<p />').text(L
.tr('Please stand by while the device is reconfiguring …')),
232 .css('width', '100%')
233 .addClass('progressbar')
234 .addClass('intermediate')
236 .css('width', '100%'))
240 for (var i
= 0; i
< protocols
.length
; i
++)
241 images
= images
.add($('<img />').attr('url', protocols
[i
] + '://' + address
+ ':' + ports
[i
]));
243 //L.network.getNetworkStatus(function(s) {
244 // for (var i = 0; i < protocols.length; i++)
246 // for (var j = 0; j < s.length; j++)
248 // for (var k = 0; k < s[j]['ipv4-address'].length; k++)
249 // images = images.add($('<img />').attr('url', protocols[i] + '://' + s[j]['ipv4-address'][k].address + ':' + ports[i]));
251 // for (var l = 0; l < s[j]['ipv6-address'].length; l++)
252 // images = images.add($('<img />').attr('url', protocols[i] + '://[' + s[j]['ipv6-address'][l].address + ']:' + ports[i]));
255 //}).then(function() {
256 images
.on('load', function() {
257 var url
= this.getAttribute('url');
258 L
.session
.isAlive().then(function(access
) {
261 window
.clearTimeout(timeout
);
262 window
.clearInterval(interval
);
273 interval
= window
.setInterval(function() {
274 images
.each(function() {
275 this.setAttribute('src', this.getAttribute('url') + L
.globals
.resource
+ '/icons/loading.gif?r=' + Math
.random());
279 timeout
= window
.setTimeout(function() {
280 window
.clearInterval(interval
);
284 L
.tr('Device not responding'),
285 L
.tr('The device was not responding within 180 seconds, you might need to manually reconnect your computer or use SSH to regain access.'),
292 login: function(invalid
)
294 var state
= L
.ui
._login
|| (L
.ui
._login
= {
297 .attr('method', 'post')
299 .addClass('alert alert-danger')
300 .text(L
.tr('Wrong username or password given!')))
302 .append($('<label />')
303 .text(L
.tr('Username'))
305 .append($('<input />')
306 .attr('type', 'text')
307 .attr('name', 'username')
308 .attr('value', 'root')
309 .addClass('form-control')
310 .keypress(function(ev
) {
311 if (ev
.which
== 10 || ev
.which
== 13)
315 .append($('<label />')
316 .text(L
.tr('Password'))
318 .append($('<input />')
319 .attr('type', 'password')
320 .attr('name', 'password')
321 .addClass('form-control')
322 .keypress(function(ev
) {
323 if (ev
.which
== 10 || ev
.which
== 13)
327 .text(L
.tr('Enter your username and password above, then click "%s" to proceed.').format(L
.tr('Ok')))),
329 response_cb: function(response
) {
330 if (!response
.ubus_rpc_session
)
336 L
.globals
.sid
= response
.ubus_rpc_session
;
337 L
.setHash('id', L
.globals
.sid
);
338 L
.session
.startHeartbeat();
340 state
.deferred
.resolve();
344 confirm_cb: function() {
345 var u
= state
.form
.find('[name=username]').val();
346 var p
= state
.form
.find('[name=password]').val();
352 L
.tr('Logging in'), [
353 $('<p />').text(L
.tr('Log in in progress …')),
355 .css('width', '100%')
356 .addClass('progressbar')
357 .addClass('intermediate')
359 .css('width', '100%'))
363 L
.globals
.sid
= '00000000000000000000000000000000';
364 L
.session
.login(u
, p
).then(state
.response_cb
);
368 if (!state
.deferred
|| state
.deferred
.state() != 'pending')
369 state
.deferred
= $.Deferred();
371 /* try to find sid from hash */
372 var sid
= L
.getHash('id');
373 if (sid
&& sid
.match(/^[a-f0-9]{32}$/))
376 L
.session
.isAlive().then(function(access
) {
379 L
.session
.startHeartbeat();
380 state
.deferred
.resolve();
384 L
.setHash('id', undefined);
389 return state
.deferred
;
393 state
.form
.find('.alert').show();
395 state
.form
.find('.alert').hide();
397 L
.ui
.dialog(L
.tr('Authorization Required'), state
.form
, {
399 confirm
: state
.confirm_cb
402 state
.form
.find('[name=password]').focus();
404 return state
.deferred
;
407 cryptPassword
: L
.rpc
.declare({
411 expect
: { crypt
: '' }
415 mergeACLScope: function(acl_scope
, scope
)
417 if ($.isArray(scope
))
419 for (var i
= 0; i
< scope
.length
; i
++)
420 acl_scope
[scope
[i
]] = true;
422 else if ($.isPlainObject(scope
))
424 for (var object_name
in scope
)
426 if (!$.isArray(scope
[object_name
]))
429 var acl_object
= acl_scope
[object_name
] || (acl_scope
[object_name
] = { });
431 for (var i
= 0; i
< scope
[object_name
].length
; i
++)
432 acl_object
[scope
[object_name
][i
]] = true;
437 mergeACLPermission: function(acl_perm
, perm
)
439 if ($.isPlainObject(perm
))
441 for (var scope_name
in perm
)
443 var acl_scope
= acl_perm
[scope_name
] || (acl_perm
[scope_name
] = { });
444 L
.ui
.mergeACLScope(acl_scope
, perm
[scope_name
]);
449 mergeACLGroup: function(acl_group
, group
)
451 if ($.isPlainObject(group
))
453 if (!acl_group
.description
)
454 acl_group
.description
= group
.description
;
458 var acl_perm
= acl_group
.read
|| (acl_group
.read
= { });
459 L
.ui
.mergeACLPermission(acl_perm
, group
.read
);
464 var acl_perm
= acl_group
.write
|| (acl_group
.write
= { });
465 L
.ui
.mergeACLPermission(acl_perm
, group
.write
);
470 callACLsCallback: function(trees
)
474 for (var i
= 0; i
< trees
.length
; i
++)
476 if (!$.isPlainObject(trees
[i
]))
479 for (var group_name
in trees
[i
])
481 var acl_group
= acl_tree
[group_name
] || (acl_tree
[group_name
] = { });
482 L
.ui
.mergeACLGroup(acl_group
, trees
[i
][group_name
]);
489 callACLs
: L
.rpc
.declare({
492 expect
: { acls
: [ ] }
495 getAvailableACLs: function()
497 return this.callACLs().then(this.callACLsCallback
);
500 renderChangeIndicator: function()
503 .addClass('nav navbar-nav navbar-right')
506 .attr('id', 'changes')
508 .append($('<span />')
509 .addClass('label label-info'))));
512 callMenuCallback: function(entries
)
514 L
.globals
.mainMenu
= new L
.ui
.menu();
515 L
.globals
.mainMenu
.entries(entries
);
519 .append(L
.globals
.mainMenu
.render(0, 1))
520 .append(L
.ui
.renderChangeIndicator());
523 callMenu
: L
.rpc
.declare({
526 expect
: { menu
: { } }
529 renderMainMenu: function()
531 return this.callMenu().then(this.callMenuCallback
);
534 renderViewMenu: function()
538 .append(L
.globals
.mainMenu
.render(2, 900));
541 renderView: function()
543 var node
= arguments
[0];
544 var name
= node
.view
.split(/\//).join('.');
545 var cname
= L
.toClassName(name
);
546 var views
= L
.views
|| (L
.views
= { });
549 for (var i
= 1; i
< arguments
.length
; i
++)
550 args
.push(arguments
[i
]);
552 if (L
.globals
.currentView
)
553 L
.globals
.currentView
.finish();
555 L
.ui
.renderViewMenu();
556 L
.setHash('view', node
.view
);
558 if (views
[cname
] instanceof L
.ui
.view
)
560 L
.globals
.currentView
= views
[cname
];
561 return views
[cname
].render
.apply(views
[cname
], args
);
564 var url
= L
.globals
.resource
+ '/view/' + name
+ '.js';
570 }).then(function(data
) {
572 var viewConstructorSource
= (
573 '(function(L, $) { ' +
579 var viewConstructor
= eval(viewConstructorSource
);
581 views
[cname
] = new viewConstructor({
583 acls
: node
.write
|| { }
586 L
.globals
.currentView
= views
[cname
];
587 return views
[cname
].render
.apply(views
[cname
], args
);
590 alert('Unable to instantiate view "%s": %s'.format(url
, e
));
593 return $.Deferred().resolve();
597 changeView: function()
599 var name
= L
.getHash('view');
600 var node
= L
.globals
.defaultNode
;
602 if (name
&& L
.globals
.mainMenu
)
603 node
= L
.globals
.mainMenu
.getNode(name
);
608 L
.ui
.renderView(node
).then(function() {
609 $('#mainmenu.in').collapse('hide');
615 updateHostname: function()
617 return L
.system
.getBoardInfo().then(function(info
) {
619 $('#hostname').text(info
.hostname
);
623 updateChanges: function()
625 return L
.uci
.changes().then(function(changes
) {
629 for (var config
in changes
)
633 for (var i
= 0; i
< changes
[config
].length
; i
++)
635 var c
= changes
[config
][i
];
640 log
.push('uci reorder %s.<ins>%s=<strong>%s</strong></ins>'.format(config
, c
[1], c
[2]));
645 log
.push('uci delete %s.<del>%s</del>'.format(config
, c
[1]));
647 log
.push('uci delete %s.%s.<del>%s</del>'.format(config
, c
[1], c
[2]));
652 log
.push('uci rename %s.<ins>%s=<strong>%s</strong></ins>'.format(config
, c
[1], c
[2], c
[3]));
654 log
.push('uci rename %s.%s.<ins>%s=<strong>%s</strong></ins>'.format(config
, c
[1], c
[2], c
[3], c
[4]));
658 log
.push('uci add %s <ins>%s</ins> (= <ins><strong>%s</strong></ins>)'.format(config
, c
[2], c
[1]));
662 log
.push('uci add_list %s.%s.<ins>%s=<strong>%s</strong></ins>'.format(config
, c
[1], c
[2], c
[3], c
[4]));
666 log
.push('uci del_list %s.%s.<del>%s=<strong>%s</strong></del>'.format(config
, c
[1], c
[2], c
[3], c
[4]));
671 log
.push('uci set %s.<ins>%s=<strong>%s</strong></ins>'.format(config
, c
[1], c
[2]));
673 log
.push('uci set %s.%s.<ins>%s=<strong>%s</strong></ins>'.format(config
, c
[1], c
[2], c
[3], c
[4]));
678 html
+= '<code>/etc/config/%s</code><pre class="uci-changes">%s</pre>'.format(config
, log
.join('\n'));
679 n
+= changes
[config
].length
;
684 .click(function(ev
) {
685 L
.ui
.dialog(L
.tr('Staged configuration changes'), html
, {
687 confirm: function() {
689 function(code
) { alert('Success with code ' + code
); },
690 function(code
) { alert('Error with code ' + code
); }
698 .text(L
.trcp('Pending configuration changes', '1 change', '%d changes', n
).format(n
));
700 $('#changes').children('span').hide();
711 L
.session
.updateACLs(),
712 self
.updateHostname(),
713 self
.updateChanges(),
714 self
.renderMainMenu(),
717 self
.renderView(L
.globals
.defaultNode
).then(function() {
721 $(window
).on('hashchange', function() {
727 button: function(label
, style
, title
)
729 style
= style
|| 'default';
731 return $('<button />')
732 .attr('type', 'button')
733 .attr('title', title
? title
: '')
734 .addClass('btn btn-' + style
)
738 icon: function(src
, alt
, title
)
740 if (!src
.match(/\.[a-z]+$/))
743 if (!src
.match(/^\//))
744 src
= L
.globals
.resource
+ '/icons/' + src
;
746 var icon
= $('<img />')
749 if (typeof(alt
) !== 'undefined')
750 icon
.attr('alt', alt
);
752 if (typeof(title
) !== 'undefined')
753 icon
.attr('title', title
);
759 ui_class
.AbstractWidget
= Class
.extend({
760 i18n: function(text
) {
765 var key
= arguments
[0];
768 for (var i
= 1; i
< arguments
.length
; i
++)
769 args
.push(arguments
[i
]);
771 switch (typeof(this.options
[key
]))
777 return this.options
[key
].apply(this, args
);
780 return ''.format
.apply('' + this.options
[key
], args
);
784 toString: function() {
785 return $('<div />').append(this.render()).html();
788 insertInto: function(id
) {
789 return $(id
).empty().append(this.render());
792 appendTo: function(id
) {
793 return $(id
).append(this.render());
796 on: function(evname
, evfunc
)
798 var evnames
= L
.toArray(evname
);
803 for (var i
= 0; i
< evnames
.length
; i
++)
804 this.events
[evnames
[i
]] = evfunc
;
809 trigger: function(evname
, evdata
)
813 var evnames
= L
.toArray(evname
);
815 for (var i
= 0; i
< evnames
.length
; i
++)
816 if (this.events
[evnames
[i
]])
817 this.events
[evnames
[i
]].call(this, evdata
);
824 ui_class
.view
= ui_class
.AbstractWidget
.extend({
825 _fetch_template: function()
827 return $.ajax(L
.globals
.resource
+ '/template/' + this.options
.name
+ '.htm', {
831 success: function(data
) {
832 data
= data
.replace(/<%([#:=])?(.+?)%>/g, function(match
, p1
, p2
) {
833 p2
= p2
.replace(/^\s+/, '').replace(/\s+$/, '');
843 return L
.globals
[p2
] || '';
846 return '(?' + match
+ ')';
850 $('#maincontent').append(data
);
857 throw "Not implemented";
862 var container
= $('#maincontent');
867 container
.append($('<h2 />').append(this.title
));
869 if (this.description
)
870 container
.append($('<p />').append(this.description
));
875 for (var i
= 0; i
< arguments
.length
; i
++)
876 args
.push(arguments
[i
]);
878 return this._fetch_template().then(function() {
879 return L
.deferrable(self
.execute
.apply(self
, args
));
883 repeat: function(func
, interval
)
888 self
._timeouts
= [ ];
890 var index
= self
._timeouts
.length
;
892 if (typeof(interval
) != 'number')
895 var setTimer
, runTimer
;
897 setTimer = function() {
899 self
._timeouts
[index
] = window
.setTimeout(runTimer
, interval
);
902 runTimer = function() {
903 L
.deferrable(func
.call(self
)).then(setTimer
, setTimer
);
911 if ($.isArray(this._timeouts
))
913 for (var i
= 0; i
< this._timeouts
.length
; i
++)
914 window
.clearTimeout(this._timeouts
[i
]);
916 delete this._timeouts
;
921 ui_class
.menu
= ui_class
.AbstractWidget
.extend({
926 entries: function(entries
)
928 for (var entry
in entries
)
930 var path
= entry
.split(/\//);
931 var node
= this._nodes
;
933 for (i
= 0; i
< path
.length
; i
++)
938 if (!node
.childs
[path
[i
]])
939 node
.childs
[path
[i
]] = { };
941 node
= node
.childs
[path
[i
]];
944 $.extend(node
, entries
[entry
]);
948 sortNodesCallback: function(a
, b
)
950 var x
= a
.index
|| 0;
951 var y
= b
.index
|| 0;
955 firstChildView: function(node
)
961 for (var child
in (node
.childs
|| { }))
962 nodes
.push(node
.childs
[child
]);
964 nodes
.sort(this.sortNodesCallback
);
966 for (var i
= 0; i
< nodes
.length
; i
++)
968 var child
= this.firstChildView(nodes
[i
]);
971 for (var key
in child
)
972 if (!node
.hasOwnProperty(key
) && child
.hasOwnProperty(key
))
973 node
[key
] = child
[key
];
982 handleClick: function(ev
)
984 L
.setHash('view', ev
.data
);
990 renderNodes: function(childs
, level
, min
, max
)
993 for (var node
in childs
)
995 var child
= this.firstChildView(childs
[node
]);
997 nodes
.push(childs
[node
]);
1000 nodes
.sort(this.sortNodesCallback
);
1002 var list
= $('<ul />');
1005 list
.addClass('nav').addClass('navbar-nav');
1006 else if (level
== 1)
1007 list
.addClass('dropdown-menu').addClass('navbar-inverse');
1009 for (var i
= 0; i
< nodes
.length
; i
++)
1011 if (!L
.globals
.defaultNode
)
1013 var v
= L
.getHash('view');
1014 if (!v
|| v
== nodes
[i
].view
)
1015 L
.globals
.defaultNode
= nodes
[i
];
1018 var item
= $('<li />')
1021 .text(L
.tr(nodes
[i
].title
)))
1024 if (nodes
[i
].childs
&& level
< max
)
1026 item
.addClass('dropdown');
1029 .addClass('dropdown-toggle')
1030 .attr('data-toggle', 'dropdown')
1031 .append('<b class="caret"></b>');
1033 item
.append(this.renderNodes(nodes
[i
].childs
, level
+ 1));
1037 item
.find('a').click(nodes
[i
].view
, this.handleClick
);
1044 render: function(min
, max
)
1046 var top
= min
? this.getNode(L
.globals
.defaultNode
.view
, min
) : this._nodes
;
1047 return this.renderNodes(top
.childs
, 0, min
, max
);
1050 getNode: function(path
, max
)
1052 var p
= path
.split(/\//);
1053 var n
= this._nodes
;
1055 if (typeof(max
) == 'undefined')
1058 for (var i
= 0; i
< max
; i
++)
1060 if (!n
.childs
[p
[i
]])
1070 ui_class
.table
= ui_class
.AbstractWidget
.extend({
1076 row: function(values
)
1078 if ($.isArray(values
))
1080 this._rows
.push(values
);
1082 else if ($.isPlainObject(values
))
1085 for (var i
= 0; i
< this.options
.columns
.length
; i
++)
1087 var col
= this.options
.columns
[i
];
1089 if (typeof col
.key
== 'string')
1090 v
.push(values
[col
.key
]);
1098 rows: function(rows
)
1100 for (var i
= 0; i
< rows
.length
; i
++)
1104 render: function(id
)
1106 var fieldset
= document
.createElement('fieldset');
1107 fieldset
.className
= 'cbi-section';
1109 if (this.options
.caption
)
1111 var legend
= document
.createElement('legend');
1112 $(legend
).append(this.options
.caption
);
1113 fieldset
.appendChild(legend
);
1116 var table
= document
.createElement('table');
1117 table
.className
= 'table table-condensed table-hover';
1119 var has_caption
= false;
1120 var has_description
= false;
1122 for (var i
= 0; i
< this.options
.columns
.length
; i
++)
1123 if (this.options
.columns
[i
].caption
)
1128 else if (this.options
.columns
[i
].description
)
1130 has_description
= true;
1136 var tr
= table
.insertRow(-1);
1137 tr
.className
= 'cbi-section-table-titles';
1139 for (var i
= 0; i
< this.options
.columns
.length
; i
++)
1141 var col
= this.options
.columns
[i
];
1142 var th
= document
.createElement('th');
1143 th
.className
= 'cbi-section-table-cell';
1148 th
.style
.width
= col
.width
;
1151 th
.style
.textAlign
= col
.align
;
1154 $(th
).append(col
.caption
);
1158 if (has_description
)
1160 var tr
= table
.insertRow(-1);
1161 tr
.className
= 'cbi-section-table-descr';
1163 for (var i
= 0; i
< this.options
.columns
.length
; i
++)
1165 var col
= this.options
.columns
[i
];
1166 var th
= document
.createElement('th');
1167 th
.className
= 'cbi-section-table-cell';
1172 th
.style
.width
= col
.width
;
1175 th
.style
.textAlign
= col
.align
;
1177 if (col
.description
)
1178 $(th
).append(col
.description
);
1182 if (this._rows
.length
== 0)
1184 if (this.options
.placeholder
)
1186 var tr
= table
.insertRow(-1);
1187 var td
= tr
.insertCell(-1);
1188 td
.className
= 'cbi-section-table-cell';
1190 td
.colSpan
= this.options
.columns
.length
;
1191 $(td
).append(this.options
.placeholder
);
1196 for (var i
= 0; i
< this._rows
.length
; i
++)
1198 var tr
= table
.insertRow(-1);
1200 for (var j
= 0; j
< this.options
.columns
.length
; j
++)
1202 var col
= this.options
.columns
[j
];
1203 var td
= tr
.insertCell(-1);
1205 var val
= this._rows
[i
][j
];
1207 if (typeof(val
) == 'undefined')
1208 val
= col
.placeholder
;
1210 if (typeof(val
) == 'undefined')
1214 td
.style
.width
= col
.width
;
1217 td
.style
.textAlign
= col
.align
;
1219 if (typeof col
.format
== 'string')
1220 $(td
).append(col
.format
.format(val
));
1221 else if (typeof col
.format
== 'function')
1222 $(td
).append(col
.format(val
, i
));
1230 fieldset
.appendChild(table
);
1236 ui_class
.progress
= ui_class
.AbstractWidget
.extend({
1239 var vn
= parseInt(this.options
.value
) || 0;
1240 var mn
= parseInt(this.options
.max
) || 100;
1241 var pc
= Math
.floor((100 / mn
) * vn
);
1245 if (typeof(this.options
.format
) == 'string')
1246 text
= this.options
.format
.format(this.options
.value
, this.options
.max
, pc
);
1247 else if (typeof(this.options
.format
) == 'function')
1248 text
= this.options
.format(pc
);
1250 text
= '%.2f%%'.format(pc
);
1253 .addClass('progress')
1254 .append($('<div />')
1255 .addClass('progress-bar')
1256 .addClass('progress-bar-info')
1257 .css('width', pc
+ '%'))
1258 .append($('<small />')
1263 ui_class
.devicebadge
= ui_class
.AbstractWidget
.extend({
1266 var l2dev
= this.options
.l2_device
|| this.options
.device
;
1267 var l3dev
= this.options
.l3_device
;
1268 var dev
= l3dev
|| l2dev
|| '?';
1270 var span
= document
.createElement('span');
1271 span
.className
= 'badge';
1273 if (typeof(this.options
.signal
) == 'number' ||
1274 typeof(this.options
.noise
) == 'number')
1277 if (typeof(this.options
.signal
) != 'undefined' &&
1278 typeof(this.options
.noise
) != 'undefined')
1280 var q
= (-1 * (this.options
.noise
- this.options
.signal
)) / 5;
1293 span
.appendChild(document
.createElement('img'));
1294 span
.lastChild
.src
= L
.globals
.resource
+ '/icons/signal-' + r
+ '.png';
1297 span
.title
= L
.tr('No signal');
1299 span
.title
= '%s: %d %s / %s: %d %s'.format(
1300 L
.tr('Signal'), this.options
.signal
, L
.tr('dBm'),
1301 L
.tr('Noise'), this.options
.noise
, L
.tr('dBm')
1306 var type
= 'ethernet';
1307 var desc
= L
.tr('Ethernet device');
1312 desc
= L
.tr('Tunnel interface');
1314 else if (dev
.indexOf('br-') == 0)
1317 desc
= L
.tr('Bridge');
1319 else if (dev
.indexOf('.') > 0)
1322 desc
= L
.tr('VLAN interface');
1324 else if (dev
.indexOf('wlan') == 0 ||
1325 dev
.indexOf('ath') == 0 ||
1326 dev
.indexOf('wl') == 0)
1329 desc
= L
.tr('Wireless Network');
1332 span
.appendChild(document
.createElement('img'));
1333 span
.lastChild
.src
= L
.globals
.resource
+ '/icons/' + type
+ (this.options
.up
? '' : '_disabled') + '.png';
1337 $(span
).append(' ');
1338 $(span
).append(dev
);
1344 return Class
.extend(ui_class
);