diff options
16 files changed, 1378 insertions, 298 deletions
diff --git a/applications/luci-app-ddns/luasrc/view/ddns/system_status.htm b/applications/luci-app-ddns/luasrc/view/ddns/system_status.htm index 5bdcb03e7..7fcb882a1 100644 --- a/applications/luci-app-ddns/luasrc/view/ddns/system_status.htm +++ b/applications/luci-app-ddns/luasrc/view/ddns/system_status.htm @@ -1,4 +1,3 @@ - <!-- ++ BEGIN ++ Dynamic DNS ++ system_status.htm ++ --> <script type="text/javascript">//<![CDATA[ // helper to move status data to the relevant @@ -10,8 +9,8 @@ if ( !(tbl) ) { return; } // clear all rows - while (tbl.rows.length > 1) - tbl.deleteRow(1); + while (tbl.firstElementChild !== tbl.lastElementChild) + tbl.removeChild(tbl.lastElementChild); // variable for Modulo-Division use to set cbi-rowstyle-? (0 or 1) var i = -1; @@ -21,22 +20,22 @@ if (data[0].enabled == 0) { var txt = '<strong><font color="red"><%:DDNS Autostart disabled%></font>' ; var url = '<a href="' + data[0].url_up + '"><%:enable here%></a></strong>' ; - var tr = tbl.insertRow(-1); - tr.className = 'cbi-section-table-row cbi-rowstyle-' + (((j + i) % 2) + 1); - var td = tr.insertCell(-1); - td.colSpan = 2 ; - td.innerHTML = txt + " - " + url - tr.insertCell(-1).colSpan = 3 ; + tbl.appendChild(E('<div class="tr cbi-section-table-row cbi-rowstyle-%d">'.format(((j + i) % 2) + 1), [ + E('<div class="td">', [ txt," - ", url ]) + ])); i++ ; } + var configuration; + var next_update; + var lookup; + var registered_ip; + var network; + for( j = 1; j < data.length; j++ ) { - var tr = tbl.insertRow(-1); - tr.className = 'cbi-section-table-row cbi-rowstyle-' + (((j + i) % 2) + 1) ; - // configuration - tr.insertCell(-1).innerHTML = '<strong>' + data[j].section + '</strong>' ; + configuration = data[j].section; // pid // data[j].pid ignored here @@ -47,66 +46,74 @@ // next update switch (data[j].datenext) { case "_empty_": - tr.insertCell(-1).innerHTML = '<em><%:Unknown error%></em>' ; + next_update = '<em><%:Unknown error%></em>'; break; case "_stopped_": - tr.insertCell(-1).innerHTML = '<em><%:Stopped%></em>' ; + next_update = '<em><%:Stopped%></em>'; break; case "_disabled_": - tr.insertCell(-1).innerHTML = '<em><%:Disabled%></em>' ; + next_update = '<em><%:Disabled%></em>'; break; case "_noupdate_": - tr.insertCell(-1).innerHTML = '<em><%:Update error%></em>' ; + next_update = '<em><%:Update error%></em>'; break; case "_runonce_": - tr.insertCell(-1).innerHTML = '<em><%:Run once%></em>' ; + next_update = '<em><%:Run once%></em>'; break; case "_verify_": - tr.insertCell(-1).innerHTML = '<em><%:Verify%></em>'; + next_update = '<em><%:Verify%></em>'; break; default: - tr.insertCell(-1).innerHTML = data[j].datenext ; + next_update = data[j].datenext; break; } // lookup if (data[j].lookup == "_nolookup_") - tr.insertCell(-1).innerHTML = '<em><%:config error%></em>'; + lookup = '<em><%:config error%></em>'; else - tr.insertCell(-1).innerHTML = data[j].lookup; + lookup = data[j].lookup; // registered IP switch (data[j].reg_ip) { case "_nolookup_": - tr.insertCell(-1).innerHTML = '<em><%:Config error%></em>'; + registered_ip = '<em><%:config error%></em>'; break; case "_nodata_": - tr.insertCell(-1).innerHTML = '<em><%:No data%></em>'; + registered_ip = '<em><%:No data%></em>'; break; case "_noipv6_": - tr.insertCell(-1).innerHTML = '<em><%:IPv6 not supported%></em>'; + registered_ip = '<em><%:IPv6 not supported%></em>'; break; default: - tr.insertCell(-1).innerHTML = data[j].reg_ip; + registered_ip = data[j].reg_ip; break; } // monitored interface if (data[j].iface == "_nonet_") - tr.insertCell(-1).innerHTML = '<em><%:Config error%></em>'; + network = '<em><%:Config error%></em>'; else - tr.insertCell(-1).innerHTML = data[j].iface; + network = data[j].iface; + + tbl.appendChild(E('<div class="tr cbi-section-table-row cbi-rowstyle-%d">'.format(((j + i) % 2) + 1), [ + E('<div class="td">', [ E('<strong>', configuration) ]), + E('<div class="td">', next_update), + E('<div class="td">', lookup), + E('<div class="td">', registered_ip), + E('<div class="td">', network) + ])); } - if (tbl.rows.length == 1 || (data[0].enabled == 0 && tbl.rows.length == 2) ) { + if (tbl.firstElementChild === tbl.lastElementChild || (data[0].enabled == 0 && tbl.childNodes.length == 2) ) { var br = '<br />'; - if (tbl.rows.length > 1) + var msg = '<%:There is no service configured.%>'; + if (tbl.firstElementChild !== tbl.lastElementChild) br = ''; - var tr = tbl.insertRow(-1); - tr.className = "cbi-section-table-row"; - var td = tr.insertCell(-1); - td.colSpan = 5; - td.innerHTML = '<em>' + br + '<%:There is no service configured.%></em>' ; + msg = br + msg; + tbl.appendChild(E('<div class="tr cbi-section-table-row">', [ + E('<div class="td">', [ E('<em>', msg) ]) + ])); } } @@ -128,17 +135,17 @@ <fieldset class="cbi-section" id="ddns_status_section"> <legend><a href="<%=url([[admin]], [[services]], [[ddns]])%>"><%:Dynamic DNS%></a></legend> - <table class="cbi-section-table" id="ddns_status_table"> - <tr class="cbi-section-table-titles"> - <th class="cbi-section-table-cell"><%:Configuration%></th> - <th class="cbi-section-table-cell"><%:Next Update%></th> - <th class="cbi-section-table-cell"><%:Lookup Hostname%></th> - <th class="cbi-section-table-cell"><%:Registered IP%></th> - <th class="cbi-section-table-cell"><%:Network%></th> - </tr> - <tr class="cbi-section-table-row"> - <td colspan="5"><em><br /><%:Collecting data...%></em></td> - </tr> - </table> + <div class="table cbi-section-table" id="ddns_status_table"> + <div class="tr cbi-section-table-titles"> + <div class="th cbi-section-table-cell"><%:Configuration%></div> + <div class="th cbi-section-table-cell"><%:Next Update%></div> + <div class="th cbi-section-table-cell"><%:Lookup Hostname%></div> + <div class="th cbi-section-table-cell"><%:Registered IP%></div> + <div class="th cbi-section-table-cell"><%:Network%></div> + </div> + <div class="tr cbi-section-table-row"> + <div class="td"><em><br /><%:Collecting data...%></em></div> + </div> + </div> </fieldset> <!-- ++ END ++ Dynamic DNS ++ system_status.htm ++ --> diff --git a/applications/luci-app-firewall/luasrc/model/cbi/firewall/forward-details.lua b/applications/luci-app-firewall/luasrc/model/cbi/firewall/forward-details.lua index 17a49483d..39895c6f0 100644 --- a/applications/luci-app-firewall/luasrc/model/cbi/firewall/forward-details.lua +++ b/applications/luci-app-firewall/luasrc/model/cbi/firewall/forward-details.lua @@ -55,6 +55,7 @@ o = s:option(Value, "src", translate("Source zone")) o.nocreate = true o.default = "wan" o.template = "cbi/firewall_zonelist" +o.rmempty = false o = s:option(DynamicList, "src_mac", diff --git a/applications/luci-app-firewall/luasrc/model/cbi/firewall/rule-details.lua b/applications/luci-app-firewall/luasrc/model/cbi/firewall/rule-details.lua index 1c838888f..fffa64dad 100644 --- a/applications/luci-app-firewall/luasrc/model/cbi/firewall/rule-details.lua +++ b/applications/luci-app-firewall/luasrc/model/cbi/firewall/rule-details.lua @@ -255,7 +255,7 @@ else o = s:option(Value, "src", translate("Source zone")) o.nocreate = true o.allowany = true - o.default = "wan" + o.allowlocal = "src" o.template = "cbi/firewall_zonelist" @@ -282,11 +282,21 @@ else o.placeholder = translate("any") - o = s:option(Value, "dest", translate("Destination zone")) + o = s:option(Value, "dest_local", translate("Output zone")) + o.nocreate = true + o.allowany = true + o.rmempty = false + o.template = "cbi/firewall_zonelist" + o.alias = "dest" + o:depends("src", "") + + o = s:option(Value, "dest_remote", translate("Destination zone")) o.nocreate = true o.allowany = true o.allowlocal = true o.template = "cbi/firewall_zonelist" + o.alias = "dest" + o:depends({["src"] = "", ["!reverse"] = true}) o = s:option(Value, "dest_ip", translate("Destination address")) @@ -316,9 +326,9 @@ else translate("Passes additional arguments to iptables. Use with care!")) end -o = s:option(MultiValue, "weekdays", translate("Week Days")) -o.oneline = true -o.widget = "checkbox" +o = s:option(DropDown, "weekdays", translate("Week Days")) +o.multiple = true +o.display = 5 o:value("Sun", translate("Sunday")) o:value("Mon", translate("Monday")) o:value("Tue", translate("Tuesday")) @@ -327,9 +337,9 @@ o:value("Thu", translate("Thursday")) o:value("Fri", translate("Friday")) o:value("Sat", translate("Saturday")) -o = s:option(MultiValue, "monthdays", translate("Month Days")) -o.oneline = true -o.widget = "checkbox" +o = s:option(DropDown, "monthdays", translate("Month Days")) +o.multiple = true +o.display = 15 for i = 1,31 do o:value(translate(i)) end diff --git a/applications/luci-app-firewall/luasrc/model/cbi/firewall/zone-details.lua b/applications/luci-app-firewall/luasrc/model/cbi/firewall/zone-details.lua index a42c1499f..e168c3c60 100644 --- a/applications/luci-app-firewall/luasrc/model/cbi/firewall/zone-details.lua +++ b/applications/luci-app-firewall/luasrc/model/cbi/firewall/zone-details.lua @@ -101,9 +101,12 @@ end function net.write(self, section, value) zone:clear_networks() - local n - for n in ut.imatch(value) do - zone:add_network(n) + local net + for net in ut.imatch(value) do + local n = nw:get_network(net) or nw:add_network(net, { proto = "none" }) + if n then + zone:add_network(n:name()) + end end end diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi.js b/modules/luci-base/htdocs/luci-static/resources/cbi.js index 0a1961916..a7f999d87 100644 --- a/modules/luci-base/htdocs/luci-static/resources/cbi.js +++ b/modules/luci-base/htdocs/luci-static/resources/cbi.js @@ -465,31 +465,16 @@ function cbi_d_add(field, dep, index) { } function cbi_d_checkvalue(target, ref) { - var t = document.getElementById(target); - var value; + var value = null, + query = 'input[id="'+target+'"], input[name="'+target+'"], ' + + 'select[id="'+target+'"], select[name="'+target+'"]'; - if (!t) { - var tl = document.getElementsByName(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) { @@ -634,6 +619,10 @@ function cbi_init() { node.getAttribute('data-type')); } + document.querySelectorAll('.cbi-dropdown').forEach(function(s) { + cbi_dropdown_init(s); + }); + cbi_d_update(); } @@ -1574,3 +1563,484 @@ function E() 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', {})); + }); + }, + + findParent: function(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 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 = sbox.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 = sbox.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 = sbox.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 = sbox.findParent(this, 'li').querySelector('input[type="checkbox"]'); + if (cbox) cbox.checked = true; + sb.setAttribute('locked-in', ''); + }); + + create.addEventListener('blur', function(ev) { + var cbox = sbox.findParent(this, 'li').querySelector('input[type="checkbox"]'); + if (cbox) cbox.checked = false; + sb.removeAttribute('locked-in'); + }); + + var li = sbox.findParent(create, 'li'); + + li.setAttribute('unselectable', ''); + li.addEventListener('click', function(ev) { + this.querySelector(sbox.create).focus(); + }); + } +} + +cbi_dropdown_init.prototype = CBIDropdown; diff --git a/modules/luci-base/luasrc/cbi.lua b/modules/luci-base/luasrc/cbi.lua index 4800d2aa7..d275c5b27 100644 --- a/modules/luci-base/luasrc/cbi.lua +++ b/modules/luci-base/luasrc/cbi.lua @@ -1417,6 +1417,12 @@ function AbstractValue.parse(self, section, novld) self:add_error(section, "invalid", val_err) end + if self.alias then + self.section.aliased = self.section.aliased or {} + self.section.aliased[section] = self.section.aliased[section] or {} + self.section.aliased[section][self.alias] = true + end + if fvalue and (self.forcewrite or not (fvalue == cvalue)) then if self:write(section, fvalue) then -- Push events @@ -1426,10 +1432,16 @@ function AbstractValue.parse(self, section, novld) end else -- Unset the UCI or error if self.rmempty or self.optional then - if self:remove(section) then - -- Push events - self.section.changed = true - --luci.util.append(self.map.events, self.events) + if not self.alias or + not self.section.aliased or + not self.section.aliased[section] or + not self.section.aliased[section][self.alias] + then + if self:remove(section) then + -- Push events + self.section.changed = true + --luci.util.append(self.map.events, self.events) + end end elseif cvalue ~= fvalue and not novld then -- trigger validator with nil value to get custom user error msg. @@ -1455,7 +1467,7 @@ function AbstractValue.cfgvalue(self, section) if self.tag_error[section] then value = self:formvalue(section) else - value = self.map:get(section, self.option) + value = self.map:get(section, self.alias or self.option) end if not value then @@ -1496,12 +1508,12 @@ AbstractValue.transform = AbstractValue.validate -- Write to UCI function AbstractValue.write(self, section, value) - return self.map:set(section, self.option, value) + return self.map:set(section, self.alias or self.option, value) end -- Remove from UCI function AbstractValue.remove(self, section) - return self.map:del(section, self.option) + return self.map:del(section, self.alias or self.option) end @@ -1834,6 +1846,15 @@ function DynamicList.formvalue(self, section) end +DropDown = class(MultiValue) + +function DropDown.__init__(self, ...) + ListValue.__init__(self, ...) + self.template = "cbi/dropdown" + self.delimiter = " " +end + + --[[ TextValue - A multi-line value rows: Rows diff --git a/modules/luci-base/luasrc/view/cbi/dropdown.htm b/modules/luci-base/luasrc/view/cbi/dropdown.htm new file mode 100644 index 000000000..bdf724837 --- /dev/null +++ b/modules/luci-base/luasrc/view/cbi/dropdown.htm @@ -0,0 +1,42 @@ +<%+cbi/valueheader%> + +<%- + local selected = { } + + if self.multiple then + local val + for val in luci.util.imatch(self:cfgvalue(section)) do + selected[val] = true + end + else + selected[self:cfgvalue(section)] = true + end + + if not next(selected) and self.default then + selected[self.default] = true + end +-%> + +<div class="cbi-dropdown"<%= + attr("name", cbid) .. + attr("display-items", self.display or self.size or 3) .. + attr("dropdown-items", self.dropdown or self.display or self.size or 5) .. + attr("placeholder", self.placeholder or translate("-- please select --")) .. + ifattr(self.multiple, "multiple", "multiple") .. + ifattr(self.optional or self.rmempty, "optional", "optional") +%>> + <ul> + <% local i, key; for i, key in pairs(self.keylist) do %> + <li<%= + attr("data-index", i) .. + attr("data-depends", self:deplist2json(section, self.deplist[i])) .. + attr("value", key) .. + ifattr(selected[key], "selected", "selected") + %>> + <%=pcdata(self.vallist[i])%> + </li> + <% end %> + </ul> +</div> + +<%+cbi/valuefooter%> diff --git a/modules/luci-base/luasrc/view/cbi/firewall_zonelist.htm b/modules/luci-base/luasrc/view/cbi/firewall_zonelist.htm index b4260707e..3a108020b 100644 --- a/modules/luci-base/luasrc/view/cbi/firewall_zonelist.htm +++ b/modules/luci-base/luasrc/view/cbi/firewall_zonelist.htm @@ -24,26 +24,42 @@ end -%> -<span> - <ul style="margin:0; list-style-type:none; text-align:left"> +<div class="cbi-dropdown" dropdown-items="5" placeholder="<%:-- please select -- %>"<%= + attr("name", cbid) .. + ifattr(self.widget == "checkbox", "multiple", "multiple") .. + ifattr(self.rmempty or self.optional, "optional", "optional") +%>> + <script type="item-template"><!-- + <li value="{{value}}"> + <span class="zonebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)"> + <strong>{{value}}:</strong><em>(<%:create%>)</em> + </span> + </li> + --></script> + <ul> <% if self.allowlocal then %> - <li style="padding:0.5em"> - <input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_empty") .. attr("name", cbid) .. attr("value", "") .. ifattr(checked[""], "checked", "checked")%> />   - <label<%=attr("for", cbid .. "_empty")%>></label> - <label<%=attr("for", cbid .. "_empty")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge"> + <li value=""<%=ifattr(checked[""], "selected", "selected")%>> + <span style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge"> <strong><%:Device%></strong> - <% if self.allowany and self.allowlocal then %>(<%:input%>)<% end %> - </label> + <% if self.allowany and self.allowlocal then -%> + (<%= self.alias ~= "dest" + and translate("output") or translate("input") %>) + <%- end %> + </span> + </li> + <% elseif self.widget ~= "checkbox" and (self.rmempty or self.optional) then %> + <li value=""<%=ifattr(checked[""], "selected", "selected")%>> + <span class="zonebadge"> + <em><%:unspecified%></em> + </span> </li> <% end %> <% if self.allowany then %> - <li style="padding:0.5em"> - <input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_any") .. attr("name", cbid) .. attr("value", "*") .. ifattr(checked["*"], "checked", "checked")%> />   - <label<%=attr("for", cbid .. "_any")%>></label> - <label<%=attr("for", cbid .. "_any")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge"> + <li value="*"<%=ifattr(checked["*"], "selected", "selected")%>> + <span style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge"> <strong><%:Any zone%></strong> <% if self.allowany and self.allowlocal then %>(<%:forward%>)<% end %> - </label> + </span> </li> <% end %> <% @@ -51,45 +67,42 @@ if zone:name() ~= self.exclude then selected = selected or (value == zone:name()) %> - <li style="padding:0.5em"> - <input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "." .. zone:name()) .. attr("name", cbid) .. attr("value", zone:name()) .. ifattr(checked[zone:name()], "checked", "checked")%> />   - <label<%=attr("for", cbid .. "." .. zone:name())%>></label> - <label<%=attr("for", cbid .. "." .. zone:name())%> style="background-color:<%=zone:get_color()%>" class="zonebadge"> + <li<%=attr("value", zone:name()) .. ifattr(checked[zone:name()], "selected", "selected")%>> + <span style="background-color:<%=zone:get_color()%>" class="zonebadge"> <strong><%=zone:name()%>:</strong> - <% + <%- local zempty = true for _, net in ipairs(zone:get_networks()) do net = nwm:get_network(net) if net then zempty = false - %> + -%> <span class="ifacebadge<% if net:name() == self.network then %> ifacebadge-active<% end %>"><%=net:name()%>: - <% + <%- local nempty = true for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do nempty = false %> - <img<%=attr("title", iface:get_i18n())%> style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> + <img<%=attr("title", iface:get_i18n())%> src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> <% end %> - <% if nempty then %><em><%:(empty)%></em><% end %> + <% if nempty then %><em><%:(empty)%></em><% end -%> </span> - <% end end %> - <% if zempty then %><em><%:(empty)%></em><% end %> - </label> + <%- end end -%> + <%- if zempty then %><em><%:(empty)%></em><% end -%> + </span> </li> <% end end %> <% if self.widget ~= "checkbox" and not self.nocreate then %> - <li style="padding:0.5em"> - <input class="cbi-input-radio" data-update="click change" type="radio"<%=attr("id", cbid .. "_new") .. attr("name", cbid) .. attr("value", "-") .. ifattr(not selected, "checked", "checked")%> />   - <label<%=attr("for", cbid .. "_new")%>></label> - <div onclick="document.getElementById('<%=cbid%>_new').checked=true" class="zonebadge" style="background-color:<%=fwm.zone.get_color()%>"> - <em><%:unspecified -or- create:%> </em> - <input type="text"<%=attr("name", cbid .. ".newzone") .. ifattr(not selected, "value", luci.http.formvalue(cbid .. ".newzone") or self.default)%> onfocus="document.getElementById('<%=cbid%>_new').checked=true" /> - </div> + <li value="-"> + <span class="zonebadge"> + <em><%:create%>:</em> + <input type="password" style="display:none" /> + <input class="create-item-input" type="text" /> + </span> </li> <% end %> </ul> -</span> +</div> <%+cbi/valuefooter%> diff --git a/modules/luci-base/luasrc/view/cbi/network_ifacelist.htm b/modules/luci-base/luasrc/view/cbi/network_ifacelist.htm index 62dbde7dd..abfa33e1e 100644 --- a/modules/luci-base/luasrc/view/cbi/network_ifacelist.htm +++ b/modules/luci-base/luasrc/view/cbi/network_ifacelist.htm @@ -19,7 +19,9 @@ if value then for value in utl.imatch(value) do - checked[value] = true + for value in utl.imatch(value) do + checked[value] = true + end end else local n = self.network and net:get_network(self.network) @@ -33,57 +35,51 @@ -%> <input type="hidden" name="<%=cbeid%>" value="1" /> -<ul style="margin:0; list-style-type:none"> - <% for _, iface in ipairs(ifaces) do - local link = iface:adminlink() - if (not self.nobridges or not iface:is_bridge()) and - (not self.noinactive or iface:is_up()) and - iface:name() ~= self.exclude - then %> - <li> - <input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%= - attr("type", self.widget or "radio") .. - attr("id", cbid .. "." .. iface:name()) .. - attr("name", cbid) .. attr("value", iface:name()) .. - ifattr(checked[iface:name()], "checked", "checked") - %> /> - <%- if not self.widget or self.widget == "checkbox" or self.widget == "radio" then -%> - <label<%=attr("for", cbid .. "." .. iface:name())%>></label> - <%- end -%> -   - <label<%=attr("for", cbid .. "." .. iface:name())%>> - <% if link then -%><a href="<%=link%>"><% end -%> - <img<%=attr("title", iface:get_i18n())%> style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> - <% if link then -%></a><% end -%> - <%=pcdata(iface:get_i18n())%> - <% local ns = iface:get_networks(); if #ns > 0 then %>( - <%- local i, n; for i, n in ipairs(ns) do -%> - <%-= (i>1) and ', ' -%> - <a href="<%=n:adminlink()%>"><%=n:name()%></a> - <%- end -%> - )<% end %> - </label> - </li> - <% end end %> - <% if not self.nocreate then %> - <li> - <input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%= - attr("type", self.widget or "radio") .. - attr("id", cbid .. "_custom") .. - attr("name", cbid) .. - attr("value", " ") - %> /> - <%- if not self.widget or self.widget == "checkbox" or self.widget == "radio" then -%> - <label<%=attr("for", cbid .. "_custom")%>></label> - <%- end -%> -   - <label<%=attr("for", cbid .. "_custom")%>> - <img title="<%:Custom Interface%>" style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/ethernet_disabled.png" /> - <%:Custom Interface%>: - </label> - <input type="text" style="width:50px" onfocus="document.getElementById('<%=cbid%>_custom').checked=true" onblur="var x=document.getElementById('<%=cbid%>_custom'); x.value=this.value; x.checked=true" /> - </li> - <% end %> -</ul> + +<div class="cbi-dropdown" display-items="5" placeholder="<%:-- please select -- %>"<%= + attr("name", cbid) .. + ifattr(self.widget == "checkbox", "multiple", "multiple") .. + ifattr(self.widget == "checkbox", "optional", "optional") +%>> + <script type="item-template"><!-- + <li value="{{value}}"> + <img title="<%:Custom Interface%>: "{{value}}"" src="<%=resource%>/icons/ethernet_disabled.png" /> + <span class="hide-open">{{value}}</span> + <span class="hide-close"><%:Custom Interface%>: "{{value}}"</span> + </li> + --></script> + <ul> + <% for _, iface in ipairs(ifaces) do + if (not self.nobridges or not iface:is_bridge()) and + (not self.noinactive or iface:is_up()) and + iface:name() ~= self.exclude + then %> + <li<%= + attr("value", iface:name()) .. + ifattr(checked[iface:name()], "selected", "selected") + %>> + <img<%=attr("title", iface:get_i18n())%> src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> + <span class="hide-open"><%=pcdata(iface:name())%></span> + <span class="hide-close"> + <%=pcdata(iface:get_i18n())%> + <% local ns = iface:get_networks(); if #ns > 0 then %>( + <%- local i, n; for i, n in ipairs(ns) do -%> + <%-= (i>1) and ', ' -%> + <a href="<%=n:adminlink()%>"><%=n:name()%></a> + <%- end -%> + )<% end %> + </span> + </li> + <% end end %> + <% if not self.nocreate then %> + <li value=""> + <img title="<%:Custom Interface%>" src="<%=resource%>/icons/ethernet_disabled.png" /> + <span><%:Custom Interface%>:</span> + <input type="password" style="display:none" /> + <input class="create-item-input" type="text" /> + </li> + <% end %> + </ul> +</div> <%+cbi/valuefooter%> diff --git a/modules/luci-base/luasrc/view/cbi/network_netlist.htm b/modules/luci-base/luasrc/view/cbi/network_netlist.htm index 8bf1a70a2..ba6ebb843 100644 --- a/modules/luci-base/luasrc/view/cbi/network_netlist.htm +++ b/modules/luci-base/luasrc/view/cbi/network_netlist.htm @@ -20,66 +20,62 @@ end -%> -<ul style="margin:0; list-style-type:none; text-align:left"> - <% for _, net in ipairs(networks) do - if (net:name() ~= "loopback") and - (net:name() ~= self.exclude) and - (not self.novirtual or not net:is_virtual()) - then %> - <li style="padding:0.25em 0"> - <input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%= - attr("type", self.widget or "radio") .. - attr("id", cbid .. "." .. net:name()) .. - attr("name", cbid) .. attr("value", net:name()) .. - ifattr(checked[net:name()], "checked", "checked") - %> />   - <label<%=attr("for", cbid .. "." .. net:name())%>> +<div class="cbi-dropdown" display-items="5" placeholder="<%:-- please select -- %>"<%= + attr("name", cbid) .. + ifattr(self.widget == "checkbox", "multiple", "multiple") .. + ifattr(self.widget == "checkbox", "optional", "optional") +%>> + <script type="item-template"><!-- + <li value="{{value}}"> + <span class="ifacebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)"> + {{value}}: <em>(<%:create%>)</em> + </span> + </li> + --></script> + <ul> + <% if self.widget ~= "checkbox" then %> + <li value=""<%= ifattr(not value, "selected", "selected") %>> + <em><%:unspecified%></em> + </li> + <% end %> + + <% for _, net in ipairs(networks) do + if (net:name() ~= "loopback") and + (net:name() ~= self.exclude) and + (not self.novirtual or not net:is_virtual()) + then %> + <li<%= attr("value", net:name()) .. ifattr(checked[net:name()], "selected", "selected") %>> <span class="ifacebadge"><%=net:name()%>: <% local empty = true for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do if not iface:is_bridge() then empty = false - %> + -%> <img<%=attr("title", iface:get_i18n())%> style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> - <% end end %> - <% if empty then %><em><%:(no interfaces attached)%></em><% end %> + <%- end end %> + <% if empty then %> + <em class="hide-close"><%:(no interfaces attached)%></em> + <em class="hide-open">-</em> + <% end %> </span> - </label> - </li> - <% end end %> + </li> + <% end end %> - <% if not self.nocreate then %> - <li style="padding:0.25em 0"> - <input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_new") .. attr("name", cbid) .. attr("value", "-") .. ifattr(not value and self.widget ~= "checkbox", "checked", "checked")%> />   - <%- if not self.widget or self.widget == "checkbox" or self.widget == "radio" then -%> - <label<%=attr("for", cbid .. "_new")%>></label> - <%- end -%> - <div style="padding:0.5em; display:inline"> - <label<%=attr("for", cbid .. "_new")%>><em> + <% if not self.nocreate then %> + <li value="-"<%= ifattr(not value and self.widget ~= "checkbox", "selected", "selected") %>> + <em> <%- if self.widget == "checkbox" then -%> <%:create:%> <%- else -%> <%:unspecified -or- create:%> - <%- end -%> </em></label> + <%- end -%> + </em> <input style="display:none" type="password" /> - <input style="width:6em" type="text"<%=attr("name", cbid .. ".newnet")%> onfocus="document.getElementById('<%=cbid%>_new').checked=true" /> - </div> - </li> - <% elseif self.widget ~= "checkbox" and self.unspecified then %> - <li style="padding:0.25em 0"> - <input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%= - attr("type", self.widget or "radio") .. - attr("id", cbid .. "_uns") .. - attr("name", cbid) .. - attr("value", "") .. - ifattr(not value or #value == 0, "checked", "checked") - %> />   - <div style="padding:0.5em; display:inline"> - <label<%=attr("for", cbid .. "_uns")%>><em><%:unspecified%></em></label> - </div> - </li> - <% end %> -</ul> + <input class="create-item-input" type="text" /> + </li> + <% end %> + </ul> +</div> <%+cbi/valuefooter%> diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/ifaces.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/ifaces.lua index 38e5de7b3..5c630bb5c 100644 --- a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/ifaces.lua +++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/ifaces.lua @@ -351,7 +351,6 @@ if has_firewall then fwzone.template = "cbi/firewall_zonelist" fwzone.network = arg[1] - fwzone.rmempty = false function fwzone.cfgvalue(self, section) self.iface = section @@ -360,22 +359,16 @@ if has_firewall then end function fwzone.write(self, section, value) - local zone = fw:get_zone(value) - - if not zone and value == '-' then - value = m:formvalue(self:cbid(section) .. ".newzone") - if value and #value > 0 then - zone = fw:add_zone(value) - else - fw:del_network(section) - end - end - + local zone = fw:get_zone(value) or fw:add_zone(value) if zone then fw:del_network(section) zone:add_network(section) end end + + function fwzone.remove(self, section) + fw:del_network(section) + end end diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua index cacaa2595..d51a72aba 100644 --- a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua +++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua @@ -390,22 +390,16 @@ network.novirtual = true function network.write(self, section, value) local i = nw:get_interface(section) if i then - if value == '-' then - value = m:formvalue(self:cbid(section) .. ".newnet") - if value and #value > 0 then - local n = nw:add_network(value, {proto="none"}) - if n then n:add_interface(i) end - else - local n = i:get_network() - if n then n:del_interface(i) end - end - else - local v - for _, v in ipairs(i:get_networks()) do - v:del_interface(i) - end - for v in ut.imatch(value) do - local n = nw:get_network(v) + local _, net, old, new = nil, nil, {}, {} + + for _, net in ipairs(i:get_networks()) do + old[net:name()] = true + end + + for net in ut.imatch(value) do + new[net] = true + if not old[net] then + local n = nw:get_network(net) or nw:add_network(net, { proto = "none" }) if n then if not n:is_empty() then n:set("type", "bridge") @@ -414,6 +408,15 @@ function network.write(self, section, value) end end end + + for net, _ in pairs(old) do + if not new[net] then + local n = nw:get_network(net) + if n then + n:del_interface(i) + end + end + end end end diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi_add.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi_add.lua index 8277deb2f..e8a305882 100644 --- a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi_add.lua +++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi_add.lua @@ -94,14 +94,9 @@ function newnet.parse(self, section) local net, zone if has_firewall then - local zval = fwzone:formvalue(section) - zone = fw:get_zone(zval) - - if not zone and zval == '-' then - zval = m:formvalue(fwzone:cbid(section) .. ".newzone") - if zval and #zval > 0 then - zone = fw:add_zone(zval) - end + local value = fwzone:formvalue(section) + if value and #value > 0 then + zone = fw:get_zone(value) or fw:add_zone(value) end end diff --git a/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css b/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css index 5aff09148..4a40f6d68 100644 --- a/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css +++ b/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css @@ -430,7 +430,6 @@ form .clearfix, form .cbi-value { margin-bottom: 18px; zoom: 1; - overflow: hidden; } form .clearfix:before, form .clearfix:after, @@ -477,6 +476,7 @@ input[type=checkbox], input[type=radio] { input, textarea, select, +.cbi-dropdown, .uneditable-input { display: inline-block; width: 210px; @@ -487,6 +487,13 @@ select, color: #808080; border: 1px solid #ccc; border-radius: 3px; + box-sizing: border-box; +} + +.cbi-dropdown { + min-width: 210px; + max-width: 400px; + width: auto; } select { @@ -1332,6 +1339,159 @@ footer { background-image: url('../resources/cbi/download.gif'), linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); } +.cbi-dropdown { + border: 1px solid #ccc; + border-radius: 3px; + display: inline-flex; + padding: 0; + cursor: pointer; + height: auto; + background: linear-gradient(#fff 0%, #e9e8e6 100%); + position: relative; + color: #404040; +} + +.cbi-dropdown:focus { + outline: 2px solid #4b6e9b; +} + +.cbi-dropdown > ul { + margin: 0 !important; + padding: 0; + list-style: none; + overflow-x: hidden; + overflow-y: auto; + display: flex; + width: 100%; +} + +.cbi-dropdown > ul.preview { + display: none; +} + +.cbi-dropdown > .open, +.cbi-dropdown > .more { + flex-grow: 0; + flex-shrink: 0; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + line-height: 2em; + padding: 0 .25em; +} + +.cbi-dropdown > .more, +.cbi-dropdown > ul > li[placeholder] { + color: #777; + font-weight: bold; + text-shadow: 1px 1px 0px #fff; + display: none; +} + +.cbi-dropdown > ul > li { + display: none; + padding: .25em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 1; + flex-grow: 1; + align-items: center; + align-self: center; + color: #404040; + min-height: 20px; +} + +.cbi-dropdown > ul > li .hide-open { display: initial; } +.cbi-dropdown > ul > li .hide-close { display: none; } + +.cbi-dropdown > ul > li[display]:not([display="0"]) { + border-left: 1px solid #ccc; +} + +.cbi-dropdown[empty] > ul { + max-width: 1px; +} + +.cbi-dropdown > ul > li > form { + display: none; + margin: 0; + padding: 0; + pointer-events: none; +} + +.cbi-dropdown > ul > li img { + vertical-align: middle; + margin-right: .25em; +} + +.cbi-dropdown > ul > li > form > input[type="checkbox"] { + margin: 0; +} + +.cbi-dropdown > ul > li input[type="text"] { + height: 20px; +} + +.cbi-dropdown[open] { + position: relative; +} + +.cbi-dropdown[open] > ul.dropdown { + display: block; + background: #f6f6f5; + border: 1px solid #918e8c; + box-shadow: 0 0 4px #918e8c; + position: absolute; + z-index: 1000; + max-width: none; + min-width: 100%; + width: auto; +} + +.cbi-dropdown > ul > li[display], +.cbi-dropdown[open] > ul.preview, +.cbi-dropdown[open] > ul.dropdown > li, +.cbi-dropdown[multiple] > ul > li > label, +.cbi-dropdown[multiple][open] > ul.dropdown > li, +.cbi-dropdown[multiple][more] > .more, +.cbi-dropdown[multiple][empty] > .more { + flex-grow: 1; + display: flex; +} + +.cbi-dropdown[empty] > ul > li, +.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder], +.cbi-dropdown[multiple][open] > ul.dropdown > li > form { + display: block; +} + +.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; } +.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: initial; } + +.cbi-dropdown[open] > ul.dropdown > li { + border-bottom: 1px solid #ccc; +} + +.cbi-dropdown[open] > ul.dropdown > li[selected] { + background: #b0d0f0; +} + +.cbi-dropdown[open] > ul.dropdown > li.focus { + background: linear-gradient(90deg, #a3c2e8 0%, #84aad9 100%); +} + +.cbi-dropdown[open] > ul.dropdown > li:last-child { + margin-bottom: 0; + border-bottom: none; +} + +.cbi-dropdown[disabled] { + pointer-events: none; + opacity: .6; +} + .btn.active, .btn:active { box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); } @@ -1423,7 +1583,7 @@ button.btn::-moz-focus-inner, input[type=submit].btn::-moz-focus-inner { opacity: 0.4; } -.alert-message, .errorbox { +.alert-message { position: relative; padding: 7px 15px; margin-bottom: 18px; @@ -1646,7 +1806,6 @@ table table td, background-color: #FFFFFF; border: 1px solid #CCCCCC; padding: 2px; - margin-left: 2px; background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); border-radius: 4px; @@ -1712,7 +1871,7 @@ table table td, .zonebadge > em, .zonebadge > strong { - margin: 5px; + margin: 0 2px; display: inline-block; } @@ -1720,6 +1879,10 @@ table table td, width: 6em; } +.zonebadge > .ifacebadge { + margin-left: 2px; +} + .zonebadge-empty { border: 1px dashed #AAAAAA; color: #AAAAAA; diff --git a/themes/luci-theme-material/htdocs/luci-static/material/css/style.css b/themes/luci-theme-material/htdocs/luci-static/material/css/style.css index a3b22ccf8..a9585cf40 100755 --- a/themes/luci-theme-material/htdocs/luci-static/material/css/style.css +++ b/themes/luci-theme-material/htdocs/luci-static/material/css/style.css @@ -107,7 +107,8 @@ select { } select, -input { +input, +.cbi-dropdown { background-color: transparent; color: rgba(0, 0, 0, .87); border: none; @@ -157,7 +158,7 @@ header { transition: box-shadow .2s; float: left; position: fixed; - z-index: 101; + z-index: 2000; } footer { @@ -452,11 +453,7 @@ fieldset { font-style: normal; line-height: 1; font-family: inherit; - min-width: inherit; - overflow-x: auto; - overflow-y: hidden; - border-radius: 0; background-color: #FFF; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12); @@ -626,6 +623,7 @@ td > table > tbody > tr > td, box-shadow: none; } +.btn + .btn, form.inline + form.inline, .cbi-button + .cbi-button { margin-left: 0.6rem; @@ -804,6 +802,172 @@ form.inline + form.inline, font-size: small; } + +.cbi-dropdown { + display: inline-flex; + cursor: pointer; + position: relative; + padding: 0; + height: auto; +} + +.cbi-dropdown:focus { + outline: 2px solid #4b6e9b; +} + +.cbi-dropdown > ul { + margin: 0 !important; + padding: 0; + list-style: none; + overflow-x: hidden; + overflow-y: auto; + display: flex; + width: 100%; +} + +.cbi-dropdown > ul.preview { + display: none; +} + +.cbi-dropdown > .open { + border: 2px outset #eee; + flex-basis: 15px; + background: #eee; +} + +.cbi-dropdown > .open, +.cbi-dropdown > .more { + flex-grow: 0; + flex-shrink: 0; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + line-height: 2em; + padding: 0 .25em; +} + +.cbi-dropdown > .more, +.cbi-dropdown > ul > li[placeholder] { + color: #777; + font-weight: bold; + text-shadow: 1px 1px 0px #fff; + display: none; +} + +.cbi-dropdown > ul > li { + display: none; + padding: .25em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 1; + flex-grow: 1; + align-items: center; + align-self: center; + min-height: 20px; +} + +.cbi-dropdown > ul > li .hide-open { display: initial; } +.cbi-dropdown > ul > li .hide-close { display: none; } + +.cbi-dropdown > ul > li[display]:not([display="0"]) { + border-left: 1px solid #ccc; +} + +.cbi-dropdown[empty] > ul { + max-width: 1px; +} + +.cbi-dropdown > ul > li > form { + display: none; + margin: 0; + padding: 0; + pointer-events: none; +} + +.cbi-dropdown > ul > li img { + vertical-align: middle; + margin-right: .25em; +} + +.cbi-dropdown > ul > li > form > input[type="checkbox"] { + margin: 0; + height: auto; +} + +.cbi-dropdown > ul > li input[type="text"] { + height: 20px; +} + +.cbi-dropdown[open] { + position: relative; +} + +.cbi-dropdown[open] > ul.dropdown { + display: block; + background: #f6f6f5; + border: 1px solid #918e8c; + box-shadow: 0 0 4px #918e8c; + position: absolute; + z-index: 1000; + max-width: none; + min-width: 100%; + width: auto; +} + +.cbi-dropdown > ul > li[display], +.cbi-dropdown[open] > ul.preview, +.cbi-dropdown[open] > ul.dropdown > li, +.cbi-dropdown[multiple] > ul > li > label, +.cbi-dropdown[multiple][open] > ul.dropdown > li, +.cbi-dropdown[multiple][more] > .more, +.cbi-dropdown[multiple][empty] > .more { + flex-grow: 1; + display: flex; + align-items: center; +} + +.cbi-dropdown[empty] > ul > li, +.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder], +.cbi-dropdown[multiple][open] > ul.dropdown > li > form { + display: block; +} + +.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; } +.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: initial; } + +.cbi-dropdown[open] > ul.dropdown > li { + border-bottom: 1px solid #ccc; +} + +.cbi-dropdown[open] > ul.dropdown > li[selected] { + background: #b0d0f0; +} + +.cbi-dropdown[open] > ul.dropdown > li.focus { + background: linear-gradient(90deg, #a3c2e8 0%, #84aad9 100%); +} + +.cbi-dropdown[open] > ul.dropdown > li:last-child { + margin-bottom: 0; + border-bottom: none; +} + +.cbi-dropdown[disabled] { + pointer-events: none; + opacity: .6; +} + +.cbi-dropdown .zonebadge { + width: 100%; +} + +.cbi-dropdown[open] .zonebadge { + width: auto; +} + + /* luci */ .hidden { @@ -839,17 +1003,20 @@ form.inline + form.inline, } /* select */ -.cbi-value-field .cbi-input-select { +.cbi-value-field .cbi-dropdown { min-width: 15rem; } +.cbi-value-field .cbi-input-select { + width: 15rem; +} + .ifacebadge { display: inline-flex; border-bottom: 1px solid #CCCCCC; padding: 0.5rem 1rem; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + background: #fff; } td > .ifacebadge, @@ -858,12 +1025,17 @@ td > .ifacebadge, font-size: 0.9rem; } +.ifacebadge > em, .ifacebadge > img { - float: right; - margin: 0 0.3rem; + display: inline-block; + margin: 0 .2rem; align-self: start; } +.ifacebadge > img + img { + margin: 0 .2rem 0 0; +} + .network-status-table { display: flex; flex-wrap: wrap; @@ -1038,8 +1210,8 @@ td > .ifacebadge, } .zonebadge > .ifacebadge { - padding: 0.2rem 1rem; - margin: 0.3rem; + padding: .2rem .3rem; + margin: 0.1rem 0.2rem; border: 1px solid #6C6C6C; } @@ -1049,6 +1221,12 @@ td > .ifacebadge, margin-top: 0.3rem; } +.zonebadge > em, +.zonebadge > strong { + margin: 0 0.2rem; + display: inline-block; +} + .cbi-value-field .cbi-input-checkbox, .cbi-value-field .cbi-input-radio { margin-top: 0.5rem; @@ -1073,10 +1251,14 @@ td > .ifacebadge, margin-top: -0.5rem; } -.cbi-section-table-row > .cbi-value-field .cbi-input-select { +.cbi-section-table-row > .cbi-value-field .cbi-dropdown { min-width: 7rem; } +.cbi-section-table-row > .cbi-value-field .cbi-input-select { + width: 7rem; +} + .cbi-section-create > .cbi-button-add { margin: 0.5rem; } @@ -1607,8 +1789,6 @@ body.lang_pl.node-main-login .cbi-value-title { line-height: 1; font-family: inherit; min-width: inherit; - overflow-x: auto; - overflow-y: hidden; border-radius: 0; background-color: #FFF; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12); @@ -1627,33 +1807,36 @@ body.lang_pl.node-main-login .cbi-value-title { @media screen and (min-width: 992px) { .cbi-value input[type="password"], - .cbi-value input[type="text"] { - min-width: 20rem; + .cbi-value input[type="text"], + .cbi-value-field .cbi-input-select { + width: 20rem; } - .cbi-value-field .cbi-input-select { + .cbi-value-field .cbi-dropdown { min-width: 20rem; } } @media screen and (min-width: 1280px) { .cbi-value input[type="password"], - .cbi-value input[type="text"] { - min-width: 22rem; + .cbi-value input[type="text"], + .cbi-value-field .cbi-input-select { + width: 22rem; } - .cbi-value-field .cbi-input-select { + .cbi-value-field .cbi-dropdown { min-width: 22rem; } } @media screen and (min-width: 1600px) { .cbi-value input[type="password"], - .cbi-value input[type="text"] { - min-width: 25rem; + .cbi-value input[type="text"], + .cbi-value-field .cbi-input-select { + width: 25rem; } - .cbi-value-field .cbi-input-select { + .cbi-value-field .cbi-dropdown { min-width: 25rem; } } diff --git a/themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css b/themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css index 8b359d475..1d5ac8ab2 100644 --- a/themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css +++ b/themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css @@ -27,6 +27,7 @@ body { * { margin: 0; padding: 0; + box-sizing: border-box; } .table { display: table; } @@ -456,13 +457,20 @@ select:hover { color: #000000; } +input[type=text], +input[type=password] { + padding: 1px 3px; +} + select, input[type=text], input[type=password] { + font-size: inherit; width: 20em; } .td select, +.td .cbi-dropdown, .td input[type=text], .td input[type=password] { width: 99%; @@ -773,6 +781,7 @@ div.cbi-section-create { vertical-align: top; } +div.cbi-section-create .btn, div.cbi-section-create .cbi-button { margin: 0.25em; } @@ -844,13 +853,6 @@ div.cbi-section-remove { vertical-align: bottom; } -div.cbi-error { - font-size: 95%; - font-weight: bold; - color: #ff0000; - background-color: #ffffff; -} - .td.cbi-value-error { border-color: red; } @@ -926,6 +928,169 @@ div.cbi-tab-descr { padding: 0.5em 0.5em 0.5em 2em; } + +.cbi-dropdown { + border: 1px inset #000; + display: inline-flex; + cursor: pointer; + background: #eee; + position: relative; + padding: 0; + color: #000; + min-width: 20em; +} + +.cbi-dropdown:hover { + background: #fff; +} + +.cbi-dropdown:focus { + outline: 2px solid #4b6e9b; +} + +.cbi-dropdown > ul { + margin: 0 !important; + padding: 0; + list-style: none; + overflow-x: hidden; + overflow-y: auto; + display: flex; + width: 100%; +} + +.cbi-dropdown > ul.preview { + display: none; +} + +.cbi-dropdown > .open { + border: 2px outset #eee; + flex-basis: 15px; +} + +.cbi-dropdown > .open, +.cbi-dropdown > .more { + flex-grow: 0; + flex-shrink: 0; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + line-height: 2em; + padding: 0 .25em; +} + +.cbi-dropdown > .more, +.cbi-dropdown > ul > li[placeholder] { + color: #777; + font-weight: bold; + text-shadow: 1px 1px 0px #fff; + display: none; +} + +.cbi-dropdown > ul > li { + display: none; + padding: .25em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 1; + flex-grow: 1; + align-items: center; + align-self: center; + min-height: 20px; +} + +.cbi-dropdown > ul > li .hide-open { display: initial; } +.cbi-dropdown > ul > li .hide-close { display: none; } + +.cbi-dropdown > ul > li[display]:not([display="0"]) { + border-left: 1px solid #ccc; +} + +.cbi-dropdown[empty] > ul { + max-width: 1px; +} + +.cbi-dropdown > ul > li > form { + display: none; + margin: 0; + padding: 0; + pointer-events: none; +} + +.cbi-dropdown > ul > li img { + vertical-align: middle; + margin-right: .25em; +} + +.cbi-dropdown > ul > li > form > input[type="checkbox"] { + margin: 0; +} + +.cbi-dropdown > ul > li input[type="text"] { + height: 20px; +} + +.cbi-dropdown[open] { + position: relative; +} + +.cbi-dropdown[open] > ul.dropdown { + display: block; + background: #f6f6f5; + border: 1px solid #918e8c; + box-shadow: 0 0 4px #918e8c; + position: absolute; + z-index: 1000; + max-width: none; + min-width: 100%; + width: auto; +} + +.cbi-dropdown > ul > li[display], +.cbi-dropdown[open] > ul.preview, +.cbi-dropdown[open] > ul.dropdown > li, +.cbi-dropdown[multiple] > ul > li > label, +.cbi-dropdown[multiple][open] > ul.dropdown > li, +.cbi-dropdown[multiple][more] > .more, +.cbi-dropdown[multiple][empty] > .more { + flex-grow: 1; + display: flex; + align-items: center; +} + +.cbi-dropdown[empty] > ul > li, +.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder], +.cbi-dropdown[multiple][open] > ul.dropdown > li > form { + display: block; +} + +.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; } +.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: initial; } + +.cbi-dropdown[open] > ul.dropdown > li { + border-bottom: 1px solid #ccc; +} + +.cbi-dropdown[open] > ul.dropdown > li[selected] { + background: #b0d0f0; +} + +.cbi-dropdown[open] > ul.dropdown > li.focus { + background: linear-gradient(90deg, #a3c2e8 0%, #84aad9 100%); +} + +.cbi-dropdown[open] > ul.dropdown > li:last-child { + margin-bottom: 0; + border-bottom: none; +} + +.cbi-dropdown[disabled] { + pointer-events: none; + opacity: .6; +} + + .left { text-align: left !important; } @@ -985,9 +1150,16 @@ div.cbi-tab-descr { background: #90c0e0; } -.ifacebadge > img { +.ifacebadge, .zonebadge { + align-items: center; +} + +.ifacebadge > img, +.ifacebadge > em { margin-right: 5px; align-self: start; + display: inline-block; + height: 16px; } .ifacebadge-active { @@ -1021,7 +1193,7 @@ div.cbi-tab-descr { .network-status-table .ifacebox-body { display: flex; flex-direction: column; - height: 100%; + flex: 1 0; } .network-status-table .ifacebox-body > span { @@ -1047,12 +1219,12 @@ div.cbi-tab-descr { display: inline-block; white-space: nowrap; cursor: pointer; + border-radius: 3px; } -.zonebadge em, -.zonebadge strong { +.zonebadge > em, +.zonebadge > strong { margin: 3px; - display: inline-block; } .zonebadge input { @@ -1060,6 +1232,18 @@ div.cbi-tab-descr { height: 1.5em; } +.zonebadge .ifacebadge, +.cbi-dropdown .ifacebadge { + margin: 0 .125em; +} + +.zonebadge .ifacebadge img, +.zonebadge .ifacebadge em, +.cbi-dropdown .ifacebadge img, +.cbi-dropdown .ifacebadge em { + margin: 0 1px; +} + .zonebadge-empty { border: 1px dashed #AAAAAA; color: #AAAAAA; |