1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter-feeds.git synced 2025-03-09 15:40:03 +00:00

Update to latest luci-base and luci-mod-admin-full

This commit is contained in:
Ycarus 2018-06-08 10:51:00 +02:00
parent 613042b0a0
commit b218a47995
17 changed files with 2882 additions and 221 deletions

View file

@ -465,31 +465,16 @@ function cbi_d_add(field, dep, index) {
} }
function cbi_d_checkvalue(target, ref) { function cbi_d_checkvalue(target, ref) {
var t = document.getElementById(target); var value = null,
var value; query = 'input[id="'+target+'"], input[name="'+target+'"], ' +
'select[id="'+target+'"], select[name="'+target+'"]';
if (!t) { document.querySelectorAll(query).forEach(function(i) {
var tl = document.getElementsByName(target); if (value === null && ((i.type !== 'radio' && i.type !== 'checkbox') || i.checked === true))
value = i.value;
});
if( tl.length > 0 && (tl[0].type == 'radio' || tl[0].type == 'checkbox')) return (((value !== null) ? value : "") == ref);
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 : "";
}
}
return (value == ref)
} }
function cbi_d_check(deps) { function cbi_d_check(deps) {
@ -634,6 +619,10 @@ function cbi_init() {
node.getAttribute('data-type')); node.getAttribute('data-type'));
} }
document.querySelectorAll('.cbi-dropdown').forEach(function(s) {
cbi_dropdown_init(s);
});
cbi_d_update(); cbi_d_update();
} }
@ -1574,3 +1563,484 @@ function E()
return elem; 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;

View file

