mirror of https://github.com/aredn/aredn.git
790 lines
26 KiB
Lua
Executable File
790 lines
26 KiB
Lua
Executable File
#!/usr/bin/lua
|
|
--[[
|
|
|
|
Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
|
|
Copyright (C) 2023 Tim Wilkinson
|
|
See Contributors file for additional contributors
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation version 3 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
Additional Terms:
|
|
|
|
Additional use restrictions exist on the AREDN® trademark and logo.
|
|
See AREDNLicense.txt for more info.
|
|
|
|
Attributions to the AREDN® Project must be retained in the source code.
|
|
If importing this code into a new or existing project attribution
|
|
to the AREDN® project must be added to the source code.
|
|
|
|
You must not misrepresent the origin of the material contained within.
|
|
|
|
Modified versions must be modified to attribute to the original source
|
|
and be marked in reasonable ways as differentiate it from the original
|
|
version
|
|
|
|
--]]
|
|
|
|
require("nixio")
|
|
require("aredn.hardware")
|
|
require("aredn.http")
|
|
local html = require("aredn.html")
|
|
|
|
math.randomseed(os.time())
|
|
|
|
local base = "/etc/aredn_include/"
|
|
local xlink_file = "/etc/config.mesh/xlink"
|
|
|
|
local default_5_port_layout = { ports = { [1] = "wan", [2] = "lan1", [3] = "lan2", [4] = "lan3", [5] = "lan4" } }
|
|
local default_3_port_layout = { ports = { [1] = "lan2", [2] = "lan1", [3] = "wan" } }
|
|
local function default_n_port_layout(board_type)
|
|
local ports = {}
|
|
for _, i in ipairs(nixio.getifaddrs())
|
|
do
|
|
if i.family == "packet" and i.name:match("^eth") then
|
|
ports[#ports + 1] = i.name
|
|
end
|
|
end
|
|
return { ports = ports }
|
|
end
|
|
local layouts = {
|
|
["mikrotik,hap-ac2"] = default_5_port_layout,
|
|
["mikrotik,hap-ac3"] = default_5_port_layout,
|
|
["glinet,gl-b1300"] = default_3_port_layout,
|
|
["qemu"] = default_n_port_layout,
|
|
["vmware"] = default_n_port_layout
|
|
}
|
|
|
|
local default_5_port_config = {
|
|
{
|
|
name = "dtdlink",
|
|
vlan = 2,
|
|
ports = { lan4 = { tagged = true } },
|
|
tagged = true
|
|
},
|
|
{
|
|
name = "lan",
|
|
vlan = 3,
|
|
ports = { lan1 = { tagged = false }, lan2 = { tagged = false }, lan3 = { tagged = false } },
|
|
tagged = false
|
|
},
|
|
{
|
|
name = "wan",
|
|
vlan = 1,
|
|
ports = { wan = { tagged = false } },
|
|
tagged = false
|
|
}
|
|
}
|
|
local default_3_port_config = {
|
|
{
|
|
name = "dtdlink",
|
|
vlan = 2,
|
|
ports = { lan2 = { tagged = true } },
|
|
tagged = true
|
|
},
|
|
{
|
|
name = "lan",
|
|
vlan = 3,
|
|
ports = { lan1 = { tagged = false } },
|
|
tagged = false
|
|
},
|
|
{
|
|
name = "wan",
|
|
vlan = 1,
|
|
ports = { wan = { tagged = false } },
|
|
tagged = false
|
|
}
|
|
}
|
|
local default_1_port_config = {
|
|
{
|
|
name = "dtdlink",
|
|
vlan = 2,
|
|
ports = { eth0 = { tagged = true } },
|
|
tagged = true
|
|
},
|
|
{
|
|
name = "lan",
|
|
vlan = 3,
|
|
ports = { eth0 = { tagged = false } },
|
|
tagged = false
|
|
},
|
|
{
|
|
name = "wan",
|
|
vlan = 1,
|
|
ports = { eth0 = { tagged = true } },
|
|
tagged = true
|
|
}
|
|
}
|
|
local default_configs = {
|
|
["mikrotik,hap-ac2"] = default_5_port_config,
|
|
["mikrotik,hap-ac3"] = default_5_port_config,
|
|
["glinet,gl-b1300"] = default_3_port_config,
|
|
["qemu"] = default_1_port_config,
|
|
["vmware"] = default_1_port_config
|
|
}
|
|
|
|
function read_user_config(network)
|
|
local file = base .. network .. ".network.user"
|
|
if not nixio.fs.stat(file) then
|
|
return nil
|
|
end
|
|
local config = {
|
|
name = network,
|
|
vlan = nil,
|
|
ports = {},
|
|
tagged = false
|
|
}
|
|
local invlan = false
|
|
for line in io.lines(file)
|
|
do
|
|
if line:match("^config%s+bridge%-vlan") then
|
|
invlan = true
|
|
elseif line:match("^config") then
|
|
invlan = false
|
|
elseif invlan then
|
|
local m
|
|
m = line:match("option%s+vlan%s+'(%d+)'")
|
|
if m then
|
|
config.vlan = tonumber(m)
|
|
if config.vlan == 2 or config.vlan >= 4 then
|
|
config.tagged = true
|
|
else
|
|
config.tagged = false
|
|
end
|
|
end
|
|
m = line:match("list%s+ports%s+'(%S+):u'") or line:match("list%s+ports%s+'(%S+):t'")
|
|
if m then
|
|
config.ports[m] = {
|
|
tagged = config.tagged
|
|
}
|
|
end
|
|
end
|
|
end
|
|
return config
|
|
end
|
|
|
|
function read_xlink_config()
|
|
if not nixio.fs.stat(xlink_file) then
|
|
return {}
|
|
end
|
|
local configs = {}
|
|
local config = {}
|
|
local type = "none"
|
|
for line in io.lines(xlink_file)
|
|
do
|
|
if line:match("^config%s+bridge%-vlan") then
|
|
type = "vlan"
|
|
config = {
|
|
name = nil,
|
|
vlan = nil,
|
|
ipaddr = nil,
|
|
peer = nil,
|
|
weight = 0,
|
|
port = nil,
|
|
netmask = nil,
|
|
mac = ""
|
|
}
|
|
configs[#configs + 1] = config
|
|
elseif line:match("^config%s+interface") then
|
|
type = "interface"
|
|
config.name = line:match("^config%s+interface%s+'(%S+)'")
|
|
elseif type == "vlan" then
|
|
local m
|
|
m = line:match("option%s+vlan%s+'(%d+)'")
|
|
if m then
|
|
config.vlan = tonumber(m)
|
|
end
|
|
m = line:match("list%s+ports%s+'(%S+):t'")
|
|
if m then
|
|
config.port = m
|
|
end
|
|
elseif type == "interface" then
|
|
local m
|
|
m = line:match("option%s+macaddr%s+'(%S+)'")
|
|
if m then
|
|
config.mac = m
|
|
end
|
|
m = line:match("option%s+ipaddr%s+'([%d%.]+)'")
|
|
if m then
|
|
config.ipaddr = m
|
|
end
|
|
m = line:match("option%s+peer%s+'([%d%.]+)'")
|
|
if m then
|
|
config.peer = m
|
|
end
|
|
m = line:match("option%s+weight%s+'([%d]+)'")
|
|
if m then
|
|
config.weight = tonumber(m)
|
|
end
|
|
m = line:match("option%s+netmask%s+'([%d%.]+)'")
|
|
if m then
|
|
config.netmask = m
|
|
end
|
|
end
|
|
end
|
|
return configs
|
|
end
|
|
|
|
function write_user_config(config, variables)
|
|
local network = config.name
|
|
local f = io.open(base .. network .. ".network.user", "w")
|
|
f:write("# Generated by advancednetwork\n")
|
|
f:write("\nconfig bridge-vlan\n")
|
|
f:write("\toption device 'br0'\n")
|
|
f:write("\toption vlan '" .. config.vlan .. "'\n")
|
|
for name, port in pairs(config.ports)
|
|
do
|
|
f:write("\tlist ports '" .. name .. (port.tagged and ":t" or ":u") .. "'\n")
|
|
end
|
|
f:write("\nconfig device\n")
|
|
f:write("\toption name 'br-" .. network .. "'\n")
|
|
f:write("\toption type 'bridge'\n")
|
|
f:write("\toption macaddr '<" .. network .. "_mac>'\n")
|
|
f:write("\tlist ports 'br0." .. config.vlan .. "'\n")
|
|
f:write("\nconfig interface " .. network .. "\n")
|
|
f:write("\toption device 'br-" .. network .. "'\n")
|
|
if network == "dtdlink" then
|
|
f:write("\toption proto 'static'\n")
|
|
f:write("\toption ipaddr '<" .. network .. "_ip>'\n")
|
|
f:write("\toption netmask '255.0.0.0'\n")
|
|
else
|
|
f:write("\toption proto '<" .. network .. "_proto>'\n")
|
|
f:write("\toption ipaddr '<" .. network .. "_ip>'\n")
|
|
f:write("\toption netmask '<" .. network .. "_mask>'\n")
|
|
end
|
|
if network == "lan" then
|
|
f:write("\toption dns '<wan_dns1> <wan_dns2>'\n")
|
|
end
|
|
if network == "wan" then
|
|
f:write("\toption gateway '<wan_gw>'\n")
|
|
end
|
|
f:close()
|
|
end
|
|
|
|
function update_legacy_wan_vlan(config)
|
|
local lines = {}
|
|
for line in io.lines("/etc/config.mesh/_setup")
|
|
do
|
|
if not line:match("^wan_intf = ") then
|
|
lines[#lines + 1] = line
|
|
end
|
|
end
|
|
|
|
for name, port in pairs(config.ports)
|
|
do
|
|
if port.tagged then
|
|
local wan_intf = ""
|
|
for dev in aredn.hardware.get_board_network_ifname("wan"):gmatch("%S+")
|
|
do
|
|
wan_intf = wan_intf .. " " .. dev:match("^([^%.]+)") .. "." .. config.vlan
|
|
end
|
|
if wan_intf ~= "" then
|
|
lines[#lines + 1] = "wan_intf =" .. wan_intf
|
|
end
|
|
break
|
|
end
|
|
end
|
|
|
|
local f = io.open("/etc/config.mesh/_setup", "w")
|
|
if f then
|
|
for _, line in ipairs(lines)
|
|
do
|
|
f:write(line .. "\n")
|
|
end
|
|
f:close()
|
|
end
|
|
end
|
|
|
|
function write_xlink_config(configs)
|
|
local f = io.open(xlink_file, "w")
|
|
f:write("# Generated by advancednetwork\n")
|
|
for _, config in ipairs(configs)
|
|
do
|
|
f:write("\nconfig bridge-vlan\n")
|
|
f:write("\toption device 'br0'\n")
|
|
f:write("\toption vlan '" .. config.vlan .. "'\n")
|
|
f:write("\tlist ports '" .. config.port .. ":t'\n")
|
|
f:write("\nconfig interface '" .. config.name .. "'\n")
|
|
f:write("\toption ifname 'br0." .. config.vlan .. "'\n")
|
|
if config.mac == "" then
|
|
config.mac = string.gsub("x2:xx:xx:xx:xx:xx", "x", function()
|
|
local i = math.random(1, 16)
|
|
return string.sub("0123456789ABCDEF", i, i)
|
|
end)
|
|
end
|
|
f:write("\toption macaddr '" .. config.mac .. "'\n")
|
|
f:write("\toption proto 'static'\n")
|
|
f:write("\toption ipaddr '" .. config.ipaddr .. "'\n")
|
|
f:write("\toption weight '" .. config.weight .. "'\n")
|
|
if config.netmask and config.netmask ~= "" then
|
|
f:write("\toption netmask '" .. config.netmask .. "'\n")
|
|
else
|
|
f:write("\toption netmask '255.255.255.255'\n")
|
|
end
|
|
if config.peer and config.peer ~= "" then
|
|
f:write("\toption peer '" .. config.peer .. "'\n")
|
|
f:write("\nconfig route\n")
|
|
f:write("\toption interface '" .. config.name .. "'\n")
|
|
f:write("\toption target '" .. config.peer .. "'\n")
|
|
end
|
|
end
|
|
f:close()
|
|
end
|
|
|
|
local get_board_type = aredn.hardware.get_board_type()
|
|
|
|
local layout = layouts[get_board_type]
|
|
if type(layout) == "function" then
|
|
layout = layout(get_board_type)
|
|
end
|
|
local configs = {}
|
|
|
|
local pending_restart = false
|
|
|
|
if os.getenv("REQUEST_METHOD") == "POST" then
|
|
require('luci.http')
|
|
local request = luci.http.Request(nixio.getenv(),
|
|
function()
|
|
local v = io.read(1024)
|
|
if not v then
|
|
io.close()
|
|
end
|
|
return v
|
|
end
|
|
)
|
|
local params = request:formvalue()
|
|
if params.op == "save" then
|
|
if params.configs then
|
|
local variables = {}
|
|
for line in io.lines("/etc/config.mesh/_setup")
|
|
do
|
|
if not (line:match("^%s*#") or line:match("^%s*$")) then
|
|
local k, v = line:match("^([^%s]*)%s*=%s*(.*)%s*$")
|
|
variables[k] = v
|
|
end
|
|
end
|
|
local configs = luci.jsonc.parse(params.configs)
|
|
for _, config in ipairs(configs)
|
|
do
|
|
write_user_config(config, variables)
|
|
if config.name == "wan" then
|
|
update_legacy_wan_vlan(config)
|
|
end
|
|
end
|
|
write_xlink_config(luci.jsonc.parse(params.xlinks))
|
|
pending_restart = true
|
|
end
|
|
elseif params.op == "defaults" then
|
|
for _, network in ipairs({ "dtdlink", "lan", "wan" })
|
|
do
|
|
nixio.fs.remove(base .. network .. ".network.user")
|
|
end
|
|
write_xlink_config({})
|
|
pending_restart = true
|
|
elseif params.op == "reboot" then
|
|
html.reboot()
|
|
end
|
|
end
|
|
|
|
local default_config = default_configs[get_board_type]
|
|
if type(default_config) == "function" then
|
|
default_config = default_config(get_board_type)
|
|
end
|
|
if default_config then
|
|
for _, network in ipairs({ "dtdlink", "lan", "wan" })
|
|
do
|
|
local config = read_user_config(network)
|
|
if not config then
|
|
for _, dconfig in ipairs(default_config)
|
|
do
|
|
if dconfig.name == network then
|
|
config = dconfig
|
|
break
|
|
end
|
|
end
|
|
end
|
|
configs[#configs + 1] = config
|
|
end
|
|
end
|
|
|
|
local xlinks = read_xlink_config()
|
|
|
|
http_header()
|
|
html.header("Advanced Network Configuration")
|
|
html.print("<body><center>")
|
|
html.alert_banner()
|
|
html.print([[
|
|
<style>
|
|
.title {
|
|
font-size: 24px;
|
|
padding: 5px 0;
|
|
text-align: center;
|
|
}
|
|
.control {
|
|
text-align: center;
|
|
padding-bottom: 8px;
|
|
}
|
|
.control input {
|
|
margin: 0 4px;
|
|
}
|
|
.msg {
|
|
font-size: 14px;
|
|
text-align: center;
|
|
font-weight: bold;
|
|
padding-bottom: 10px;
|
|
}
|
|
caption {
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
padding-bottom: 6px;
|
|
}
|
|
.ports {
|
|
margin: 10px 0 30px 0;
|
|
padding: 20px;
|
|
border: 1px solid #808080;
|
|
}
|
|
.ports tr:first-child {
|
|
font-variant: all-small-caps;
|
|
}
|
|
.ports td {
|
|
text-align: center;
|
|
vertical-align: middle;
|
|
}
|
|
.ports td:first-child {
|
|
text-align: left;
|
|
}
|
|
.h {
|
|
width: 50px;
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
}
|
|
.l {
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
width: 120px;
|
|
padding: 6px 0 2px 0;
|
|
}
|
|
.v {
|
|
font-size: 12px;
|
|
color: grey;
|
|
}
|
|
.v input {
|
|
padding-left: 2px;
|
|
font-size: 12px;
|
|
width: 65px;
|
|
border: 1px solid #c0c0c0;
|
|
}
|
|
.xlinks {
|
|
min-width: 610px;
|
|
margin: 30px auto;
|
|
padding: 20px;
|
|
border: 1px solid #808080;
|
|
}
|
|
.xlinks .h {
|
|
height: 30px;
|
|
vertical-align: top;
|
|
font-variant: all-small-caps;
|
|
}
|
|
.xlinks td {
|
|
width: 100px;
|
|
padding-left: 10px;
|
|
}
|
|
.xlinks td:nth-child(1), .xlinks td:nth-child(4), .xlinks td:nth-child(5) {
|
|
width: 40px;
|
|
}
|
|
.xlinks td:nth-child(6), .xlinks td:nth-child(7) {
|
|
width: 20px;
|
|
}
|
|
.xlinks td input {
|
|
width: 100%;
|
|
border: 1px solid transparent;
|
|
}
|
|
.xlinks td input.error {
|
|
border-color: red;
|
|
}
|
|
.xlinks td:nth-child(1) input, .xlinks td:nth-child(4) input {
|
|
text-align: right;
|
|
}
|
|
.xlinks td:nth-child(7) button {
|
|
width: 24px;
|
|
line-height: 14px;
|
|
background-color: #f0f0f0;
|
|
border: 1px solid #c0c0c0;
|
|
border-radius: 5px;
|
|
}
|
|
</style>
|
|
]])
|
|
html.print([[
|
|
<script>
|
|
const configs = ]] .. luci.jsonc.stringify(configs, true) .. [[;
|
|
configs.forEach(config => {
|
|
if (Array.isArray(config.ports)) {
|
|
config.ports = {}
|
|
}
|
|
});
|
|
function port_change(input, network, port, checked) {
|
|
const config = configs.find(c => c.name == network);
|
|
if (!config.tagged && checked) {
|
|
configs.forEach(config => {
|
|
if (!config.tagged && config.ports[port]) {
|
|
input.checked = false;
|
|
}
|
|
});
|
|
if (!input.checked) {
|
|
return;
|
|
}
|
|
}
|
|
if (checked) {
|
|
config.ports[port] = { tagged: config.tagged };
|
|
}
|
|
else {
|
|
delete config.ports[port]
|
|
}
|
|
}
|
|
function wan_vlan_change(input, value) {
|
|
const config = configs.find(c => c.name == "wan");
|
|
if (isNaN(value) || value < 4) {
|
|
if (config.tagged) {
|
|
config.tagged = false;
|
|
config.vlan = 1;
|
|
config.ports = {};
|
|
const boxes = document.querySelectorAll("#wan_row td input[type=checkbox]");
|
|
for (let i = 0; i < boxes.length; i++) {
|
|
boxes[i].checked = false;
|
|
}
|
|
}
|
|
input.value = "";
|
|
}
|
|
else {
|
|
config.tagged = true;
|
|
config.vlan = value;
|
|
for (port in config.ports) {
|
|
config.ports[port].tagged = true;
|
|
}
|
|
input.value = value;
|
|
}
|
|
validate();
|
|
}
|
|
function xlink_add() {
|
|
const tbody = document.querySelector("table.xlinks tbody");
|
|
const row = document.createElement("tr");
|
|
row.innerHTML = "<td><input type='text' value=''></td><td><input type='text' value=''></td><td><input type='text' value=''></td><td><input type='text' value='0'></td>]]
|
|
.. (function() local s = "<td" if #layout.ports <= 1 then s = s .. " style='display:none'" end s = s .. "><select>" for pos, port in ipairs(layout.ports) do s = s .. "<option value='" .. port .. "'>" .. pos .. "</option>" end return s .. "</select></td>" end)()
|
|
.. (function() local s = "<td><select><option value=''>-</option>" for v = 30, 16, -1 do s = s .. "<option value='" .. v .. "'>/" .. v .. "</option>" end return s .. "</select></td>" end)()
|
|
.. [[<td><input type='hidden' value=''><button onclick='xlink_remove(this)'>-</button></td>"
|
|
tbody.appendChild(row)
|
|
validate();
|
|
}
|
|
function xlink_remove(button) {
|
|
const row = button.parentNode.parentNode;
|
|
row.parentNode.removeChild(row)
|
|
validate();
|
|
}
|
|
function generate_xlinks() {
|
|
const xlinks = [];
|
|
const rows = document.querySelectorAll("table.xlinks tr");
|
|
for (let i = 1; i < rows.length; i++) {
|
|
const cells = rows[i].querySelectorAll("td input");
|
|
const selects = rows[i].querySelectorAll("select");
|
|
xlinks.push({
|
|
name: "xlink" + (i - 1),
|
|
vlan: parseInt(cells[0].value),
|
|
ipaddr: cells[1].value,
|
|
peer: cells[2].value,
|
|
weight: parseInt(cells[3].value),
|
|
port: selects[0].value,
|
|
netmask: {
|
|
"": "255.255.255.255",
|
|
"30": "255.255.255.252",
|
|
"29": "255.255.255.248",
|
|
"28": "255.255.255.240",
|
|
"27": "255.255.255.224",
|
|
"26": "255.255.255.192",
|
|
"25": "255.255.255.128",
|
|
"24": "255.255.255.0",
|
|
"23": "255.255.254.0",
|
|
"22": "255.255.252.0",
|
|
"21": "255.255.248.0",
|
|
"20": "255.255.240.0",
|
|
"19": "255.255.224.0",
|
|
"18": "255.255.192.0",
|
|
"17": "255.255.128.0",
|
|
"16": "255.255.0.0"
|
|
}[selects[1].value],
|
|
mac: cells[4].value
|
|
});
|
|
}
|
|
return xlinks;
|
|
}
|
|
function validate() {
|
|
let cansave = true;
|
|
const wan_config = configs.find(c => c.name == "wan");
|
|
let wan_vlan = "__untagged__";
|
|
if (wan_config && wan_config.tagged) {
|
|
wan_vlan = wan_config.vlan;
|
|
}
|
|
const rows = document.querySelectorAll("table.xlinks tr");
|
|
for (let i = 1; i < rows.length; i++) {
|
|
const cells = rows[i].querySelectorAll("td input");
|
|
if (/^\d+$/.test(cells[0].value) && parseInt(cells[0].value) > 3 && cells[0].value != wan_vlan) {
|
|
cells[0].classList.remove("error");
|
|
}
|
|
else {
|
|
cells[0].classList.add("error");
|
|
cansave = false;
|
|
}
|
|
if (/^\d+\.\d+\.\d+\.\d+$/.test(cells[1].value)) {
|
|
cells[1].classList.remove("error");
|
|
}
|
|
else {
|
|
cells[1].classList.add("error");
|
|
cansave = false;
|
|
}
|
|
if (cells[2].value == "" || /^\d+\.\d+\.\d+\.\d+$/.test(cells[2].value)) {
|
|
cells[2].classList.remove("error");
|
|
}
|
|
else {
|
|
cells[2].classList.add("error");
|
|
cansave = false;
|
|
}
|
|
if (/^\d+$/.test(cells[3].value)) {
|
|
cells[3].classList.remove("error");
|
|
}
|
|
else {
|
|
cells[3].classList.add("error");
|
|
cansave = false;
|
|
}
|
|
if (!cells[1].classList.contains("error") && !cells[1].classList.contains("error")) {
|
|
const ip = cells[1].value.split(".");
|
|
const peer = cells[2].value.split(".");
|
|
const i3 = parseInt(ip[3]);
|
|
const p3 = parseInt(peer[3]);
|
|
if (i3 == 0 || p3 == 0 || i3 == 255 || p3 == 255) {
|
|
cells[1].classList.add("error");
|
|
cells[2].classList.add("error");
|
|
cansave = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cansave) {
|
|
document.getElementById("save").disabled = null;
|
|
}
|
|
else {
|
|
document.getElementById("save").disabled = "disabled"
|
|
}
|
|
}
|
|
function save_config() {
|
|
document.getElementById('configs').value = JSON.stringify(configs);
|
|
document.getElementById('xlinks').value = JSON.stringify(generate_xlinks());
|
|
}
|
|
</script>
|
|
]])
|
|
html.print("<div>")
|
|
|
|
-- navbar
|
|
html.navbar_admin("advancednetwork")
|
|
|
|
html.print([[
|
|
<div class="control">
|
|
<form method="post" action="advancednetwork" enctype="multipart/form-data">
|
|
<input type="button" id="save" value="Save Changes" onclick="save_config(); document.getElementById('op').value = 'save'; form.submit()">
|
|
<input type="button" value="Default Values" onclick="document.getElementById('op').value = 'defaults'; form.submit()">
|
|
<input type="button" value="Reboot" style="font-weight:bold" onclick="document.getElementById('op').value = 'reboot'; form.submit()">
|
|
<input type="hidden" name="configs" id="configs" value="">
|
|
<input type="hidden" name="xlinks" id="xlinks" value="">
|
|
<input type="hidden" name="op" id="op" value="">
|
|
</form>
|
|
</div>
|
|
]])
|
|
|
|
if nixio.fs.stat("/tmp/reboot-required") then
|
|
html.print("<div class='msg'>Reboot is required for changes to take effect</div>")
|
|
end
|
|
|
|
if #layout.ports > 1 then
|
|
html.print([[<table class="ports" align="center"><caption>Ports</caption>]])
|
|
html.print("<tr>")
|
|
html.print("<td></td>")
|
|
for pos, port in ipairs(layout.ports)
|
|
do
|
|
html.print("<td class='h'>" .. pos .. "</td>")
|
|
end
|
|
html.print("</tr>")
|
|
for _, config in ipairs(configs)
|
|
do
|
|
html.print("<tr id='" .. config.name .. "_row'>")
|
|
html.print("<td>")
|
|
html.print("<div class='l'>" .. config.name .. "</div>")
|
|
if config.name == "wan" then
|
|
local value = config.tagged and config.vlan or ""
|
|
html.print("<div class='v'>vlan: <input type='text' placeholder='Untagged' onchange='wan_vlan_change(this, parseInt(this.value))' value='" .. value .. "'></div>")
|
|
elseif config.name == "dtdlink" then
|
|
html.print("<div class='v'>vlan: <span>2</span></div>")
|
|
else
|
|
html.print("<div class='v'>vlan: <span>Untagged</span></div>")
|
|
end
|
|
html.print("</td>")
|
|
for _, port in ipairs(layout.ports)
|
|
do
|
|
local checked = config.ports[port] and "checked" or ""
|
|
html.print("<td><input type='checkbox' " .. checked .. " onchange='port_change(this, \"" .. config.name .. "\", \"" .. port .. "\", this.checked)'></td>")
|
|
end
|
|
html.print("</tr>")
|
|
end
|
|
html.print([[</table>]])
|
|
end
|
|
|
|
html.print([[<table class="xlinks" align="center"><caption>Xlinks</caption>]])
|
|
html.print([[<tr class="h"><td>vlan</td><td>ip address</td><td>peer address</td><td>weight</td>]])
|
|
if #layout.ports > 1 then
|
|
html.print([[<td> port</td>]])
|
|
else
|
|
html.print([[<td style='display:none'> port</td>]])
|
|
end
|
|
html.print([[<td>cidr</td>]])
|
|
html.print([[<td><button onclick="xlink_add()">+</button></td></tr>]])
|
|
for _, xlink in ipairs(xlinks)
|
|
do
|
|
html.print("<tr id='" .. xlink.name .. "'>")
|
|
html.print("<td><input type='text' value='" .. xlink.vlan .. "'></td><td><input type='text' value='" .. xlink.ipaddr .. "'></td><td><input type='text' value='" .. (xlink.peer or "") .. "'></td>")
|
|
html.print("<td><input type='text' value='" .. xlink.weight .. "'></td>")
|
|
html.print("<td" .. (#layout.ports <= 1 and " style='display:none'" or "") .. "><select>")
|
|
for pos, port in ipairs(layout.ports)
|
|
do
|
|
html.print("<option value='" .. port .. "'" .. (xlink.port == port and " selected" or "") .. ">" .. pos .. "</option>")
|
|
end
|
|
html.print("</select></td>")
|
|
html.print("<td><select>")
|
|
html.print("<option value=''>-</option>")
|
|
local cidr = netmask_to_cidr(xlink.netmask)
|
|
for v = 30, 16, -1
|
|
do
|
|
html.print("<option value='" .. v .. "'" .. (cidr == v and " selected" or "") .. ">/" .. v .. "</option>")
|
|
end
|
|
html.print("</select></td>")
|
|
html.print("<td><input type='hidden' value='" .. xlink.mac .. "'><button onclick='xlink_remove(this)'>-</button></td>")
|
|
html.print("</tr>")
|
|
end
|
|
html.print([[</table>]])
|
|
html.print([[<script>document.querySelector(".xlinks").addEventListener("change", validate);</script>]])
|
|
|
|
print("</div></center>")
|
|
html.footer()
|
|
html.print("</body></html>")
|
|
http_footer()
|
|
|
|
if pending_restart then
|
|
os.execute("/usr/local/bin/node-setup > /dev/null 2>&1")
|
|
os.execute("/usr/local/bin/restart-services.sh > /dev/null 2>&1")
|
|
end
|