Merge pull request #2688 from lemoer/pr_config_mode_ui_for_interface_roles_v2

Config-Mode UI for Interface Role Assignment (v2)
This commit is contained in:
Matthias Schiffer 2023-04-27 21:11:33 +02:00 committed by GitHub
commit 10e52bec3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 230 additions and 87 deletions

View File

@ -29,6 +29,7 @@ files["package/**/check_site.lua"] = {
"need", "need",
"need_alphanumeric_key", "need_alphanumeric_key",
"need_array", "need_array",
"need_array_elements_exclusive",
"need_array_of", "need_array_of",
"need_boolean", "need_boolean",
"need_chanlist", "need_chanlist",
@ -50,6 +51,7 @@ files["package/**/check_site.lua"] = {
files["package/**/luasrc/lib/gluon/config-mode/*"] = { files["package/**/luasrc/lib/gluon/config-mode/*"] = {
globals = { globals = {
"MultiListValue",
"DynamicList", "DynamicList",
"Flag", "Flag",
"Form", "Form",

View File

@ -448,13 +448,8 @@ interfaces \: optional
The ``client`` role requires exclusive control over an interface. When The ``client`` role requires exclusive control over an interface. When
the ``client`` role is assigned to an interface at the same time as other the ``client`` role is assigned to an interface at the same time as other
roles (like ``'client', 'mesh'`` in the above example), the other roles take roles (like ``'client', 'mesh'`` in the above example), the other roles take
precedence (enabling ``mesh``, but not ``client`` in the example). precedence (enabling ``mesh``, but not ``client`` in the example). In that
case, the ``client`` role is removed from the config of the interface.
Such a default configuration still fulfills a purpose (and is in fact the
recommended way to enable "Mesh-on-LAN" by default): The "LAN interface
meshing" checkbox in the advanced network settings will only add or remove
the ``mesh`` role, so the ``client`` role must already be in the configuration
to make the LAN port a regular client interface when the checkbox is disabled.
All interface settings are optional. If unset, the following defaults are All interface settings are optional. If unset, the following defaults are
used: used:

File diff suppressed because one or more lines are too long

View File

@ -273,6 +273,10 @@ input[type=checkbox] {
text-align: center; text-align: center;
font-size: 1.7em; font-size: 1.7em;
} }
&[disabled] + label {
background-color: #dcdcdc !important;
}
} }
input[type=radio] { input[type=radio] {
@ -366,6 +370,13 @@ input[type=password] {
min-width: 20em; min-width: 20em;
} }
.gluon-multi-list-option-descr {
display: inline-block;
vertical-align: top;
margin-top: 0.35em;
margin-left: 0.4em;
}
.gluon-button { .gluon-button {
@include button; @include button;

View File

@ -77,7 +77,11 @@ need_boolean(in_domain({'mesh', 'vxlan'}), false)
local interfaces_roles = {'client', 'uplink', 'mesh'} local interfaces_roles = {'client', 'uplink', 'mesh'}
for _, config in ipairs({'wan', 'lan', 'single'}) do for _, config in ipairs({'wan', 'lan', 'single'}) do
need_array_of(in_site({'interfaces', config, 'default_roles'}), interfaces_roles, false) local default_roles = in_site({'interfaces', config, 'default_roles'})
need_array_of(default_roles, interfaces_roles, false)
need_array_elements_exclusive(default_roles, 'client', 'mesh', false)
need_array_elements_exclusive(default_roles, 'client', 'uplink', false)
end end
obsolete({'mesh_on_wan'}, 'Use interfaces.wan.default_roles.') obsolete({'mesh_on_wan'}, 'Use interfaces.wan.default_roles.')

View File

@ -55,6 +55,14 @@ local function merge(a, b)
return m return m
end end
local function contains(table, val)
for i=1,#table do
if table[i] == val then
return true
end
end
return false
end
local function path_to_string(path) local function path_to_string(path)
if path.is_value then if path.is_value then
@ -370,6 +378,21 @@ function M.need_array_of(path, array, required)
return M.need_array(path, function(e) M.need_one_of(e, array) end, required) return M.need_array(path, function(e) M.need_one_of(e, array) end, required)
end end
function M.need_array_elements_exclusive(path, a, b, required)
local val = need_type(path, 'table', required, 'be an array')
if not val then
return nil
end
if contains(val, a) and contains(val, b) then
config_error(conf_src(path),
'expected %s to contain only one of the elements %s and %s, but not both.',
path_to_string(path), format(a), format(b))
end
return val
end
function M.need_chanlist(path, channels, required) function M.need_chanlist(path, channels, required)
local valid_chanlist = check_chanlist(channels) local valid_chanlist = check_chanlist(channels)
return M.need(path, valid_chanlist, required, return M.need(path, valid_chanlist, required,

View File

@ -63,4 +63,19 @@ for iface in pairs(interfaces) do
end end
end end
-- Fix invalid role configurations
uci:foreach('gluon', 'interface', function(interface)
local function has_role(role)
return util.contains(interface.role, role)
end
if has_role('client') and (has_role('mesh') or has_role('uplink')) then
-- remove 'client' role
util.remove_from_set(interface.role, 'client')
uci:set('gluon', interface['.name'], 'role', interface.role)
end
end)
uci:save('gluon') uci:save('gluon')

View File

@ -0,0 +1,25 @@
<%
local br = self.orientation == "horizontal" and '&#160;&#160;&#160;' or '<br>'
local entries = self:entries()
local util = require 'gluon.util'
%>
<div>
<% for i, entry in pairs(entries) do %>
<label<%=
attr("data-index", i) ..
attr("data-depends", self:deplist(entry.deps))
%>>
<input data-update="click change" type="checkbox"<%=
attr("id", id.."."..entry.key) ..
attr("name", id) ..
attr("value", entry.key) ..
attr("checked", (util.contains(self:cfgvalue(), entry.key)) and "checked") ..
attr("data-exclusive-with", self.exclusions[entry.key]) ..
attr("data-update", "change")
%>>
<label<%= attr("for", id.."."..entry.key)%>></label>
<span class="gluon-multi-list-option-descr"><%|entry.value%></span>
</label>
<% if i ~= #entries then write(br) end %>
<% end %>
</div>

View File

@ -1,19 +1,15 @@
/* /*
Copyright 2008 Steven Barth <steven@midlink.org> SPDX-License-Identifier: Apache-2.0
Copyright 2008-2012 Jo-Philipp Wich <jow@openwrt.org> SPDX-FileCopyrightText: 2008, Steven Barth <steven@midlink.org>
Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net> SPDX-FileCopyrightText: 2008-2012, Jo-Philipp Wich <jow@openwrt.org>
SPDX-FileCopyrightText: 2017, Matthias Schiffer <mschiffer@universe-factory.net>
Licensed under the Apache License, Version 2.0 (the "License"); SPDX-FileCopyrightText: 2023, Leonardo Mörlein <me@irrelefant.net>
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/ */
/* /*
Build using: Build using:
uglifyjs javascript/gluon-web-model.js -o javascript/gluon-web-model.min.js -c -m --support-ie8 uglifyjs javascript/gluon-web-model.js -o javascript/gluon-web-model.min.js -c -m --ie
*/ */
@ -219,6 +215,20 @@
parent.parentNode.style.display = (parent.options.length <= 1) ? 'none' : ''; parent.parentNode.style.display = (parent.options.length <= 1) ? 'none' : '';
} }
var nodes = document.querySelectorAll('[data-exclusive-with]');
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
var exclusive_with = JSON.parse(node.getAttribute('data-exclusive-with'));
node.disabled = false;
for (var list_item of exclusive_with) {
var el = document.getElementById(node.name + '.' + list_item);
node.disabled ||= el.checked;
}
if (node.disabled)
node.checked = false;
}
if (state) { if (state) {
update(); update();
} }

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,10 @@
-- Copyright 2008 Steven Barth <steven@midlink.org> -- SPDX-License-Identifier: Apache-2.0
-- Copyright 2017-2018 Matthias Schiffer <mschiffer@universe-factory.net> -- SPDX-FileCopyrightText: 2008, Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0. -- SPDX-FileCopyrightText: 2017-2018, Matthias Schiffer <mschiffer@universe-factory.net>
-- SPDX-FileCopyrightText: 2023, Leonardo Mörlein <me@irrelefant.net>
local util = require "gluon.web.util" local util = require "gluon.web.util"
local gluon_util = require "gluon.util"
local datatypes = require "gluon.web.model.datatypes" local datatypes = require "gluon.web.model.datatypes"
local class = util.class local class = util.class
@ -361,6 +363,83 @@ function ListValue:validate()
end end
local MultiListValue = class(AbstractValue)
M.MultiListValue = MultiListValue
function MultiListValue:__init__(...)
AbstractValue.__init__(self, ...)
self.subtemplate = "model/mlvalue"
self.size = 1
self.keys = {}
self.entry_list = {}
end
function MultiListValue:value(key, val, ...)
key = tostring(key)
if self.keys[key] then
return
end
self.keys[key] = true
self.exclusions = {}
val = val or key
table.insert(self.entry_list, {
key = key,
value = tostring(val),
deps = {...},
})
end
function MultiListValue:entries()
local ret = {unpack(self.entry_list)}
return ret
end
function MultiListValue:validate()
for _, val in ipairs(self.data) do
if not self.keys[val] then
return false
end
end
for key, exclusive_with in pairs(self.exclusions) do
if gluon_util.contains(self.data, key) then
for _, exclusion in ipairs(exclusive_with) do
if gluon_util.contains(self.data, exclusion) then
return false
end
end
end
end
return true
end
function MultiListValue:exclusive(a, b)
if not self.exclusions[a] then
self.exclusions[a] = {}
end
if not self.exclusions[b] then
self.exclusions[b] = {}
end
gluon_util.add_to_set(self.exclusions[a], b)
gluon_util.add_to_set(self.exclusions[b], a)
end
function MultiListValue:defaultvalue()
return self.default or {}
end
function MultiListValue:formvalue(http)
return http:formvaluetable(self:id())
end
local DynamicList = class(AbstractValue) local DynamicList = class(AbstractValue)
M.DynamicList = DynamicList M.DynamicList = DynamicList

View File

@ -1,6 +1,6 @@
-- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org> -- SPDX-License-Identifier: Apache-2.0
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net> -- SPDX-FileCopyrightText: 2010, Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0. -- SPDX-FileCopyrightText: 2017, Matthias Schiffer <mschiffer@universe-factory.net>
local M = {} local M = {}

View File

@ -28,15 +28,6 @@ msgstr "PoE-Passthrough aktivieren"
msgid "Enable PoE Power Port %s" msgid "Enable PoE Power Port %s"
msgstr "PoE-Ausgabe auf Port %s aktivieren" msgstr "PoE-Ausgabe auf Port %s aktivieren"
msgid "Enable meshing on the Ethernet interface"
msgstr "Mesh auf dem Ethernet-Port aktivieren"
msgid "Enable meshing on the LAN interface"
msgstr "Mesh auf dem LAN-Port aktivieren"
msgid "Enable meshing on the WAN interface"
msgstr "Mesh auf dem WAN-Port aktivieren"
msgid "Gateway" msgid "Gateway"
msgstr "Gateway" msgstr "Gateway"
@ -49,6 +40,12 @@ msgstr "IPv4"
msgid "IPv6" msgid "IPv6"
msgstr "IPv6" msgstr "IPv6"
msgid "Interface"
msgstr "Interface"
msgid "LAN Interfaces"
msgstr "LAN-Interfaces"
msgid "Netmask" msgid "Netmask"
msgstr "Netzmaske" msgstr "Netzmaske"
@ -61,5 +58,8 @@ msgstr "Statisch"
msgid "Static DNS servers" msgid "Static DNS servers"
msgstr "Statische DNS-Server" msgstr "Statische DNS-Server"
msgid "WAN Interfaces"
msgstr "WAN-Interfaces"
msgid "WAN connection" msgid "WAN connection"
msgstr "WAN-Verbindung" msgstr "WAN-Verbindung"

View File

@ -28,15 +28,6 @@ msgstr ""
msgid "Enable PoE Power Port %s" msgid "Enable PoE Power Port %s"
msgstr "" msgstr ""
msgid "Enable meshing on the Ethernet interface"
msgstr ""
msgid "Enable meshing on the LAN interface"
msgstr "Activer le réseau MESH sur le port LAN"
msgid "Enable meshing on the WAN interface"
msgstr "Activer le réseau MESH sur les ports WAN"
msgid "Gateway" msgid "Gateway"
msgstr "Passerelle" msgstr "Passerelle"

View File

@ -19,15 +19,6 @@ msgstr ""
msgid "Enable PoE Power Port %s" msgid "Enable PoE Power Port %s"
msgstr "" msgstr ""
msgid "Enable meshing on the Ethernet interface"
msgstr ""
msgid "Enable meshing on the LAN interface"
msgstr ""
msgid "Enable meshing on the WAN interface"
msgstr ""
msgid "Gateway" msgid "Gateway"
msgstr "" msgstr ""
@ -40,6 +31,12 @@ msgstr ""
msgid "IPv6" msgid "IPv6"
msgstr "" msgstr ""
msgid "Interface"
msgstr ""
msgid "LAN Interfaces"
msgstr ""
msgid "Netmask" msgid "Netmask"
msgstr "" msgstr ""
@ -52,5 +49,8 @@ msgstr ""
msgid "Static DNS servers" msgid "Static DNS servers"
msgstr "" msgstr ""
msgid "WAN Interfaces"
msgstr ""
msgid "WAN connection" msgid "WAN connection"
msgstr "" msgstr ""

View File

@ -1,21 +1,14 @@
--[[ -- SPDX-License-Identifier: Apache-2.0
Copyright 2014 Nils Schneider <nils@nilsschneider.net> -- SPDX-FileCopyrightText: 2014, Nils Schneider <nils@nilsschneider.net>
-- SPDX-FileCopyrightText: 2023, Leonardo Mörlein <me@irrelefant.net>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
]]--
local uci = require("simple-uci").cursor() local uci = require("simple-uci").cursor()
local sysconfig = require 'gluon.sysconfig'
local util = require 'gluon.util'
local wan = uci:get_all("network", "wan") local wan = uci:get_all("network", "wan")
local wan6 = uci:get_all("network", "wan6") local wan6 = uci:get_all("network", "wan6")
local dns_static = uci:get_first("gluon-wan-dnsmasq", "static") local dns_static = uci:get_first("gluon-wan-dnsmasq", "static")
local f = Form(translate("WAN connection")) local f = Form(translate("WAN connection"))
local s = f:section(Section) local s = f:section(Section)
@ -76,36 +69,30 @@ end
s = f:section(Section) s = f:section(Section)
local wired_mesh_help = { local pretty_ifnames = {
single = translate('Enable meshing on the Ethernet interface'), ["/wan"] = translate("WAN Interfaces"),
wan = translate('Enable meshing on the WAN interface'), ["/single"] = translate("Interface"),
lan = translate('Enable meshing on the LAN interface'), ["/lan"] = translate("LAN Interfaces")
} }
local function wired_mesh(iface) uci:foreach('gluon', 'interface', function(config)
if not sysconfig[iface .. '_ifname'] then return end local section_name = config['.name']
local iface_roles = uci:get_list('gluon', 'iface_' .. iface, 'role') local ifaces = s:option(MultiListValue, section_name, pretty_ifnames[config.name] or config.name)
local option = s:option(Flag, 'mesh_' .. iface, wired_mesh_help[iface]) ifaces.orientation = 'horizontal'
option.default = util.contains(iface_roles, 'mesh') ~= false ifaces:value('uplink', 'Uplink')
ifaces:value('mesh', 'Mesh')
ifaces:value('client', 'Client')
ifaces:exclusive('uplink', 'client')
ifaces:exclusive('mesh', 'client')
function option:write(data) ifaces.default = config.role
local roles = uci:get_list('gluon', 'iface_' .. iface, 'role')
if data then
util.add_to_set(roles, 'mesh')
else
util.remove_from_set(roles, 'mesh')
end
uci:set_list('gluon', 'iface_' .. iface, 'role', roles)
-- Reconfigure on next reboot function ifaces:write(data)
uci:set('gluon', 'core', 'reconfigure', true) uci:set_list("gluon", section_name, "role", data)
end end
end end)
wired_mesh('single')
wired_mesh('wan')
wired_mesh('lan')
local section local section
uci:foreach("system", "gpio_switch", function(si) uci:foreach("system", "gpio_switch", function(si)
@ -166,4 +153,5 @@ function f:write()
uci:commit('system') uci:commit('system')
end end
return f return f