diff --git a/.luacheckrc b/.luacheckrc index 2f491ca2..96390972 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -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", diff --git a/docs/user/site.rst b/docs/user/site.rst index fc82f314..96bb5eac 100644 --- a/docs/user/site.rst +++ b/docs/user/site.rst @@ -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: diff --git a/package/gluon-config-mode-theme/files/lib/gluon/config-mode/www/static/gluon.css b/package/gluon-config-mode-theme/files/lib/gluon/config-mode/www/static/gluon.css index c84ac01b..d239fd8f 100644 --- a/package/gluon-config-mode-theme/files/lib/gluon/config-mode/www/static/gluon.css +++ b/package/gluon-config-mode-theme/files/lib/gluon/config-mode/www/static/gluon.css @@ -1 +1 @@ -html{min-height:100%;height:auto;position:relative}body,input,select,option{font-family:'Open Sans', Arial, sans-serif;font-size:12pt}body{color:#4d4e53;line-height:1.5em;margin:0;display:flex;flex-direction:column;min-height:100vh;background-color:#f3f3f3}.tabmenu1{text-align:center}ul.tabmenu{list-style:none;padding:0;margin:2em 0;display:inline-flex}ul.tabmenu li{white-space:nowrap;margin:0 0.5em;padding:0;text-align:center}ul.tabmenu li a{display:block;text-decoration:none;padding:1em;margin:0;color:#333;border-radius:2em}ul.tabmenu li a:hover{background:#ffe9b3}ul.tabmenu li.active a{font-weight:bold;background:white;color:#333}#maincontent ul{margin-left:2em}.error{color:#ff0000;background-color:white}#menubar{display:flex;background:#dc0067;color:#ffffff}#menubar a:link.topcat,#menubar a:visited.topcat{position:relative;display:block;padding:0.5em;text-decoration:none;font-size:80%;font-weight:normal;color:white}#menubar a:link.topcat:hover,#menubar a:link.topcat:focus,#menubar a:visited.topcat:hover,#menubar a:visited.topcat:focus{background:#ffb400;color:black}#menubar a:link.topcat.active,#menubar a:visited.topcat.active{background:#ffb400;color:black;font-weight:bold}#menubar .hostinfo{position:relative;margin:0;padding:0.5em;flex:1;font-weight:bold;font-size:80%}#menubar .hostinfo a:link,#menubar .hostinfo a:visited{text-decoration:none;font-weight:bold;color:white}#menubar .hostinfo a:link:hover,#menubar .hostinfo a:link:focus,#menubar .hostinfo a:visited:hover,#menubar .hostinfo a:visited:focus{text-decoration:underline}#topmenu{list-style:none;margin:0;padding:0}#topmenu li{display:inline-block}#maincontent{padding:0 1em 2em;max-width:60em;min-width:40em;margin:1em auto}#maincontent p{margin-bottom:1em}.gluon-section{margin:0;padding:0;border:none;margin-bottom:1.3em}.gluon-section:last-child{margin-bottom:0.7em}.gluon-section legend{font-size:1.4em;font-weight:bold;position:relative;padding:0;margin-bottom:0.5em}.gluon-section h2{margin:0em 0 0.5em -0.5em}.gluon-section h3{text-decoration:none;font-weight:bold;color:#555555;margin:0.25em;font-size:100%}.gluon-section-descr,.gluon-warning{margin-bottom:2em}.gluon-osm-map{width:100%;height:40em;margin-bottom:1em}input::placeholder{color:#aaaaaa}input::-webkit-input-placeholder{color:#aaaaaa}input[type=checkbox]{display:none}input[type=checkbox]+label{display:inline-block;position:relative;width:1em;height:1em;margin:0}input[type=checkbox]:checked+label::after{content:'✔';color:#dc0067;vertical-align:middle;position:absolute;top:50%;left:0;margin-top:-0.5em;width:100%;text-align:center;font-size:1.7em}input[type=radio]{display:none}input[type=radio]+label{display:inline-block;position:relative;width:0.8em;height:0.8em;padding:0.5em;margin:0.2em 0.2em 0.2em 0.1em;border:none;background:#ffe199;vertical-align:middle;border-radius:50%}input[type=radio]:checked+label::after{content:'•';color:#dc0067;vertical-align:middle;position:absolute;top:50%;left:0;margin-top:-0.4em;width:100%;text-align:center;font-size:2em}input[type=submit],input[type=reset],input[type=button]{cursor:pointer}select,input,textarea,input[type=checkbox]+label{color:#003247;border:none;background:#ffe199;border-radius:3pt;padding:0.5em;margin-top:1px;margin-bottom:2px;box-sizing:content-box;outline:0}.select-wrapper{position:relative;display:inline-block}.select-wrapper::before{position:absolute;z-index:1;right:0.05em;top:calc(2px + 0.1em);bottom:calc(2px + 0.1em);width:1.4em;border-left:0.05em solid rgba(0,0,0,0.25);pointer-events:none;background:url('data:image/svg+xml;utf8,') center/0.8em 0.5em no-repeat;content:''}.select-wrapper select{-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer}option{color:#003247;background:#ffe199}select,input[type=text],input[type=password]{min-width:20em}.gluon-button{display:inline-block;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;user-select:none;font-size:100%;padding:0.5em 1em;color:rgba(0,0,0,0.8);background-color:#E6E6E6;border:none;text-decoration:none;border-radius:2px;transition:0.1s linear box-shadow;margin-left:0.5em;background-repeat:no-repeat}.gluon-button::-moz-focus-inner{padding:0;border:0}.gluon-button:active{box-shadow:0 0 0 1px rgba(0,0,0,0.15) inset,0 0 6px rgba(0,0,0,0.2) inset}.gluon-button:focus{outline:0}.gluon-button:hover,.gluon-button:focus{background-image:linear-gradient(transparent, rgba(0,0,0,0.05) 40%, rgba(0,0,0,0.1))}.gluon-button[disabled]{border:none;background-image:none;opacity:0.40;cursor:not-allowed;box-shadow:none}.gluon-button-reset{background-color:#e30;color:#fff}.gluon-button-submit{background-color:#009ee0;color:#fff}.gluon-button-submit:active{background:grey}.gluon-input-invalid{background:#e30 !important;color:white}textarea{margin-left:-1px;margin-bottom:0.5em}.gluon-value{display:flex;flex-direction:row;margin-bottom:0.5em}.gluon-section-node .gluon-value:last-child{margin-bottom:0}.gluon-value-title{flex:2;text-align:right;padding-top:0.39em;padding-right:1em;font-weight:bold}.gluon-value-field{flex:3;position:relative}.gluon-value-field input,.gluon-value-field select,.gluon-value-field input+label{position:relative}.gluon-value-field-text{flex:3;padding-top:0.39em}.gluon-value-field-long{flex:10;position:relative;margin-top:0.65em}.gluon-value-field-long input,.gluon-value-field-long select,.gluon-value-field-long input+label{position:relative}.gluon-value-field-long-after{flex:2}.gluon-value-description{font-size:8pt}.gluon-form-descr{margin-bottom:1em}.gluon-form-descr:empty,.gluon-section-descr:empty,.gluon-warning:empty{display:none}.gluon-form-descr,.gluon-section-descr,.gluon-warning,.gluon-page-actions{padding:1em;background:#ececec}.gluon-page-actions{text-align:right;display:flex;flex-flow:row-reverse}.gluon-section-node{clear:both;position:relative;border:none}.gluon-value-error input,.gluon-value-error select{background-color:#ffcccc}.gluon-add::after,.gluon-remove::after{cursor:pointer;display:inline-block;text-align:center;vertical-align:middle;font-size:180%;width:1.2em;height:1em}.gluon-add{color:#008000;position:relative;left:21em}input+.gluon-add{left:0;top:0.04em}.gluon-add:first-child{top:0.53em;left:-0.08em}.gluon-add::after{content:'+'}.gluon-remove{color:#800000;position:relative;top:-0.03em}.gluon-remove::after{content:'–'}.gluon-warning{background:#ffe9b3}.error500{border:1px dotted #ff0000;background-color:#ffffff;color:#000000;padding:0.5em}.errorbox{border:1px solid #FF0000;background-color:#FFCCCC;padding:5px;margin-bottom:5px}.errorbox a{color:#000000 !important}.the-key{text-align:left;font-size:1.4em;background:#ffe9b3;border:3pt dashed #dc0067;margin-bottom:0.5em;padding:0.5em} +html{min-height:100%;height:auto;position:relative}body,input,select,option{font-family:'Open Sans', Arial, sans-serif;font-size:12pt}body{color:#4d4e53;line-height:1.5em;margin:0;display:flex;flex-direction:column;min-height:100vh;background-color:#f3f3f3}.tabmenu1{text-align:center}ul.tabmenu{list-style:none;padding:0;margin:2em 0;display:inline-flex}ul.tabmenu li{white-space:nowrap;margin:0 0.5em;padding:0;text-align:center}ul.tabmenu li a{display:block;text-decoration:none;padding:1em;margin:0;color:#333;border-radius:2em}ul.tabmenu li a:hover{background:#ffe9b3}ul.tabmenu li.active a{font-weight:bold;background:white;color:#333}#maincontent ul{margin-left:2em}.error{color:#ff0000;background-color:white}#menubar{display:flex;background:#dc0067;color:#ffffff}#menubar a:link.topcat,#menubar a:visited.topcat{position:relative;display:block;padding:0.5em;text-decoration:none;font-size:80%;font-weight:normal;color:white}#menubar a:link.topcat:hover,#menubar a:link.topcat:focus,#menubar a:visited.topcat:hover,#menubar a:visited.topcat:focus{background:#ffb400;color:black}#menubar a:link.topcat.active,#menubar a:visited.topcat.active{background:#ffb400;color:black;font-weight:bold}#menubar .hostinfo{position:relative;margin:0;padding:0.5em;flex:1;font-weight:bold;font-size:80%}#menubar .hostinfo a:link,#menubar .hostinfo a:visited{text-decoration:none;font-weight:bold;color:white}#menubar .hostinfo a:link:hover,#menubar .hostinfo a:link:focus,#menubar .hostinfo a:visited:hover,#menubar .hostinfo a:visited:focus{text-decoration:underline}#topmenu{list-style:none;margin:0;padding:0}#topmenu li{display:inline-block}#maincontent{padding:0 1em 2em;max-width:60em;min-width:40em;margin:1em auto}#maincontent p{margin-bottom:1em}.gluon-section{margin:0;padding:0;border:none;margin-bottom:1.3em}.gluon-section:last-child{margin-bottom:0.7em}.gluon-section legend{font-size:1.4em;font-weight:bold;position:relative;padding:0;margin-bottom:0.5em}.gluon-section h2{margin:0em 0 0.5em -0.5em}.gluon-section h3{text-decoration:none;font-weight:bold;color:#555555;margin:0.25em;font-size:100%}.gluon-section-descr,.gluon-warning{margin-bottom:2em}.gluon-osm-map{width:100%;height:40em;margin-bottom:1em}input::placeholder{color:#aaaaaa}input::-webkit-input-placeholder{color:#aaaaaa}input[type=checkbox]{display:none}input[type=checkbox]+label{display:inline-block;position:relative;width:1em;height:1em;margin:0}input[type=checkbox]:checked+label::after{content:'✔';color:#dc0067;vertical-align:middle;position:absolute;top:50%;left:0;margin-top:-0.5em;width:100%;text-align:center;font-size:1.7em}input[type=checkbox][disabled]+label{background-color:#dcdcdc !important}input[type=radio]{display:none}input[type=radio]+label{display:inline-block;position:relative;width:0.8em;height:0.8em;padding:0.5em;margin:0.2em 0.2em 0.2em 0.1em;border:none;background:#ffe199;vertical-align:middle;border-radius:50%}input[type=radio]:checked+label::after{content:'•';color:#dc0067;vertical-align:middle;position:absolute;top:50%;left:0;margin-top:-0.4em;width:100%;text-align:center;font-size:2em}input[type=submit],input[type=reset],input[type=button]{cursor:pointer}select,input,textarea,input[type=checkbox]+label{color:#003247;border:none;background:#ffe199;border-radius:3pt;padding:0.5em;margin-top:1px;margin-bottom:2px;box-sizing:content-box;outline:0}.select-wrapper{position:relative;display:inline-block}.select-wrapper::before{position:absolute;z-index:1;right:0.05em;top:calc(2px + 0.1em);bottom:calc(2px + 0.1em);width:1.4em;border-left:0.05em solid rgba(0,0,0,0.25);pointer-events:none;background:url('data:image/svg+xml;utf8,') center/0.8em 0.5em no-repeat;content:''}.select-wrapper select{-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer}option{color:#003247;background:#ffe199}select,input[type=text],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{display:inline-block;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;user-select:none;font-size:100%;padding:0.5em 1em;color:rgba(0,0,0,0.8);background-color:#E6E6E6;border:none;text-decoration:none;border-radius:2px;transition:0.1s linear box-shadow;margin-left:0.5em;background-repeat:no-repeat}.gluon-button::-moz-focus-inner{padding:0;border:0}.gluon-button:active{box-shadow:0 0 0 1px rgba(0,0,0,0.15) inset,0 0 6px rgba(0,0,0,0.2) inset}.gluon-button:focus{outline:0}.gluon-button:hover,.gluon-button:focus{background-image:linear-gradient(transparent, rgba(0,0,0,0.05) 40%, rgba(0,0,0,0.1))}.gluon-button[disabled]{border:none;background-image:none;opacity:0.40;cursor:not-allowed;box-shadow:none}.gluon-button-reset{background-color:#e30;color:#fff}.gluon-button-submit{background-color:#009ee0;color:#fff}.gluon-button-submit:active{background:grey}.gluon-input-invalid{background:#e30 !important;color:white}textarea{margin-left:-1px;margin-bottom:0.5em}.gluon-value{display:flex;flex-direction:row;margin-bottom:0.5em}.gluon-section-node .gluon-value:last-child{margin-bottom:0}.gluon-value-title{flex:2;text-align:right;padding-top:0.39em;padding-right:1em;font-weight:bold}.gluon-value-field{flex:3;position:relative}.gluon-value-field input,.gluon-value-field select,.gluon-value-field input+label{position:relative}.gluon-value-field-text{flex:3;padding-top:0.39em}.gluon-value-field-long{flex:10;position:relative;margin-top:0.65em}.gluon-value-field-long input,.gluon-value-field-long select,.gluon-value-field-long input+label{position:relative}.gluon-value-field-long-after{flex:2}.gluon-value-description{font-size:8pt}.gluon-form-descr{margin-bottom:1em}.gluon-form-descr:empty,.gluon-section-descr:empty,.gluon-warning:empty{display:none}.gluon-form-descr,.gluon-section-descr,.gluon-warning,.gluon-page-actions{padding:1em;background:#ececec}.gluon-page-actions{text-align:right;display:flex;flex-flow:row-reverse}.gluon-section-node{clear:both;position:relative;border:none}.gluon-value-error input,.gluon-value-error select{background-color:#ffcccc}.gluon-add::after,.gluon-remove::after{cursor:pointer;display:inline-block;text-align:center;vertical-align:middle;font-size:180%;width:1.2em;height:1em}.gluon-add{color:#008000;position:relative;left:21em}input+.gluon-add{left:0;top:0.04em}.gluon-add:first-child{top:0.53em;left:-0.08em}.gluon-add::after{content:'+'}.gluon-remove{color:#800000;position:relative;top:-0.03em}.gluon-remove::after{content:'–'}.gluon-warning{background:#ffe9b3}.error500{border:1px dotted #ff0000;background-color:#ffffff;color:#000000;padding:0.5em}.errorbox{border:1px solid #FF0000;background-color:#FFCCCC;padding:5px;margin-bottom:5px}.errorbox a{color:#000000 !important}.the-key{text-align:left;font-size:1.4em;background:#ffe9b3;border:3pt dashed #dc0067;margin-bottom:0.5em;padding:0.5em} diff --git a/package/gluon-config-mode-theme/sass/gluon.scss b/package/gluon-config-mode-theme/sass/gluon.scss index 575af4f9..a63156e4 100644 --- a/package/gluon-config-mode-theme/sass/gluon.scss +++ b/package/gluon-config-mode-theme/sass/gluon.scss @@ -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; diff --git a/package/gluon-core/check_site.lua b/package/gluon-core/check_site.lua index 06b9ac59..cf72f05f 100644 --- a/package/gluon-core/check_site.lua +++ b/package/gluon-core/check_site.lua @@ -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.') diff --git a/package/gluon-core/luasrc/lib/gluon/check-site.lua b/package/gluon-core/luasrc/lib/gluon/check-site.lua index 148f4968..cde55f44 100644 --- a/package/gluon-core/luasrc/lib/gluon/check-site.lua +++ b/package/gluon-core/luasrc/lib/gluon/check-site.lua @@ -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, diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/021-interface-roles b/package/gluon-core/luasrc/lib/gluon/upgrade/021-interface-roles index 5d27fb70..6e39219a 100755 --- a/package/gluon-core/luasrc/lib/gluon/upgrade/021-interface-roles +++ b/package/gluon-core/luasrc/lib/gluon/upgrade/021-interface-roles @@ -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') diff --git a/package/gluon-web-model/files/lib/gluon/web/view/model/mlvalue.html b/package/gluon-web-model/files/lib/gluon/web/view/model/mlvalue.html new file mode 100644 index 00000000..0a4fb5c9 --- /dev/null +++ b/package/gluon-web-model/files/lib/gluon/web/view/model/mlvalue.html @@ -0,0 +1,25 @@ +<% + local br = self.orientation == "horizontal" and '   ' or '
' + local entries = self:entries() + local util = require 'gluon.util' +%> +
+ <% for i, entry in pairs(entries) do %> + > + > + > + <%|entry.value%> + + <% if i ~= #entries then write(br) end %> + <% end %> +
diff --git a/package/gluon-web-model/javascript/gluon-web-model.js b/package/gluon-web-model/javascript/gluon-web-model.js index ec9cdc4a..4596a143 100644 --- a/package/gluon-web-model/javascript/gluon-web-model.js +++ b/package/gluon-web-model/javascript/gluon-web-model.js @@ -1,19 +1,15 @@ /* - Copyright 2008 Steven Barth - Copyright 2008-2012 Jo-Philipp Wich - Copyright 2017 Matthias Schiffer - - 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 + SPDX-FileCopyrightText: 2008-2012, Jo-Philipp Wich + SPDX-FileCopyrightText: 2017, Matthias Schiffer + SPDX-FileCopyrightText: 2023, Leonardo Mörlein */ /* 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(); } diff --git a/package/gluon-web-model/javascript/gluon-web-model.min.js b/package/gluon-web-model/javascript/gluon-web-model.min.js index 07478cbb..d0ae42fc 100644 --- a/package/gluon-web-model/javascript/gluon-web-model.min.js +++ b/package/gluon-web-model/javascript/gluon-web-model.min.js @@ -1 +1 @@ -!function(){var v={};function a(e){return/^-?\d+$/.test(e)?+e:NaN}function r(e){return/^-?\d*\.?\d+?$/.test(e)?+e:NaN}var u={integer:function(){return!isNaN(a(this))},uinteger:function(){return 0<=a(this)},float:function(){return!isNaN(r(this))},ufloat:function(){return 0<=r(this)},ipaddr:function(){return u.ip4addr.apply(this)||u.ip6addr.apply(this)},ip4addr:function(){var e;return!!(e=this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))&&(0<=e[1]&&e[1]<=255&&0<=e[2]&&e[2]<=255&&0<=e[3]&&e[3]<=255&&0<=e[4]&&e[4]<=255)},ip6addr:function(){return this.indexOf("::")<0?null!=this.match(/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i):!(0<=this.indexOf(":::")||this.match(/::.+::/)||this.match(/^:[^:]/)||this.match(/[^:]:$/))&&(!!this.match(/^(?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}$/i)||(!!this.match(/^(?:[a-f0-9]{1,4}:){7}:$/i)||!!this.match(/^:(?::[a-f0-9]{1,4}){7}$/i)))},wpakey:function(){var e=this;return 64==e.length?null!=e.match(/^[a-f0-9]{64}$/i):8<=e.length&&e.length<=63},range:function(e,t){var n=r(this);return+e<=n&&n<=+t},min:function(e){return r(this)>=+e},max:function(e){return r(this)<=+e},irange:function(e,t){var n=a(this);return+e<=n&&n<=+t},imin:function(e){return a(this)>=+e},imax:function(e){return a(this)<=+e},minlength:function(e){return+e<=(""+this).length},maxlength:function(e){return(""+this).length<=+e}};function o(e){for(var t=0;tn.index);i=i.nextSibling);i?r.insertBefore(n.node,i):r.appendChild(n.node),n.node.dispatchEvent(new Event("gluon-show")),e=!0}r&&r.parentNode&&r.getAttribute("data-optionals")&&(r.parentNode.style.display=r.options.length<=1?"none":"")}e&&h()}function g(e,t,n,a){return e.addEventListener?e.addEventListener(t,n,!!a):e.attachEvent("on"+t,function(){var e=window.event;return!e.target&&e.srcElement&&(e.target=e.srcElement),!!n(e)}),e}function m(l,s){var c=s.prefix;function o(e,t,n){for(var a=[];l.firstChild;){var r=l.firstChild;(i=+r.index)!=n&&("input"==r.nodeName.toLowerCase()?a.push(r.value||""):"select"==r.nodeName.toLowerCase()&&(a[a.length-1]=r.options[r.selectedIndex].value)),l.removeChild(r)}0<=t?(e=t+1,a.splice(t,0,"")):s.optional||0!=a.length||a.push("");for(var i=1;i<=a.length;i++){var o=document.createElement("input");if(o.id=c+"."+i,o.name=c,o.value=a[i-1],o.type="text",o.index=i,s.size&&(o.size=s.size),s.placeholder&&(o.placeholder=s.placeholder),l.appendChild(o),s.type&&y(o,!1,s.type),g(o,"keydown",f),g(o,"keypress",p),i==e)o.focus();else if(-i==e){o.focus();var d=o.value;o.value=" ",o.value=d}if(s.optional||1=+e},max:function(e){return i(this)<=+e},irange:function(e,t){var n=a(this);return+e<=n&&n<=+t},imin:function(e){return a(this)>=+e},imax:function(e){return a(this)<=+e},minlength:function(e){return+e<=(""+this).length},maxlength:function(e){return(""+this).length<=+e}};function p(e){for(var t,n,a,i=0;in.index);r=r.nextSibling);r?i.insertBefore(n.node,r):i.appendChild(n.node),n.node.dispatchEvent(new Event("gluon-show")),t=!0}i&&i.parentNode&&i.getAttribute("data-optionals")&&(i.parentNode.style.display=i.options.length<=1?"none":"")}for(var d=document.querySelectorAll("[data-exclusive-with]"),o=0;(a=d[o])!==undefined;o++){var u,l=JSON.parse(a.getAttribute("data-exclusive-with"));a.disabled=!1;for(u of l){var c=document.getElementById(a.name+"."+u);a.disabled||=c.checked}a.disabled&&(a.checked=!1)}t&&f()}function v(e,t,n,a){e.addEventListener?e.addEventListener(t,n,!!a):e.attachEvent("on"+t,function(){var e=window.event;return!e.target&&e.srcElement&&(e.target=e.srcElement),!!n(e)})}function m(t,n,e){var a,i,r=(i=(e=e).match(/^([^\(]+)\(([^,]+),([^\)]+)\)$/))&&(a=d[i[1]])!==undefined?function(){return a.apply(this,[i[2],i[3]])}:(i=e.match(/^([^\(]+)\(([^,\)]+)\)$/))&&(a=d[i[1]])!==undefined?function(){return a.apply(this,[i[2]])}:d[e];r&&(v(t,"blur",e=function(){var e;t.form&&(t.className=t.className.replace(/ gluon-input-invalid/g,""),0==(e=(t.options&&-1 --- Copyright 2017-2018 Matthias Schiffer --- Licensed to the public under the Apache License 2.0. +-- SPDX-License-Identifier: Apache-2.0 +-- SPDX-FileCopyrightText: 2008, Steven Barth +-- SPDX-FileCopyrightText: 2017-2018, Matthias Schiffer +-- SPDX-FileCopyrightText: 2023, Leonardo Mörlein 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 diff --git a/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/datatypes.lua b/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/datatypes.lua index fdee7d7d..31c1aa82 100644 --- a/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/datatypes.lua +++ b/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/datatypes.lua @@ -1,6 +1,6 @@ --- Copyright 2010 Jo-Philipp Wich --- Copyright 2017 Matthias Schiffer --- Licensed to the public under the Apache License 2.0. +-- SPDX-License-Identifier: Apache-2.0 +-- SPDX-FileCopyrightText: 2010, Jo-Philipp Wich +-- SPDX-FileCopyrightText: 2017, Matthias Schiffer local M = {} diff --git a/package/gluon-web-network/i18n/de.po b/package/gluon-web-network/i18n/de.po index 77b3a6a3..ccb29f48 100644 --- a/package/gluon-web-network/i18n/de.po +++ b/package/gluon-web-network/i18n/de.po @@ -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" diff --git a/package/gluon-web-network/i18n/fr.po b/package/gluon-web-network/i18n/fr.po index 97067343..7bc89801 100644 --- a/package/gluon-web-network/i18n/fr.po +++ b/package/gluon-web-network/i18n/fr.po @@ -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" diff --git a/package/gluon-web-network/i18n/gluon-web-network.pot b/package/gluon-web-network/i18n/gluon-web-network.pot index a75929df..ce8cb39b 100644 --- a/package/gluon-web-network/i18n/gluon-web-network.pot +++ b/package/gluon-web-network/i18n/gluon-web-network.pot @@ -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 "" diff --git a/package/gluon-web-network/luasrc/lib/gluon/config-mode/model/admin/network.lua b/package/gluon-web-network/luasrc/lib/gluon/config-mode/model/admin/network.lua index df92c965..c57264b2 100644 --- a/package/gluon-web-network/luasrc/lib/gluon/config-mode/model/admin/network.lua +++ b/package/gluon-web-network/luasrc/lib/gluon/config-mode/model/admin/network.lua @@ -1,21 +1,14 @@ ---[[ -Copyright 2014 Nils Schneider - -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 +-- SPDX-FileCopyrightText: 2023, Leonardo Mörlein 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