diff options
Diffstat (limited to 'modules/luci-base/htdocs')
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/cbi.js | 1004 | ||||
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/icons/alias.png | bin | 0 -> 706 bytes | |||
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/icons/alias_disabled.png | bin | 0 -> 391 bytes | |||
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/xhr.js | 48 |
4 files changed, 858 insertions, 194 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi.js b/modules/luci-base/htdocs/luci-static/resources/cbi.js index 5790e303dd..ddbff83418 100644 --- a/modules/luci-base/htdocs/luci-static/resources/cbi.js +++ b/modules/luci-base/htdocs/luci-static/resources/cbi.js @@ -23,6 +23,62 @@ function Dec(x) { return (/^-?\d+(?:\.\d+)?$/.test(x) ? +x : NaN); } +function IPv4(x) { + if (!x.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) + return null; + + if (RegExp.$1 > 255 || RegExp.$2 > 255 || RegExp.$3 > 255 || RegExp.$4 > 255) + return null; + + return [ +RegExp.$1, +RegExp.$2, +RegExp.$3, +RegExp.$4 ]; +} + +function IPv6(x) { + if (x.match(/^([a-fA-F0-9:]+):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/)) { + var v6 = RegExp.$1, v4 = IPv4(RegExp.$2); + + if (!v4) + return null; + + x = v6 + ':' + (v4[0] * 256 + v4[1]).toString(16) + + ':' + (v4[2] * 256 + v4[3]).toString(16); + } + + if (!x.match(/^[a-fA-F0-9:]+$/)) + return null; + + var prefix_suffix = x.split(/::/); + + if (prefix_suffix.length > 2) + return null; + + var prefix = (prefix_suffix[0] || '0').split(/:/); + var suffix = prefix_suffix.length > 1 ? (prefix_suffix[1] || '0').split(/:/) : []; + + if (suffix.length ? (prefix.length + suffix.length > 7) : (prefix.length > 8)) + return null; + + var i, word; + var words = []; + + for (i = 0, word = parseInt(prefix[0], 16); i < prefix.length; word = parseInt(prefix[++i], 16)) + if (prefix[i].length <= 4 && !isNaN(word) && word <= 0xFFFF) + words.push(word); + else + return null; + + for (i = 0; i < (8 - prefix.length - suffix.length); i++) + words.push(0); + + for (i = 0, word = parseInt(suffix[0], 16); i < suffix.length; word = parseInt(suffix[++i], 16)) + if (suffix[i].length <= 4 && !isNaN(word) && word <= 0xFFFF) + words.push(word); + else + return null; + + return words; +} + var cbi_validators = { 'integer': function() @@ -53,69 +109,83 @@ var cbi_validators = { 'ip4addr': function() { - if (this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\S+))?$/)) - { - return (RegExp.$1 >= 0) && (RegExp.$1 <= 255) && - (RegExp.$2 >= 0) && (RegExp.$2 <= 255) && - (RegExp.$3 >= 0) && (RegExp.$3 <= 255) && - (RegExp.$4 >= 0) && (RegExp.$4 <= 255) && - ((RegExp.$6.indexOf('.') < 0) - ? ((RegExp.$6 >= 0) && (RegExp.$6 <= 32)) - : (cbi_validators.ip4addr.apply(RegExp.$6))) - ; - } - - return false; + var m = this.match(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|\/(\d{1,2}))?$/); + return !!(m && IPv4(m[1]) && (m[2] ? IPv4(m[2]) : (m[3] ? cbi_validators.ip4prefix.apply(m[3]) : true))); }, 'ip6addr': function() { - if( this.match(/^([a-fA-F0-9:.]+)(\/(\d+))?$/) ) - { - if( !RegExp.$2 || ((RegExp.$3 >= 0) && (RegExp.$3 <= 128)) ) - { - var addr = RegExp.$1; + var m = this.match(/^([0-9a-fA-F:.]+)(?:\/(\d{1,3}))?$/); + return !!(m && IPv6(m[1]) && (m[2] ? cbi_validators.ip6prefix.apply(m[2]) : true)); + }, - if( addr == '::' ) - { - return true; - } + 'ip4prefix': function() + { + return !isNaN(this) && this >= 0 && this <= 32; + }, - if( addr.indexOf('.') > 0 ) - { - var off = addr.lastIndexOf(':'); + 'ip6prefix': function() + { + return !isNaN(this) && this >= 0 && this <= 128; + }, - if( !(off && cbi_validators.ip4addr.apply(addr.substr(off+1))) ) - return false; + 'cidr': function() + { + return cbi_validators.cidr4.apply(this) || + cbi_validators.cidr6.apply(this); + }, - addr = addr.substr(0, off) + ':0:0'; - } + 'cidr4': function() + { + var m = this.match(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})$/); + return !!(m && IPv4(m[1]) && cbi_validators.ip4prefix.apply(m[2])); + }, - if( addr.indexOf('::') >= 0 ) - { - var colons = 0; - var fill = '0'; + 'cidr6': function() + { + var m = this.match(/^([0-9a-fA-F:.]+)\/(\d{1,3})$/); + return !!(m && IPv6(m[1]) && cbi_validators.ip6prefix.apply(m[2])); + }, - for( var i = 1; i < (addr.length-1); i++ ) - if( addr.charAt(i) == ':' ) - colons++; + 'ipnet4': function() + { + var m = this.match(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/); + return !!(m && IPv4(m[1]) && IPv4(m[2])); + }, - if( colons > 7 ) - return false; + 'ipnet6': function() + { + var m = this.match(/^([0-9a-fA-F:.]+)\/([0-9a-fA-F:.]+)$/); + return !!(m && IPv6(m[1]) && IPv6(m[2])); + }, - for( var i = 0; i < (7 - colons); i++ ) - fill += ':0'; + 'ip6hostid': function() + { + if (this == "eui64" || this == "random") + return true; - if (addr.match(/^(.*?)::(.*?)$/)) - addr = (RegExp.$1 ? RegExp.$1 + ':' : '') + fill + - (RegExp.$2 ? ':' + RegExp.$2 : ''); - } + var v6 = IPv6(this); + return !(!v6 || v6[0] || v6[1] || v6[2] || v6[3]); + }, - return (addr.match(/^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$/) != null); - } - } + 'ipmask': function() + { + return cbi_validators.ipmask4.apply(this) || + cbi_validators.ipmask6.apply(this); + }, - return false; + 'ipmask4': function() + { + return cbi_validators.cidr4.apply(this) || + cbi_validators.ipnet4.apply(this) || + cbi_validators.ip4addr.apply(this); + }, + + 'ipmask6': function() + { + return cbi_validators.cidr6.apply(this) || + cbi_validators.ipnet6.apply(this) || + cbi_validators.ip6addr.apply(this); }, 'port': function() @@ -145,15 +215,16 @@ var cbi_validators = { { return cbi_validators.hostname.apply(this) || ((ipv4only != 1) && cbi_validators.ipaddr.apply(this)) || - ((ipv4only == 1) && cb_validators.ip4addr.apply(this)); + ((ipv4only == 1) && cbi_validators.ip4addr.apply(this)); }, - 'hostname': function() + 'hostname': function(strict) { if (this.length <= 253) - return (this.match(/^[a-zA-Z0-9]+$/) != null || + return (this.match(/^[a-zA-Z0-9_]+$/) != null || (this.match(/^[a-zA-Z0-9_][a-zA-Z0-9_\-.]*[a-zA-Z0-9]$/) && - this.match(/[^0-9.]/))); + this.match(/[^0-9.]/))) && + (!strict || !this.match(/^_/)); return false; }, @@ -394,31 +465,16 @@ function cbi_d_add(field, dep, index) { } function cbi_d_checkvalue(target, ref) { - var t = document.getElementById(target); - var value; - - if (!t) { - var tl = document.getElementsByName(target); + var value = null, + query = 'input[id="'+target+'"], input[name="'+target+'"], ' + + 'select[id="'+target+'"], select[name="'+target+'"]'; - if( tl.length > 0 && (tl[0].type == 'radio' || tl[0].type == 'checkbox')) - for( var i = 0; i < tl.length; i++ ) - if( tl[i].checked ) { - value = tl[i].value; - break; - } - - value = value ? value : ""; - } else if (!t.value) { - value = ""; - } else { - value = t.value; - - if (t.type == "checkbox") { - value = t.checked ? value : ""; - } - } + document.querySelectorAll(query).forEach(function(i) { + if (value === null && ((i.type !== 'radio' && i.type !== 'checkbox') || i.checked === true)) + value = i.value; + }); - return (value == ref) + return (((value !== null) ? value : "") == ref); } function cbi_d_check(deps) { @@ -437,8 +493,9 @@ function cbi_d_check(deps) { istat = (istat && cbi_d_checkvalue(j, deps[i][j])) } } - if (istat) { - return !reverse; + + if (istat ^ reverse) { + return true; } } return def; @@ -523,13 +580,6 @@ function cbi_init() { } } - nodes = document.querySelectorAll('[data-type]'); - - for (var i = 0, node; (node = nodes[i]) !== undefined; i++) { - cbi_validate_field(node, node.getAttribute('data-optional') === 'true', - node.getAttribute('data-type')); - } - nodes = document.querySelectorAll('[data-choices]'); for (var i = 0, node; (node = nodes[i]) !== undefined; i++) { @@ -562,6 +612,21 @@ function cbi_init() { cbi_dynlist_init(node, choices[2], choices[3], options); } + nodes = document.querySelectorAll('[data-type]'); + + for (var i = 0, node; (node = nodes[i]) !== undefined; i++) { + cbi_validate_field(node, node.getAttribute('data-optional') === 'true', + node.getAttribute('data-type')); + } + + document.querySelectorAll('.cbi-dropdown').forEach(function(s) { + cbi_dropdown_init(s); + }); + + document.querySelectorAll('.cbi-tooltip:not(:empty)').forEach(function(s) { + s.parentNode.classList.add('cbi-tooltip-container'); + }); + cbi_d_update(); } @@ -604,9 +669,6 @@ function cbi_combobox(id, values, def, man, focus) { var dt = obj.getAttribute('cbi_datatype'); var op = obj.getAttribute('cbi_optional'); - if (dt) - cbi_validate_field(sel, op == 'true', dt); - if (!values[obj.value]) { if (obj.value == "") { var optdef = document.createElement("option"); @@ -641,6 +703,9 @@ function cbi_combobox(id, values, def, man, focus) { obj.style.display = "none"; + if (dt) + cbi_validate_field(sel, op == 'true', dt); + cbi_bind(sel, "change", function() { if (sel.selectedIndex == sel.options.length - 1) { obj.style.display = "inline"; @@ -683,7 +748,7 @@ function cbi_filebrowser(id, defpath) { browser.focus(); } -function cbi_browser_init(id, defpath) +function cbi_browser_init(id, resource, defpath) { function cbi_browser_btnclick(e) { cbi_filebrowser(id, defpath); @@ -694,7 +759,7 @@ function cbi_browser_init(id, defpath) var btn = document.createElement('img'); btn.className = 'cbi-image-button'; - btn.src = cbi_strings.path.resource + '/cbi/folder.gif'; + btn.src = (resource || cbi_strings.path.resource) + '/cbi/folder.gif'; field.parentNode.insertBefore(btn, field.nextSibling); cbi_bind(btn, 'click', cbi_browser_btnclick); @@ -761,7 +826,7 @@ function cbi_dynlist_init(parent, datatype, optional, choices) parent.appendChild(b); if (datatype == 'file') { - cbi_browser_init(t.id, parent.getAttribute('data-browser-path')); + cbi_browser_init(t.id, null, parent.getAttribute('data-browser-path')); } parent.appendChild(document.createElement('br')); @@ -1171,51 +1236,53 @@ function cbi_validate_field(cbid, optional, type) function cbi_row_swap(elem, up, store) { - var tr = elem.parentNode; - while (tr && tr.nodeName.toLowerCase() != 'tr') - tr = tr.parentNode; + var tr = findParent(elem.parentNode, '.cbi-section-table-row'); if (!tr) return false; - var table = tr.parentNode; - while (table && table.nodeName.toLowerCase() != 'table') - table = table.parentNode; + tr.classList.remove('flash'); - if (!table) - return false; - - var s = up ? 3 : 2; - var e = up ? table.rows.length : table.rows.length - 1; + if (up) { + var prev = tr.previousElementSibling; - for (var idx = s; idx < e; idx++) - { - if (table.rows[idx] == tr) - { - if (up) - tr.parentNode.insertBefore(table.rows[idx], table.rows[idx-1]); - else - tr.parentNode.insertBefore(table.rows[idx+1], table.rows[idx]); + if (prev && prev.classList.contains('cbi-section-table-row')) + tr.parentNode.insertBefore(tr, prev); + else + return; + } + else { + var next = tr.nextElementSibling ? tr.nextElementSibling.nextElementSibling : null; - break; - } + if (next && next.classList.contains('cbi-section-table-row')) + tr.parentNode.insertBefore(tr, next); + else if (!next) + tr.parentNode.appendChild(tr); + else + return; } var ids = [ ]; - for (idx = 2; idx < table.rows.length; idx++) - { - table.rows[idx].className = table.rows[idx].className.replace( - /cbi-rowstyle-[12]/, 'cbi-rowstyle-' + (1 + (idx % 2)) - ); - if (table.rows[idx].id && table.rows[idx].id.match(/-([^\-]+)$/) ) - ids.push(RegExp.$1); + for (var i = 0, n = 0; i < tr.parentNode.childNodes.length; i++) { + var node = tr.parentNode.childNodes[i]; + if (node.classList && node.classList.contains('cbi-section-table-row')) { + node.classList.remove('cbi-rowstyle-1'); + node.classList.remove('cbi-rowstyle-2'); + node.classList.add((n++ % 2) ? 'cbi-rowstyle-2' : 'cbi-rowstyle-1'); + + if (/-([^\-]+)$/.test(node.id)) + ids.push(RegExp.$1); + } } var input = document.getElementById(store); if (input) input.value = ids.join(' '); + window.scrollTo(0, tr.offsetTop); + window.setTimeout(function() { tr.classList.add('flash'); }, 1); + return false; } @@ -1239,58 +1306,6 @@ function cbi_tag_last(container) } } -String.prototype.serialize = function() -{ - var o = this; - switch(typeof(o)) - { - case 'object': - // null - if( o == null ) - { - return 'null'; - } - - // array - else if( o.length ) - { - var i, s = ''; - - for( var i = 0; i < o.length; i++ ) - s += (s ? ', ' : '') + String.serialize(o[i]); - - return '[ ' + s + ' ]'; - } - - // object - else - { - var k, s = ''; - - for( k in o ) - s += (s ? ', ' : '') + k + ': ' + String.serialize(o[k]); - - return '{ ' + s + ' }'; - } - - break; - - case 'string': - // complex string - if( o.match(/[^a-zA-Z0-9_,.: -]/) ) - return 'decodeURIComponent("' + encodeURIComponent(o) + '")'; - - // simple string - else - return '"' + o + '"'; - - break; - - default: - return o.toString(); - } -} - String.prototype.format = function() { if (!RegExp) @@ -1401,10 +1416,6 @@ String.prototype.format = function() subst = esc(param, quot_esc); break; - case 'j': - subst = String.serialize(param); - break; - case 't': var td = 0; var th = 0; @@ -1471,14 +1482,6 @@ String.prototype.nobr = function() return this.replace(/[\s\n]+/g, ' '); } -String.serialize = function() -{ - var a = [ ]; - for (var i = 1; i < arguments.length; i++) - a.push(arguments[i]); - return ''.serialize.apply(arguments[0], a); -} - String.format = function() { var a = [ ]; @@ -1494,3 +1497,640 @@ String.nobr = function() a.push(arguments[i]); return ''.nobr.apply(arguments[0], a); } + +if (window.NodeList && !NodeList.prototype.forEach) { + NodeList.prototype.forEach = function (callback, thisArg) { + thisArg = thisArg || window; + for (var i = 0; i < this.length; i++) { + callback.call(thisArg, this[i], i, this); + } + }; +} + + +var dummyElem, domParser; + +function isElem(e) +{ + return (typeof(e) === 'object' && e !== null && 'nodeType' in e); +} + +function toElem(s) +{ + var elem; + + try { + domParser = domParser || new DOMParser(); + elem = domParser.parseFromString(s, 'text/html').body.firstChild; + } + catch(e) {} + + if (!elem) { + try { + dummyElem = dummyElem || document.createElement('div'); + dummyElem.innerHTML = s; + elem = dummyElem.firstChild; + } + catch (e) {} + } + + return elem || null; +} + +function findParent(node, selector) +{ + while (node) + if (node.msMatchesSelector && node.msMatchesSelector(selector)) + return node; + else if (node.matches && node.matches(selector)) + return node; + else + node = node.parentNode; + + return null; +} + +function E() +{ + var html = arguments[0], + attr = (arguments[1] instanceof Object && !Array.isArray(arguments[1])) ? arguments[1] : null, + data = attr ? arguments[2] : arguments[1], + elem; + + if (isElem(html)) + elem = html; + else if (html.charCodeAt(0) === 60) + elem = toElem(html); + else + elem = document.createElement(html); + + if (!elem) + return null; + + if (attr) + for (var key in attr) + if (attr.hasOwnProperty(key) && attr[key] !== null && attr[key] !== undefined) + elem.setAttribute(key, attr[key]); + + if (typeof(data) === 'function') + data = data(elem); + + if (isElem(data)) { + elem.appendChild(data); + } + else if (Array.isArray(data)) { + for (var i = 0; i < data.length; i++) + if (isElem(data[i])) + elem.appendChild(data[i]); + else + elem.appendChild(document.createTextNode('' + data[i])); + } + else if (data !== null && data !== undefined) { + elem.innerHTML = '' + data; + } + + return elem; +} + +if (typeof(window.CustomEvent) !== 'function') { + function CustomEvent(event, params) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); + return evt; + } + + CustomEvent.prototype = window.Event.prototype; + window.CustomEvent = CustomEvent; +} + +CBIDropdown = { + openDropdown: function(sb) { + var st = window.getComputedStyle(sb, null), + ul = sb.querySelector('ul'), + li = ul.querySelectorAll('li'), + sel = ul.querySelector('[selected]'), + rect = sb.getBoundingClientRect(), + h = sb.clientHeight - parseFloat(st.paddingTop) - parseFloat(st.paddingBottom), + mh = this.dropdown_items * h, + eh = Math.min(mh, li.length * h); + + document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) { + s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {})); + }); + + ul.style.maxHeight = mh + 'px'; + sb.setAttribute('open', ''); + + ul.scrollTop = sel ? Math.max(sel.offsetTop - sel.offsetHeight, 0) : 0; + ul.querySelectorAll('[selected] input[type="checkbox"]').forEach(function(c) { + c.checked = true; + }); + + ul.style.top = ul.style.bottom = ''; + ul.style[((sb.getBoundingClientRect().top + eh) > window.innerHeight) ? 'bottom' : 'top'] = rect.height + 'px'; + ul.classList.add('dropdown'); + + var pv = ul.cloneNode(true); + pv.classList.remove('dropdown'); + pv.classList.add('preview'); + + sb.insertBefore(pv, ul.nextElementSibling); + + li.forEach(function(l) { + l.setAttribute('tabindex', 0); + }); + + sb.lastElementChild.setAttribute('tabindex', 0); + + this.setFocus(sb, sel || li[0], true); + }, + + closeDropdown: function(sb, no_focus) { + if (!sb.hasAttribute('open')) + return; + + var pv = sb.querySelector('ul.preview'), + ul = sb.querySelector('ul.dropdown'), + li = ul.querySelectorAll('li'); + + li.forEach(function(l) { l.removeAttribute('tabindex'); }); + sb.lastElementChild.removeAttribute('tabindex'); + + sb.removeChild(pv); + sb.removeAttribute('open'); + sb.style.width = sb.style.height = ''; + + ul.classList.remove('dropdown'); + + if (!no_focus) + this.setFocus(sb, sb); + + this.saveValues(sb, ul); + }, + + toggleItem: function(sb, li, force_state) { + if (li.hasAttribute('unselectable')) + return; + + if (this.multi) { + var cbox = li.querySelector('input[type="checkbox"]'), + items = li.parentNode.querySelectorAll('li'), + label = sb.querySelector('ul.preview'), + sel = li.parentNode.querySelectorAll('[selected]').length, + more = sb.querySelector('.more'), + ndisplay = this.display_items, + n = 0; + + if (li.hasAttribute('selected')) { + if (force_state !== true) { + if (sel > 1 || this.optional) { + li.removeAttribute('selected'); + cbox.checked = cbox.disabled = false; + sel--; + } + else { + cbox.disabled = true; + } + } + } + else { + if (force_state !== false) { + li.setAttribute('selected', ''); + cbox.checked = true; + cbox.disabled = false; + sel++; + } + } + + while (label.firstElementChild) + label.removeChild(label.firstElementChild); + + for (var i = 0; i < items.length; i++) { + items[i].removeAttribute('display'); + if (items[i].hasAttribute('selected')) { + if (ndisplay-- > 0) { + items[i].setAttribute('display', n++); + label.appendChild(items[i].cloneNode(true)); + } + var c = items[i].querySelector('input[type="checkbox"]'); + if (c) + c.disabled = (sel == 1 && !this.optional); + } + } + + if (ndisplay < 0) + sb.setAttribute('more', ''); + else + sb.removeAttribute('more'); + + if (ndisplay === this.display_items) + sb.setAttribute('empty', ''); + else + sb.removeAttribute('empty'); + + more.innerHTML = (ndisplay === this.display_items) ? this.placeholder : '···'; + } + else { + var sel = li.parentNode.querySelector('[selected]'); + if (sel) { + sel.removeAttribute('display'); + sel.removeAttribute('selected'); + } + + li.setAttribute('display', 0); + li.setAttribute('selected', ''); + + this.closeDropdown(sb, true); + } + + this.saveValues(sb, li.parentNode); + }, + + transformItem: function(sb, li) { + var cbox = E('form', {}, E('input', { type: 'checkbox', tabindex: -1, onclick: 'event.preventDefault()' })), + label = E('label'); + + while (li.firstChild) + label.appendChild(li.firstChild); + + li.appendChild(cbox); + li.appendChild(label); + }, + + saveValues: function(sb, ul) { + var sel = ul.querySelectorAll('[selected]'), + div = sb.lastElementChild; + + while (div.lastElementChild) + div.removeChild(div.lastElementChild); + + sel.forEach(function (s) { + div.appendChild(E('input', { + type: 'hidden', + name: s.hasAttribute('name') ? s.getAttribute('name') : (sb.getAttribute('name') || ''), + value: s.hasAttribute('value') ? s.getAttribute('value') : s.innerText + })); + }); + + cbi_d_update(); + }, + + setFocus: function(sb, elem, scroll) { + if (sb && sb.hasAttribute && sb.hasAttribute('locked-in')) + return; + + document.querySelectorAll('.focus').forEach(function(e) { + if (e.nodeName.toLowerCase() !== 'input') { + e.classList.remove('focus'); + e.blur(); + } + }); + + if (elem) { + elem.focus(); + elem.classList.add('focus'); + + if (scroll) + elem.parentNode.scrollTop = elem.offsetTop - elem.parentNode.offsetTop; + } + }, + + createItems: function(sb, value) { + var sbox = this, + val = (value || '').trim().split(/\s+/), + ul = sb.querySelector('ul'); + + if (!sbox.multi) + val.length = Math.min(val.length, 1); + + val.forEach(function(item) { + var new_item = null; + + ul.childNodes.forEach(function(li) { + if (li.getAttribute && li.getAttribute('value') === item) + new_item = li; + }); + + if (!new_item) { + var markup, + tpl = sb.querySelector(sbox.template); + + if (tpl) + markup = (tpl.textContent || tpl.innerHTML || tpl.firstChild.data).replace(/^<!--|-->$/, '').trim(); + else + markup = '<li value="{{value}}">{{value}}</li>'; + + new_item = E(markup.replace(/{{value}}/g, item)); + + if (sbox.multi) { + sbox.transformItem(sb, new_item); + } + else { + var old = ul.querySelector('li[created]'); + if (old) + ul.removeChild(old); + + new_item.setAttribute('created', ''); + } + + new_item = ul.insertBefore(new_item, ul.lastElementChild); + } + + sbox.toggleItem(sb, new_item, true); + sbox.setFocus(sb, new_item, true); + }); + }, + + closeAllDropdowns: function() { + document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) { + s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {})); + }); + } +}; + +function cbi_dropdown_init(sb) { + if (!(this instanceof cbi_dropdown_init)) + return new cbi_dropdown_init(sb); + + this.multi = sb.hasAttribute('multiple'); + this.optional = sb.hasAttribute('optional'); + this.placeholder = sb.getAttribute('placeholder') || '---'; + this.display_items = parseInt(sb.getAttribute('display-items') || 3); + this.dropdown_items = parseInt(sb.getAttribute('dropdown-items') || 5); + this.create = sb.getAttribute('item-create') || '.create-item-input'; + this.template = sb.getAttribute('item-template') || 'script[type="item-template"]'; + + var sbox = this, + ul = sb.querySelector('ul'), + items = ul.querySelectorAll('li'), + more = sb.appendChild(E('span', { class: 'more', tabindex: -1 }, '···')), + open = sb.appendChild(E('span', { class: 'open', tabindex: -1 }, '▾')), + canary = sb.appendChild(E('div')), + create = sb.querySelector(this.create), + ndisplay = this.display_items, + n = 0; + + if (this.multi) { + for (var i = 0; i < items.length; i++) { + sbox.transformItem(sb, items[i]); + + if (items[i].hasAttribute('selected') && ndisplay-- > 0) + items[i].setAttribute('display', n++); + } + } + else { + var sel = sb.querySelectorAll('[selected]'); + + sel.forEach(function(s) { + s.removeAttribute('selected'); + }); + + var s = sel[0] || items[0]; + if (s) { + s.setAttribute('selected', ''); + s.setAttribute('display', n++); + } + + ndisplay--; + + if (this.optional && !ul.querySelector('li[value=""]')) { + var placeholder = E('li', { placeholder: '' }, this.placeholder); + ul.firstChild ? ul.insertBefore(placeholder, ul.firstChild) : ul.appendChild(placeholder); + } + } + + sbox.saveValues(sb, ul); + + ul.setAttribute('tabindex', -1); + sb.setAttribute('tabindex', 0); + + if (ndisplay < 0) + sb.setAttribute('more', '') + else + sb.removeAttribute('more'); + + if (ndisplay === this.display_items) + sb.setAttribute('empty', '') + else + sb.removeAttribute('empty'); + + more.innerHTML = (ndisplay === this.display_items) ? this.placeholder : '···'; + + + sb.addEventListener('click', function(ev) { + if (!this.hasAttribute('open')) { + if (ev.target.nodeName.toLowerCase() !== 'input') + sbox.openDropdown(this); + } + else { + var li = findParent(ev.target, 'li'); + if (li && li.parentNode.classList.contains('dropdown')) + sbox.toggleItem(this, li); + } + + ev.preventDefault(); + ev.stopPropagation(); + }); + + sb.addEventListener('keydown', function(ev) { + if (ev.target.nodeName.toLowerCase() === 'input') + return; + + if (!this.hasAttribute('open')) { + switch (ev.keyCode) { + case 37: + case 38: + case 39: + case 40: + sbox.openDropdown(this); + ev.preventDefault(); + } + } + else + { + var active = findParent(document.activeElement, 'li'); + + switch (ev.keyCode) { + case 27: + sbox.closeDropdown(this); + break; + + case 13: + if (active) { + if (!active.hasAttribute('selected')) + sbox.toggleItem(this, active); + sbox.closeDropdown(this); + ev.preventDefault(); + } + break; + + case 32: + if (active) { + sbox.toggleItem(this, active); + ev.preventDefault(); + } + break; + + case 38: + if (active && active.previousElementSibling) { + sbox.setFocus(this, active.previousElementSibling); + ev.preventDefault(); + } + break; + + case 40: + if (active && active.nextElementSibling) { + sbox.setFocus(this, active.nextElementSibling); + ev.preventDefault(); + } + break; + } + } + }); + + sb.addEventListener('cbi-dropdown-close', function(ev) { + sbox.closeDropdown(this, true); + }); + + if ('ontouchstart' in window) { + sb.addEventListener('touchstart', function(ev) { ev.stopPropagation(); }); + window.addEventListener('touchstart', sbox.closeAllDropdowns); + } + else { + sb.addEventListener('mouseover', function(ev) { + if (!this.hasAttribute('open')) + return; + + var li = findParent(ev.target, 'li'); + if (li) { + if (li.parentNode.classList.contains('dropdown')) + sbox.setFocus(this, li); + + ev.stopPropagation(); + } + }); + + sb.addEventListener('focus', function(ev) { + document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) { + if (s !== this || this.hasAttribute('open')) + s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {})); + }); + }); + + canary.addEventListener('focus', function(ev) { + sbox.closeDropdown(this.parentNode); + }); + + window.addEventListener('mouseover', sbox.setFocus); + window.addEventListener('click', sbox.closeAllDropdowns); + } + + if (create) { + create.addEventListener('keydown', function(ev) { + switch (ev.keyCode) { + case 13: + sbox.createItems(sb, this.value); + ev.preventDefault(); + this.value = ''; + this.blur(); + break; + } + }); + + create.addEventListener('focus', function(ev) { + var cbox = findParent(this, 'li').querySelector('input[type="checkbox"]'); + if (cbox) cbox.checked = true; + sb.setAttribute('locked-in', ''); + }); + + create.addEventListener('blur', function(ev) { + var cbox = findParent(this, 'li').querySelector('input[type="checkbox"]'); + if (cbox) cbox.checked = false; + sb.removeAttribute('locked-in'); + }); + + var li = findParent(create, 'li'); + + li.setAttribute('unselectable', ''); + li.addEventListener('click', function(ev) { + this.querySelector(sbox.create).focus(); + }); + } +} + +cbi_dropdown_init.prototype = CBIDropdown; + +function cbi_update_table(table, data, placeholder) { + target = isElem(table) ? table : document.querySelector(table); + + if (!isElem(target)) + return; + + target.querySelectorAll('.tr.table-titles, .cbi-section-table-titles').forEach(function(thead) { + var titles = []; + + thead.querySelectorAll('.th').forEach(function(th) { + titles.push(th); + }); + + if (Array.isArray(data)) { + var n = 0, rows = target.querySelectorAll('.tr'); + + data.forEach(function(row) { + var trow = E('div', { 'class': 'tr' }); + + for (var i = 0; i < titles.length; i++) { + var text = (titles[i].innerText || '').trim(); + var td = trow.appendChild(E('div', { + 'class': titles[i].className, + 'data-title': (text !== '') ? text : null + }, row[i] || '')); + + td.classList.remove('th'); + td.classList.add('td'); + } + + trow.classList.add('cbi-rowstyle-%d'.format((n++ % 2) ? 2 : 1)); + + if (rows[n]) + target.replaceChild(trow, rows[n]); + else + target.appendChild(trow); + }); + + while (rows[++n]) + target.removeChild(rows[n]); + + if (placeholder && target.firstElementChild === target.lastElementChild) { + var trow = target.appendChild(E('div', { 'class': 'tr placeholder' })); + var td = trow.appendChild(E('div', { 'class': titles[0].className }, placeholder)); + + td.classList.remove('th'); + td.classList.add('td'); + } + } + else { + thead.parentNode.style.display = 'none'; + + thead.parentNode.querySelectorAll('.tr, .cbi-section-table-row').forEach(function(trow) { + if (trow !== thead) { + var n = 0; + trow.querySelectorAll('.th, .td').forEach(function(td) { + if (n < titles.length) { + var text = (titles[n++].innerText || '').trim(); + if (text !== '') + td.setAttribute('data-title', text); + } + }); + } + }); + + thead.parentNode.style.display = ''; + } + }); +} + +document.addEventListener('DOMContentLoaded', function() { + document.querySelectorAll('.table').forEach(cbi_update_table); +}); diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/alias.png b/modules/luci-base/htdocs/luci-static/resources/icons/alias.png Binary files differnew file mode 100644 index 0000000000..8772bbad55 --- /dev/null +++ b/modules/luci-base/htdocs/luci-static/resources/icons/alias.png diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/alias_disabled.png b/modules/luci-base/htdocs/luci-static/resources/icons/alias_disabled.png Binary files differnew file mode 100644 index 0000000000..38d0531e36 --- /dev/null +++ b/modules/luci-base/htdocs/luci-static/resources/icons/alias_disabled.png diff --git a/modules/luci-base/htdocs/luci-static/resources/xhr.js b/modules/luci-base/htdocs/luci-static/resources/xhr.js index 701c12ac19..62b525ebb0 100644 --- a/modules/luci-base/htdocs/luci-static/resources/xhr.js +++ b/modules/luci-base/htdocs/luci-static/resources/xhr.js @@ -39,10 +39,11 @@ XHR = function() this._xmlHttp.abort(); } - this.get = function(url,data,callback) + this.get = function(url,data,callback,timeout) { this.reinit(); + var ts = Date.now(); var xhr = this._xmlHttp; var code = this._encode(data); @@ -56,43 +57,49 @@ XHR = function() xhr.open('GET', url, true); + if (!isNaN(timeout)) + xhr.timeout = timeout; + xhr.onreadystatechange = function() { if (xhr.readyState == 4) { var json = null; if (xhr.getResponseHeader("Content-Type") == "application/json") { try { - json = eval('(' + xhr.responseText + ')'); + json = JSON.parse(xhr.responseText); } catch(e) { json = null; } } - callback(xhr, json); + callback(xhr, json, Date.now() - ts); } } xhr.send(null); } - this.post = function(url,data,callback) + this.post = function(url,data,callback,timeout) { this.reinit(); + var ts = Date.now(); var xhr = this._xmlHttp; var code = this._encode(data); xhr.onreadystatechange = function() { if (xhr.readyState == 4) - callback(xhr); + callback(xhr, null, Date.now() - ts); } xhr.open('POST', url, true); + + if (!isNaN(timeout)) + xhr.timeout = timeout; + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - xhr.setRequestHeader('Content-length', code.length); - xhr.setRequestHeader('Connection', 'close'); xhr.send(code); } @@ -170,7 +177,7 @@ XHR.get = function(url, data, callback) (new XHR()).get(url, data, callback); } -XHR.poll = function(interval, url, data, callback) +XHR.poll = function(interval, url, data, callback, post) { if (isNaN(interval) || interval < 1) interval = 5; @@ -183,22 +190,37 @@ XHR.poll = function(interval, url, data, callback) for (var i = 0, e = XHR._q[0]; i < XHR._q.length; e = XHR._q[++i]) { if (!(XHR._t % e.interval) && !e.xhr.busy()) - e.xhr.get(e.url, e.data, e.callback); + e.xhr[post ? 'post' : 'get'](e.url, e.data, e.callback, e.interval * 1000 * 5 - 5); } XHR._t++; }; } - XHR._q.push({ + var e = { interval: interval, callback: callback, url: url, data: data, xhr: new XHR() - }); + }; - XHR.run(); + XHR._q.push(e); + + return e; +} + +XHR.stop = function(e) +{ + for (var i = 0; XHR._q && XHR._q[i]; i++) { + if (XHR._q[i] === e) { + e.xhr.cancel(); + XHR._q.splice(i, 1); + return true; + } + } + + return false; } XHR.halt = function() @@ -239,3 +261,5 @@ XHR.running = function() { return !!(XHR._r && XHR._i); } + +document.addEventListener('DOMContentLoaded', XHR.run); |