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:
		
						commit
						10e52bec3a
					
				| @ -29,6 +29,7 @@ files["package/**/check_site.lua"] = { | ||||
| 		"need", | ||||
| 		"need_alphanumeric_key", | ||||
| 		"need_array", | ||||
| 		"need_array_elements_exclusive", | ||||
| 		"need_array_of", | ||||
| 		"need_boolean", | ||||
| 		"need_chanlist", | ||||
| @ -50,6 +51,7 @@ files["package/**/check_site.lua"] = { | ||||
| 
 | ||||
| files["package/**/luasrc/lib/gluon/config-mode/*"] = { | ||||
| 	globals = { | ||||
| 		"MultiListValue", | ||||
| 		"DynamicList", | ||||
| 		"Flag", | ||||
| 		"Form", | ||||
|  | ||||
| @ -448,13 +448,8 @@ interfaces \: optional | ||||
|   The ``client`` role requires exclusive control over an interface. When | ||||
|   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 | ||||
|   precedence (enabling ``mesh``, but not ``client`` in the example). | ||||
| 
 | ||||
|   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. | ||||
|   precedence (enabling ``mesh``, but not ``client`` in the example). In that | ||||
|   case, the ``client`` role is removed from the config of the interface. | ||||
| 
 | ||||
|   All interface settings are optional. If unset, the following defaults are | ||||
|   used: | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -273,6 +273,10 @@ input[type=checkbox] { | ||||
| 		text-align: center; | ||||
| 		font-size: 1.7em; | ||||
| 	} | ||||
| 
 | ||||
| 	&[disabled] + label { | ||||
| 		background-color: #dcdcdc !important; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| input[type=radio] { | ||||
| @ -366,6 +370,13 @@ input[type=password] { | ||||
| 	min-width: 20em; | ||||
| } | ||||
| 
 | ||||
| .gluon-multi-list-option-descr { | ||||
| 	display: inline-block; | ||||
| 	vertical-align: top; | ||||
| 	margin-top: 0.35em; | ||||
| 	margin-left: 0.4em; | ||||
| } | ||||
| 
 | ||||
| .gluon-button { | ||||
| 	@include button; | ||||
| 
 | ||||
|  | ||||
| @ -77,7 +77,11 @@ need_boolean(in_domain({'mesh', 'vxlan'}), false) | ||||
| 
 | ||||
| local interfaces_roles = {'client', 'uplink', 'mesh'} | ||||
| 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 | ||||
| 
 | ||||
| obsolete({'mesh_on_wan'}, 'Use interfaces.wan.default_roles.') | ||||
|  | ||||
| @ -55,6 +55,14 @@ local function merge(a, b) | ||||
| 	return m | ||||
| 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) | ||||
| 	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) | ||||
| 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) | ||||
| 	local valid_chanlist = check_chanlist(channels) | ||||
| 	return M.need(path, valid_chanlist, required, | ||||
|  | ||||
| @ -63,4 +63,19 @@ for iface in pairs(interfaces) do | ||||
| 	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') | ||||
|  | ||||
| @ -0,0 +1,25 @@ | ||||
| <% | ||||
| 	local br = self.orientation == "horizontal" and '   ' 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> | ||||
| @ -1,19 +1,15 @@ | ||||
| /* | ||||
| 	Copyright 2008 Steven Barth <steven@midlink.org> | ||||
| 	Copyright 2008-2012 Jo-Philipp Wich <jow@openwrt.org> | ||||
| 	Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.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
 | ||||
| 	SPDX-License-Identifier: Apache-2.0 | ||||
| 	SPDX-FileCopyrightText: 2008, Steven Barth <steven@midlink.org> | ||||
| 	SPDX-FileCopyrightText: 2008-2012, Jo-Philipp Wich <jow@openwrt.org> | ||||
| 	SPDX-FileCopyrightText: 2017, Matthias Schiffer <mschiffer@universe-factory.net> | ||||
| 	SPDX-FileCopyrightText: 2023, Leonardo Mörlein <me@irrelefant.net> | ||||
| */ | ||||
| 
 | ||||
| /* | ||||
| 	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' : ''; | ||||
| 		} | ||||
| 
 | ||||
| 		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) { | ||||
| 			update(); | ||||
| 		} | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -1,8 +1,10 @@ | ||||
| -- Copyright 2008 Steven Barth <steven@midlink.org> | ||||
| -- Copyright 2017-2018 Matthias Schiffer <mschiffer@universe-factory.net> | ||||
| -- Licensed to the public under the Apache License 2.0. | ||||
| -- SPDX-License-Identifier: Apache-2.0 | ||||
| -- SPDX-FileCopyrightText: 2008, Steven Barth <steven@midlink.org> | ||||
| -- 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 gluon_util = require "gluon.util" | ||||
| 
 | ||||
| local datatypes  = require "gluon.web.model.datatypes" | ||||
| local class      = util.class | ||||
| @ -361,6 +363,83 @@ function ListValue:validate() | ||||
| 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) | ||||
| M.DynamicList = DynamicList | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| -- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org> | ||||
| -- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net> | ||||
| -- Licensed to the public under the Apache License 2.0. | ||||
| -- SPDX-License-Identifier: Apache-2.0 | ||||
| -- SPDX-FileCopyrightText: 2010, Jo-Philipp Wich <jow@openwrt.org> | ||||
| -- SPDX-FileCopyrightText: 2017, Matthias Schiffer <mschiffer@universe-factory.net> | ||||
| 
 | ||||
| local M = {} | ||||
| 
 | ||||
|  | ||||
| @ -28,15 +28,6 @@ msgstr "PoE-Passthrough aktivieren" | ||||
| msgid "Enable PoE Power Port %s" | ||||
| 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" | ||||
| msgstr "Gateway" | ||||
| 
 | ||||
| @ -49,6 +40,12 @@ msgstr "IPv4" | ||||
| msgid "IPv6" | ||||
| msgstr "IPv6" | ||||
| 
 | ||||
| msgid "Interface" | ||||
| msgstr "Interface" | ||||
| 
 | ||||
| msgid "LAN Interfaces" | ||||
| msgstr "LAN-Interfaces" | ||||
| 
 | ||||
| msgid "Netmask" | ||||
| msgstr "Netzmaske" | ||||
| 
 | ||||
| @ -61,5 +58,8 @@ msgstr "Statisch" | ||||
| msgid "Static DNS servers" | ||||
| msgstr "Statische DNS-Server" | ||||
| 
 | ||||
| msgid "WAN Interfaces" | ||||
| msgstr "WAN-Interfaces" | ||||
| 
 | ||||
| msgid "WAN connection" | ||||
| msgstr "WAN-Verbindung" | ||||
|  | ||||
| @ -28,15 +28,6 @@ msgstr "" | ||||
| msgid "Enable PoE Power Port %s" | ||||
| 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" | ||||
| msgstr "Passerelle" | ||||
| 
 | ||||
|  | ||||
| @ -19,15 +19,6 @@ msgstr "" | ||||
| msgid "Enable PoE Power Port %s" | ||||
| 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" | ||||
| msgstr "" | ||||
| 
 | ||||
| @ -40,6 +31,12 @@ msgstr "" | ||||
| msgid "IPv6" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Interface" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "LAN Interfaces" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Netmask" | ||||
| msgstr "" | ||||
| 
 | ||||
| @ -52,5 +49,8 @@ msgstr "" | ||||
| msgid "Static DNS servers" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "WAN Interfaces" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "WAN connection" | ||||
| msgstr "" | ||||
|  | ||||
| @ -1,21 +1,14 @@ | ||||
| --[[ | ||||
| Copyright 2014 Nils Schneider <nils@nilsschneider.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 | ||||
| ]]-- | ||||
| -- SPDX-License-Identifier: Apache-2.0 | ||||
| -- SPDX-FileCopyrightText: 2014, Nils Schneider <nils@nilsschneider.net> | ||||
| -- SPDX-FileCopyrightText: 2023, Leonardo Mörlein <me@irrelefant.net> | ||||
| 
 | ||||
| local uci = require("simple-uci").cursor() | ||||
| local sysconfig = require 'gluon.sysconfig' | ||||
| local util = require 'gluon.util' | ||||
| 
 | ||||
| local wan = uci:get_all("network", "wan") | ||||
| local wan6 = uci:get_all("network", "wan6") | ||||
| local dns_static = uci:get_first("gluon-wan-dnsmasq", "static") | ||||
| 
 | ||||
| 
 | ||||
| local f = Form(translate("WAN connection")) | ||||
| 
 | ||||
| local s = f:section(Section) | ||||
| @ -76,36 +69,30 @@ end | ||||
| 
 | ||||
| s = f:section(Section) | ||||
| 
 | ||||
| local wired_mesh_help = { | ||||
| 	single = translate('Enable meshing on the Ethernet interface'), | ||||
| 	wan = translate('Enable meshing on the WAN interface'), | ||||
| 	lan = translate('Enable meshing on the LAN interface'), | ||||
| local pretty_ifnames = { | ||||
| 	["/wan"] = translate("WAN Interfaces"), | ||||
| 	["/single"] = translate("Interface"), | ||||
| 	["/lan"] = translate("LAN Interfaces") | ||||
| } | ||||
| 
 | ||||
| local function wired_mesh(iface) | ||||
| 	if not sysconfig[iface .. '_ifname'] then return end | ||||
| 	local iface_roles = uci:get_list('gluon', 'iface_' .. iface, 'role') | ||||
| uci:foreach('gluon', 'interface', function(config) | ||||
| 	local section_name = config['.name'] | ||||
| 	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]) | ||||
| 	option.default = util.contains(iface_roles, 'mesh') ~= false | ||||
| 	ifaces.orientation = 'horizontal' | ||||
| 	ifaces:value('uplink', 'Uplink') | ||||
| 	ifaces:value('mesh', 'Mesh') | ||||
| 	ifaces:value('client', 'Client') | ||||
| 	ifaces:exclusive('uplink', 'client') | ||||
| 	ifaces:exclusive('mesh', 'client') | ||||
| 
 | ||||
| 	function option:write(data) | ||||
| 		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) | ||||
| 	ifaces.default = config.role | ||||
| 
 | ||||
| 		-- Reconfigure on next reboot | ||||
| 		uci:set('gluon', 'core', 'reconfigure', true) | ||||
| 	function ifaces:write(data) | ||||
| 		uci:set_list("gluon", section_name, "role", data) | ||||
| 	end | ||||
| end | ||||
| end) | ||||
| 
 | ||||
| wired_mesh('single') | ||||
| wired_mesh('wan') | ||||
| wired_mesh('lan') | ||||
| 
 | ||||
| local section | ||||
| uci:foreach("system", "gpio_switch", function(si) | ||||
| @ -166,4 +153,5 @@ function f:write() | ||||
| 	uci:commit('system') | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| return f | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user