@ -1417,6 +1417,12 @@ function AbstractValue.parse(self, section, novld)
self:add_error(section, "invalid", val_err) self:add_error(section, "invalid", val_err)
end 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 fvalue and (self.forcewrite or not (fvalue == cvalue)) then
if self:write(section, fvalue) then if self:write(section, fvalue) then
-- Push events -- Push events
@ -1426,10 +1432,16 @@ function AbstractValue.parse(self, section, novld)
end end
else -- Unset the UCI or error else -- Unset the UCI or error
if self.rmempty or self.optional then if self.rmempty or self.optional then
if self:remove(section) then if not self.alias or
-- Push events not self.section.aliased or
self.section.changed = true not self.section.aliased[section] or
--luci.util.append(self.map.events, self.events) 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 end
elseif cvalue ~= fvalue and not novld then elseif cvalue ~= fvalue and not novld then
-- trigger validator with nil value to get custom user error msg. -- 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 if self.tag_error[section] then
value = self:formvalue(section) value = self:formvalue(section)
else else
value = self.map:get(section, self.option) value = self.map:get(section, self.alias or self.option)
end end
if not value then if not value then
@ -1496,12 +1508,12 @@ AbstractValue.transform = AbstractValue.validate
-- Write to UCI -- Write to UCI
function AbstractValue.write(self, section, value) 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 end
-- Remove from UCI -- Remove from UCI
function AbstractValue.remove(self, section) function AbstractValue.remove(self, section)
return self.map:del(section, self.option) return self.map:del(section, self.alias or self.option)
end end
@ -1834,6 +1846,15 @@ function DynamicList.formvalue(self, section)
end end
DropDown = class(MultiValue)
function DropDown.__init__(self, ...)
ListValue.__init__(self, ...)
self.template = "cbi/dropdown"
self.delimiter = " "
end
--[[ --[[
TextValue - A multi-line value TextValue - A multi-line value
rows: Rows rows: Rows

View file

@ -8,7 +8,7 @@ local table = require "table"
local setmetatable, rawget, rawset = setmetatable, rawget, rawset local setmetatable, rawget, rawset = setmetatable, rawget, rawset
local require, getmetatable, assert = require, getmetatable, assert local require, getmetatable, assert = require, getmetatable, assert
local error, pairs, ipairs = error, pairs, ipairs local error, pairs, ipairs, select = error, pairs, ipairs, select
local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
-- The typical workflow for UCI is: Get a cursor instance from the -- The typical workflow for UCI is: Get a cursor instance from the
@ -106,7 +106,7 @@ function changes(self, config)
local _, change local _, change
for _, change in ipairs(changes) do for _, change in ipairs(changes) do
local operation, section, option, value = unpack(change) local operation, section, option, value = unpack(change)
if option and value and operation ~= "add" then if option and operation ~= "add" then
res[package][section] = res[package][section] or { } res[package][section] = res[package][section] or { }
if operation == "list-add" then if operation == "list-add" then
@ -373,15 +373,15 @@ function add(self, config, stype)
return self:section(config, stype) return self:section(config, stype)
end end
function set(self, config, section, option, value) function set(self, config, section, option, ...)
if value == nil then if select('#', ...) == 0 then
local sname, err = self:section(config, option, section) local sname, err = self:section(config, option, section)
return (not not sname), err return (not not sname), err
else else
local _, err = call("set", { local _, err = call("set", {
config = config, config = config,
section = section, section = section,
values = { [option] = value } values = { [option] = select(1, ...) }
}) })
return (err == nil), ERRSTR[err] return (err == nil), ERRSTR[err]
end end

View file

@ -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%>

View file

@ -24,26 +24,42 @@
end end
-%> -%>
<span> <div class="cbi-dropdown" dropdown-items="5" placeholder="<%:-- please select -- %>"<%=
<ul style="margin:0; list-style-type:none; text-align:left"> 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 %> <% if self.allowlocal then %>
<li style="padding:0.5em"> <li value=""<%=ifattr(checked[""], "selected", "selected")%>>
<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")%> /> &#160; <span style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
<label<%=attr("for", cbid .. "_empty")%>></label>
<label<%=attr("for", cbid .. "_empty")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
<strong><%:Device%></strong> <strong><%:Device%></strong>
<% if self.allowany and self.allowlocal then %>(<%:input%>)<% end %> <% if self.allowany and self.allowlocal then -%>
</label> (<%= 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> </li>
<% end %> <% end %>
<% if self.allowany then %> <% if self.allowany then %>
<li style="padding:0.5em"> <li value="*"<%=ifattr(checked["*"], "selected", "selected")%>>
<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")%> /> &#160; <span style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
<label<%=attr("for", cbid .. "_any")%>></label>
<label<%=attr("for", cbid .. "_any")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
<strong><%:Any zone%></strong> <strong><%:Any zone%></strong>
<% if self.allowany and self.allowlocal then %>(<%:forward%>)<% end %> <% if self.allowany and self.allowlocal then %>(<%:forward%>)<% end %>
</label> </span>
</li> </li>
<% end %> <% end %>
<% <%
@ -51,45 +67,42 @@
if zone:name() ~= self.exclude then if zone:name() ~= self.exclude then
selected = selected or (value == zone:name()) selected = selected or (value == zone:name())
%> %>
<li style="padding:0.5em"> <li<%=attr("value", zone:name()) .. ifattr(checked[zone:name()], "selected", "selected")%>>
<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")%> /> &#160; <span style="background-color:<%=zone:get_color()%>" class="zonebadge">
<label<%=attr("for", cbid .. "." .. zone:name())%>></label>
<label<%=attr("for", cbid .. "." .. zone:name())%> style="background-color:<%=zone:get_color()%>" class="zonebadge">
<strong><%=zone:name()%>:</strong> <strong><%=zone:name()%>:</strong>
<% <%-
local zempty = true local zempty = true
for _, net in ipairs(zone:get_networks()) do for _, net in ipairs(zone:get_networks()) do
net = nwm:get_network(net) net = nwm:get_network(net)
if net then if net then
zempty = false zempty = false
%> -%>
<span class="ifacebadge<% if net:name() == self.network then %> ifacebadge-active<% end %>"><%=net:name()%>: <span class="ifacebadge<% if net:name() == self.network then %> ifacebadge-active<% end %>"><%=net:name()%>:
<% <%-
local nempty = true local nempty = true
for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do
nempty = false 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 %> <% end %>
<% if nempty then %><em><%:(empty)%></em><% end %> <% if nempty then %><em><%:(empty)%></em><% end -%>
</span> </span>
<% end end %> <%- end end -%>
<% if zempty then %><em><%:(empty)%></em><% end %> <%- if zempty then %><em><%:(empty)%></em><% end -%>
</label> </span>
</li> </li>
<% end end %> <% end end %>
<% if self.widget ~= "checkbox" and not self.nocreate then %> <% if self.widget ~= "checkbox" and not self.nocreate then %>
<li style="padding:0.5em"> <li value="-">
<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")%> /> &#160; <span class="zonebadge">
<label<%=attr("for", cbid .. "_new")%>></label> <em><%:create%>:</em>
<div onclick="document.getElementById('<%=cbid%>_new').checked=true" class="zonebadge" style="background-color:<%=fwm.zone.get_color()%>"> <input type="password" style="display:none" />
<em><%:unspecified -or- create:%>&#160;</em> <input class="create-item-input" type="text" />
<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" /> </span>
</div>
</li> </li>
<% end %> <% end %>
</ul> </ul>
</span> </div>
<%+cbi/valuefooter%> <%+cbi/valuefooter%>

View file

@ -19,7 +19,9 @@
if value then if value then
for value in utl.imatch(value) do for value in utl.imatch(value) do
checked[value] = true for value in utl.imatch(value) do
checked[value] = true
end
end end
else else
local n = self.network and net:get_network(self.network) local n = self.network and net:get_network(self.network)
@ -33,57 +35,51 @@
-%> -%>
<input type="hidden" name="<%=cbeid%>" value="1" /> <input type="hidden" name="<%=cbeid%>" value="1" />
<ul style="margin:0; list-style-type:none">
<% for _, iface in ipairs(ifaces) do <div class="cbi-dropdown" display-items="5" placeholder="<%:-- please select -- %>"<%=
local link = iface:adminlink() attr("name", cbid) ..
if (not self.nobridges or not iface:is_bridge()) and ifattr(self.widget == "checkbox", "multiple", "multiple") ..
(not self.noinactive or iface:is_up()) and ifattr(self.widget == "checkbox", "optional", "optional")
iface:name() ~= self.exclude %>>
then %> <script type="item-template"><!--
<li> <li value="{{value}}">
<input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%= <img title="<%:Custom Interface%>: &quot;{{value}}&quot;" src="<%=resource%>/icons/ethernet_disabled.png" />
attr("type", self.widget or "radio") .. <span class="hide-open">{{value}}</span>
attr("id", cbid .. "." .. iface:name()) .. <span class="hide-close"><%:Custom Interface%>: "{{value}}"</span>
attr("name", cbid) .. attr("value", iface:name()) .. </li>
ifattr(checked[iface:name()], "checked", "checked") --></script>
%> /> <ul>
<%- if not self.widget or self.widget == "checkbox" or self.widget == "radio" then -%> <% for _, iface in ipairs(ifaces) do
<label<%=attr("for", cbid .. "." .. iface:name())%>></label> if (not self.nobridges or not iface:is_bridge()) and
<%- end -%> (not self.noinactive or iface:is_up()) and
&#160; iface:name() ~= self.exclude
<label<%=attr("for", cbid .. "." .. iface:name())%>> then %>
<% if link then -%><a href="<%=link%>"><% end -%> <li<%=
<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" /> attr("value", iface:name()) ..
<% if link then -%></a><% end -%> ifattr(checked[iface:name()], "selected", "selected")
<%=pcdata(iface:get_i18n())%> %>>
<% local ns = iface:get_networks(); if #ns > 0 then %>( <img<%=attr("title", iface:get_i18n())%> src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
<%- local i, n; for i, n in ipairs(ns) do -%> <span class="hide-open"><%=pcdata(iface:name())%></span>
<%-= (i>1) and ', ' -%> <span class="hide-close">
<a href="<%=n:adminlink()%>"><%=n:name()%></a> <%=pcdata(iface:get_i18n())%>
<%- end -%> <% local ns = iface:get_networks(); if #ns > 0 then %>(
)<% end %> <%- local i, n; for i, n in ipairs(ns) do -%>
</label> <%-= (i>1) and ', ' -%>
</li> <a href="<%=n:adminlink()%>"><%=n:name()%></a>
<% end end %> <%- end -%>
<% if not self.nocreate then %> )<% end %>
<li> </span>
<input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%= </li>
attr("type", self.widget or "radio") .. <% end end %>
attr("id", cbid .. "_custom") .. <% if not self.nocreate then %>
attr("name", cbid) .. <li value="">
attr("value", " ") <img title="<%:Custom Interface%>" src="<%=resource%>/icons/ethernet_disabled.png" />
%> /> <span><%:Custom Interface%>:</span>
<%- if not self.widget or self.widget == "checkbox" or self.widget == "radio" then -%> <input type="password" style="display:none" />
<label<%=attr("for", cbid .. "_custom")%>></label> <input class="create-item-input" type="text" />
<%- end -%> </li>
&#160; <% end %>
<label<%=attr("for", cbid .. "_custom")%>> </ul>
<img title="<%:Custom Interface%>" style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/ethernet_disabled.png" /> </div>
<%: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>
<%+cbi/valuefooter%> <%+cbi/valuefooter%>

View file

@ -20,66 +20,62 @@
end end
-%> -%>
<ul style="margin:0; list-style-type:none; text-align:left"> <div class="cbi-dropdown" display-items="5" placeholder="<%:-- please select -- %>"<%=
<% for _, net in ipairs(networks) do attr("name", cbid) ..
if (net:name() ~= "loopback") and ifattr(self.widget == "checkbox", "multiple", "multiple") ..
(net:name() ~= self.exclude) and ifattr(self.widget == "checkbox", "optional", "optional")
(not self.novirtual or not net:is_virtual()) %>>
then %> <script type="item-template"><!--
<li style="padding:0.25em 0"> <li value="{{value}}">
<input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%= <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)">
attr("type", self.widget or "radio") .. {{value}}: <em>(<%:create%>)</em>
attr("id", cbid .. "." .. net:name()) .. </span>
attr("name", cbid) .. attr("value", net:name()) .. </li>
ifattr(checked[net:name()], "checked", "checked") --></script>
%> /> &#160; <ul>
<label<%=attr("for", cbid .. "." .. net:name())%>> <% 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()%>: <span class="ifacebadge"><%=net:name()%>:
<% <%
local empty = true local empty = true
for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do
if not iface:is_bridge() then if not iface:is_bridge() then
empty = false 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" /> <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 %> <%- end end %>
<% if empty then %><em><%:(no interfaces attached)%></em><% end %> <% if empty then %>
<em class="hide-close"><%:(no interfaces attached)%></em>
<em class="hide-open">-</em>
<% end %>
</span> </span>
</label> </li>
</li> <% end end %>
<% end end %>
<% if not self.nocreate then %> <% if not self.nocreate then %>
<li style="padding:0.25em 0"> <li value="-"<%= ifattr(not value and self.widget ~= "checkbox", "selected", "selected") %>>
<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")%> /> &#160; <em>
<%- 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 self.widget == "checkbox" then -%> <%- if self.widget == "checkbox" then -%>
<%:create:%> <%:create:%>
<%- else -%> <%- else -%>
<%:unspecified -or- create:%> <%:unspecified -or- create:%>
<%- end -%>&#160;</em></label> <%- end -%>
</em>
<input style="display:none" type="password" /> <input style="display:none" type="password" />
<input style="width:6em" type="text"<%=attr("name", cbid .. ".newnet")%> onfocus="document.getElementById('<%=cbid%>_new').checked=true" /> <input class="create-item-input" type="text" />
</div> </li>
</li> <% end %>
<% elseif self.widget ~= "checkbox" and self.unspecified then %> </ul>
<li style="padding:0.25em 0"> </div>
<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")
%> /> &#160;
<div style="padding:0.5em; display:inline">
<label<%=attr("for", cbid .. "_uns")%>><em><%:unspecified%></em></label>
</div>
</li>
<% end %>
</ul>
<%+cbi/valuefooter%> <%+cbi/valuefooter%>

BIN
luci-base/src/po2lmo Executable file

Binary file not shown.

BIN
luci-base/src/po2lmo.o Normal file

Binary file not shown.

Binary file not shown.

View file

@ -11,6 +11,7 @@ LUCI_TITLE:=LuCI Administration - full-featured for full control
LUCI_DEPENDS:=+luci-base +luci-app-firewall LUCI_DEPENDS:=+luci-base +luci-app-firewall
PKG_BUILD_DEPENDS:=iwinfo PKG_BUILD_DEPENDS:=iwinfo
PKG_LICENSE:=Apache-2.0
include ../luci/luci.mk include ../luci/luci.mk

View file

@ -381,7 +381,6 @@ if has_firewall then
fwzone.template = "cbi/firewall_zonelist" fwzone.template = "cbi/firewall_zonelist"
fwzone.network = arg[1] fwzone.network = arg[1]
fwzone.rmempty = false
function fwzone.cfgvalue(self, section) function fwzone.cfgvalue(self, section)
self.iface = section self.iface = section
@ -390,22 +389,16 @@ if has_firewall then
end end
function fwzone.write(self, section, value) function fwzone.write(self, section, value)
local zone = fw:get_zone(value) local zone = fw:get_zone(value) or fw:add_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
if zone then if zone then
fw:del_network(section) fw:del_network(section)
zone:add_network(section) zone:add_network(section)
end end
end end
function fwzone.remove(self, section)
fw:del_network(section)
end
end end

View file

@ -390,22 +390,16 @@ network.novirtual = true
function network.write(self, section, value) function network.write(self, section, value)
local i = nw:get_interface(section) local i = nw:get_interface(section)
if i then if i then
if value == '-' then local _, net, old, new = nil, nil, {}, {}
value = m:formvalue(self:cbid(section) .. ".newnet")
if value and #value > 0 then for _, net in ipairs(i:get_networks()) do
local n = nw:add_network(value, {proto="none"}) old[net:name()] = true
if n then n:add_interface(i) end end
else
local n = i:get_network() for net in ut.imatch(value) do
if n then n:del_interface(i) end new[net] = true
end if not old[net] then
else local n = nw:get_network(net) or nw:add_network(net, { proto = "none" })
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)
if n then if n then
if not n:is_empty() then if not n:is_empty() then
n:set("type", "bridge") n:set("type", "bridge")
@ -414,6 +408,15 @@ function network.write(self, section, value)
end end
end 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
end end

View file

@ -94,14 +94,9 @@ function newnet.parse(self, section)
local net, zone local net, zone
if has_firewall then if has_firewall then
local zval = fwzone:formvalue(section) local value = fwzone:formvalue(section)
zone = fw:get_zone(zval) if value and #value > 0 then
zone = fw:get_zone(value) or fw:add_zone(value)
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
end end
end end

View file

@ -430,7 +430,6 @@ form .clearfix,
form .cbi-value { form .cbi-value {
margin-bottom: 18px; margin-bottom: 18px;
zoom: 1; zoom: 1;
overflow: hidden;
} }
form .clearfix:before, form .clearfix:after, form .clearfix:before, form .clearfix:after,
@ -477,6 +476,7 @@ input[type=checkbox], input[type=radio] {
input, input,
textarea, textarea,
select, select,
.cbi-dropdown,
.uneditable-input { .uneditable-input {
display: inline-block; display: inline-block;
width: 210px; width: 210px;
@ -487,6 +487,13 @@ select,
color: #808080; color: #808080;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 3px; border-radius: 3px;
box-sizing: border-box;
}
.cbi-dropdown {
min-width: 210px;
max-width: 400px;
width: auto;
} }
select { select {
@ -1365,6 +1372,159 @@ footer {
background-image: url('../resources/cbi/download.gif'), linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); 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 { .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); box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
} }
@ -1456,7 +1616,7 @@ button.btn::-moz-focus-inner, input[type=submit].btn::-moz-focus-inner {
opacity: 0.4; opacity: 0.4;
} }
.alert-message, .errorbox { .alert-message {
position: relative; position: relative;
padding: 7px 15px; padding: 7px 15px;
margin-bottom: 18px; margin-bottom: 18px;
@ -1679,7 +1839,6 @@ table table td,
background-color: #FFFFFF; background-color: #FFFFFF;
border: 1px solid #CCCCCC; border: 1px solid #CCCCCC;
padding: 2px; padding: 2px;
margin-left: 2px;
background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
border-radius: 4px; border-radius: 4px;
@ -1745,7 +1904,7 @@ table table td,
.zonebadge > em, .zonebadge > em,
.zonebadge > strong { .zonebadge > strong {
margin: 5px; margin: 0 2px;
display: inline-block; display: inline-block;
} }
@ -1753,6 +1912,10 @@ table table td,
width: 6em; width: 6em;
} }
.zonebadge > .ifacebadge {
margin-left: 2px;
}
.zonebadge-empty { .zonebadge-empty {
border: 1px dashed #AAAAAA; border: 1px dashed #AAAAAA;
color: #AAAAAA; color: #AAAAAA;

File diff suppressed because it is too large Load diff

View file

@ -190,22 +190,20 @@
</div> </div>
</header> </header>
<%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") then -%> <div id="maincontent" class="container">
<div class="container"> <%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") then -%>
<div class="alert-message warning"> <div class="alert-message warning">
<h4><%:No password set!%></h4> <h4><%:No password set!%></h4>
<%:There is no password set on this router. Please configure a root password to protect the web interface and enable SSH.%><br> <p><%:There is no password set on this router. Please configure a root password to protect the web interface and enable SSH.%></p>
<a href="<%=pcdata(luci.dispatcher.build_url("admin/system/admin"))%>"><%:Go to password configuration...%></a> <div class="right"><a class="btn" href="<%=url("admin/system/admin")%>"><%:Go to password configuration...%></a></div>
</div> </div>
</div> <%- end -%>
<%- end -%>
<noscript> <noscript>
<div class="alert-message warning"> <div class="alert-message warning">
<strong><%:JavaScript required!%></strong><br /> <h4><%:JavaScript required!%></h4>
<%:You must enable JavaScript in your browser or LuCI will not work properly.%> <p><%:You must enable JavaScript in your browser or LuCI will not work properly.%></p>
</div> </div>
</noscript> </noscript>
<div id="maincontent" class="container">
<% if category then render_tabmenu(category, cattree) end %> <% if category then render_tabmenu(category, cattree) end %>