summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-mod-admin-full/luasrc/model
diff options
context:
space:
mode:
authorJo-Philipp Wich <jow@openwrt.org>2014-12-03 15:17:05 +0100
committerJo-Philipp Wich <jow@openwrt.org>2015-01-08 16:26:20 +0100
commit1bb4822dca6113f73e3bc89e2acf15935e6f8e92 (patch)
tree35e16f100466e4e00657199b38bb3d87d52bf73f /modules/luci-mod-admin-full/luasrc/model
parent9edd0e46c3f880727738ce8ca6ff1c8b85f99ef4 (diff)
Rework LuCI build system
* Rename subdirectories to their repective OpenWrt package names * Make each LuCI module its own standalone package * Deploy a shared luci.mk which is used by each module Makefile Signed-off-by: Jo-Philipp Wich <jow@openwrt.org>
Diffstat (limited to 'modules/luci-mod-admin-full/luasrc/model')
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua264
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/hosts.lua41
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/iface_add.lua104
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/ifaces.lua527
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/network.lua82
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/proto_ahcp.lua76
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/routes.lua86
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/vlan.lua315
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua956
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi_add.lua184
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_status/processes.lua44
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/admin.lua130
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/backupfiles.lua92
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/buttons.lua38
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/crontab.lua38
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab.lua156
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab/mount.lua108
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab/swap.lua65
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/ipkg.lua36
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/leds.lua129
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/startup.lua109
-rw-r--r--modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/system.lua228
22 files changed, 3808 insertions, 0 deletions
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua
new file mode 100644
index 000000000..bd6f403c5
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua
@@ -0,0 +1,264 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+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
+
+$Id$
+]]--
+
+local sys = require "luci.sys"
+
+m = Map("dhcp", translate("DHCP and DNS"),
+ translate("Dnsmasq is a combined <abbr title=\"Dynamic Host Configuration Protocol" ..
+ "\">DHCP</abbr>-Server and <abbr title=\"Domain Name System\">DNS</abbr>-" ..
+ "Forwarder for <abbr title=\"Network Address Translation\">NAT</abbr> " ..
+ "firewalls"))
+
+s = m:section(TypedSection, "dnsmasq", translate("Server Settings"))
+s.anonymous = true
+s.addremove = false
+
+s:tab("general", translate("General Settings"))
+s:tab("files", translate("Resolv and Hosts Files"))
+s:tab("tftp", translate("TFTP Settings"))
+s:tab("advanced", translate("Advanced Settings"))
+
+s:taboption("general", Flag, "domainneeded",
+ translate("Domain required"),
+ translate("Don't forward <abbr title=\"Domain Name System\">DNS</abbr>-Requests without " ..
+ "<abbr title=\"Domain Name System\">DNS</abbr>-Name"))
+
+s:taboption("general", Flag, "authoritative",
+ translate("Authoritative"),
+ translate("This is the only <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</" ..
+ "abbr> in the local network"))
+
+
+s:taboption("files", Flag, "readethers",
+ translate("Use <code>/etc/ethers</code>"),
+ translate("Read <code>/etc/ethers</code> to configure the <abbr title=\"Dynamic Host " ..
+ "Configuration Protocol\">DHCP</abbr>-Server"))
+
+s:taboption("files", Value, "leasefile",
+ translate("Leasefile"),
+ translate("file where given <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</" ..
+ "abbr>-leases will be stored"))
+
+s:taboption("files", Flag, "noresolv",
+ translate("Ignore resolve file")).optional = true
+
+rf = s:taboption("files", Value, "resolvfile",
+ translate("Resolve file"),
+ translate("local <abbr title=\"Domain Name System\">DNS</abbr> file"))
+
+rf:depends("noresolv", "")
+rf.optional = true
+
+
+s:taboption("files", Flag, "nohosts",
+ translate("Ignore Hosts files")).optional = true
+
+hf = s:taboption("files", DynamicList, "addnhosts",
+ translate("Additional Hosts files"))
+
+hf:depends("nohosts", "")
+hf.optional = true
+
+
+s:taboption("advanced", Flag, "boguspriv",
+ translate("Filter private"),
+ translate("Do not forward reverse lookups for local networks"))
+
+s:taboption("advanced", Flag, "filterwin2k",
+ translate("Filter useless"),
+ translate("Do not forward requests that cannot be answered by public name servers"))
+
+
+s:taboption("advanced", Flag, "localise_queries",
+ translate("Localise queries"),
+ translate("Localise hostname depending on the requesting subnet if multiple IPs are available"))
+
+s:taboption("general", Value, "local",
+ translate("Local server"),
+ translate("Local domain specification. Names matching this domain are never forwarded and are resolved from DHCP or hosts files only"))
+
+s:taboption("general", Value, "domain",
+ translate("Local domain"),
+ translate("Local domain suffix appended to DHCP names and hosts file entries"))
+
+s:taboption("advanced", Flag, "expandhosts",
+ translate("Expand hosts"),
+ translate("Add local domain suffix to names served from hosts files"))
+
+s:taboption("advanced", Flag, "nonegcache",
+ translate("No negative cache"),
+ translate("Do not cache negative replies, e.g. for not existing domains"))
+
+s:taboption("advanced", Flag, "strictorder",
+ translate("Strict order"),
+ translate("<abbr title=\"Domain Name System\">DNS</abbr> servers will be queried in the " ..
+ "order of the resolvfile")).optional = true
+
+
+bn = s:taboption("advanced", DynamicList, "bogusnxdomain", translate("Bogus NX Domain Override"),
+ translate("List of hosts that supply bogus NX domain results"))
+
+bn.optional = true
+bn.placeholder = "67.215.65.132"
+
+
+s:taboption("general", Flag, "logqueries",
+ translate("Log queries"),
+ translate("Write received DNS requests to syslog")).optional = true
+
+df = s:taboption("general", DynamicList, "server", translate("DNS forwardings"),
+ translate("List of <abbr title=\"Domain Name System\">DNS</abbr> " ..
+ "servers to forward requests to"))
+
+df.optional = true
+df.placeholder = "/example.org/10.1.2.3"
+
+
+rp = s:taboption("general", Flag, "rebind_protection",
+ translate("Rebind protection"),
+ translate("Discard upstream RFC1918 responses"))
+
+rp.rmempty = false
+
+
+rl = s:taboption("general", Flag, "rebind_localhost",
+ translate("Allow localhost"),
+ translate("Allow upstream responses in the 127.0.0.0/8 range, e.g. for RBL services"))
+
+rl:depends("rebind_protection", "1")
+
+
+rd = s:taboption("general", DynamicList, "rebind_domain",
+ translate("Domain whitelist"),
+ translate("List of domains to allow RFC1918 responses for"))
+
+rd:depends("rebind_protection", "1")
+rd.datatype = "host"
+rd.placeholder = "ihost.netflix.com"
+
+
+pt = s:taboption("advanced", Value, "port",
+ translate("<abbr title=\"Domain Name System\">DNS</abbr> server port"),
+ translate("Listening port for inbound DNS queries"))
+
+pt.optional = true
+pt.datatype = "port"
+pt.placeholder = 53
+
+
+qp = s:taboption("advanced", Value, "queryport",
+ translate("<abbr title=\"Domain Name System\">DNS</abbr> query port"),
+ translate("Fixed source port for outbound DNS queries"))
+
+qp.optional = true
+qp.datatype = "port"
+qp.placeholder = translate("any")
+
+
+lm = s:taboption("advanced", Value, "dhcpleasemax",
+ translate("<abbr title=\"maximal\">Max.</abbr> <abbr title=\"Dynamic Host Configuration " ..
+ "Protocol\">DHCP</abbr> leases"),
+ translate("Maximum allowed number of active DHCP leases"))
+
+lm.optional = true
+lm.datatype = "uinteger"
+lm.placeholder = translate("unlimited")
+
+
+em = s:taboption("advanced", Value, "ednspacket_max",
+ translate("<abbr title=\"maximal\">Max.</abbr> <abbr title=\"Extension Mechanisms for " ..
+ "Domain Name System\">EDNS0</abbr> packet size"),
+ translate("Maximum allowed size of EDNS.0 UDP packets"))
+
+em.optional = true
+em.datatype = "uinteger"
+em.placeholder = 1280
+
+
+cq = s:taboption("advanced", Value, "dnsforwardmax",
+ translate("<abbr title=\"maximal\">Max.</abbr> concurrent queries"),
+ translate("Maximum allowed number of concurrent DNS queries"))
+
+cq.optional = true
+cq.datatype = "uinteger"
+cq.placeholder = 150
+
+
+s:taboption("tftp", Flag, "enable_tftp",
+ translate("Enable TFTP server")).optional = true
+
+tr = s:taboption("tftp", Value, "tftp_root",
+ translate("TFTP server root"),
+ translate("Root directory for files served via TFTP"))
+
+tr.optional = true
+tr:depends("enable_tftp", "1")
+tr.placeholder = "/"
+
+
+db = s:taboption("tftp", Value, "dhcp_boot",
+ translate("Network boot image"),
+ translate("Filename of the boot image advertised to clients"))
+
+db.optional = true
+db:depends("enable_tftp", "1")
+db.placeholder = "pxelinux.0"
+
+
+m:section(SimpleSection).template = "admin_network/lease_status"
+
+s = m:section(TypedSection, "host", translate("Static Leases"),
+ translate("Static leases are used to assign fixed IP addresses and symbolic hostnames to " ..
+ "DHCP clients. They are also required for non-dynamic interface configurations where " ..
+ "only hosts with a corresponding lease are served.") .. "<br />" ..
+ translate("Use the <em>Add</em> Button to add a new lease entry. The <em>MAC-Address</em> " ..
+ "indentifies the host, the <em>IPv4-Address</em> specifies to the fixed address to " ..
+ "use and the <em>Hostname</em> is assigned as symbolic name to the requesting host."))
+
+s.addremove = true
+s.anonymous = true
+s.template = "cbi/tblsection"
+
+name = s:option(Value, "name", translate("Hostname"))
+name.datatype = "hostname"
+name.rmempty = true
+
+mac = s:option(Value, "mac", translate("<abbr title=\"Media Access Control\">MAC</abbr>-Address"))
+mac.datatype = "list(macaddr)"
+mac.rmempty = true
+
+ip = s:option(Value, "ip", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Address"))
+ip.datatype = "or(ip4addr,'ignore')"
+
+hostid = s:option(Value, "hostid", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Suffix (hex)"))
+
+sys.net.arptable(function(entry)
+ ip:value(entry["IP address"])
+ mac:value(
+ entry["HW address"],
+ entry["HW address"] .. " (" .. entry["IP address"] .. ")"
+ )
+end)
+
+function ip.validate(self, value, section)
+ local m = mac:formvalue(section) or ""
+ local n = name:formvalue(section) or ""
+ if value and #n == 0 and #m == 0 then
+ return nil, translate("One of hostname or mac address must be specified!")
+ end
+ return Value.validate(self, value, section)
+end
+
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/hosts.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/hosts.lua
new file mode 100644
index 000000000..b4fee8cf0
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/hosts.lua
@@ -0,0 +1,41 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2010 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+require("luci.sys")
+require("luci.util")
+m = Map("dhcp", translate("Hostnames"))
+
+s = m:section(TypedSection, "domain", translate("Host entries"))
+s.addremove = true
+s.anonymous = true
+s.template = "cbi/tblsection"
+
+hn = s:option(Value, "name", translate("Hostname"))
+hn.datatype = "hostname"
+hn.rmempty = true
+
+ip = s:option(Value, "ip", translate("IP address"))
+ip.datatype = "ipaddr"
+ip.rmempty = true
+
+local arptable = luci.sys.net.arptable() or {}
+for i, dataset in ipairs(arptable) do
+ ip:value(
+ dataset["IP address"],
+ "%s (%s)" %{ dataset["IP address"], dataset["HW address"] }
+ )
+end
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/iface_add.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/iface_add.lua
new file mode 100644
index 000000000..92026b157
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/iface_add.lua
@@ -0,0 +1,104 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2009-2010 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+
+]]--
+
+local nw = require "luci.model.network".init()
+local fw = require "luci.model.firewall".init()
+local utl = require "luci.util"
+local uci = require "luci.model.uci".cursor()
+
+m = SimpleForm("network", translate("Create Interface"))
+m.redirect = luci.dispatcher.build_url("admin/network/network")
+m.reset = false
+
+newnet = m:field(Value, "_netname", translate("Name of the new interface"),
+ translate("The allowed characters are: <code>A-Z</code>, <code>a-z</code>, " ..
+ "<code>0-9</code> and <code>_</code>"
+ ))
+
+newnet:depends("_attach", "")
+newnet.default = arg[1] and "net_" .. arg[1]:gsub("[^%w_]+", "_")
+newnet.datatype = "uciname"
+
+newproto = m:field(ListValue, "_netproto", translate("Protocol of the new interface"))
+
+netbridge = m:field(Flag, "_bridge", translate("Create a bridge over multiple interfaces"))
+
+
+sifname = m:field(Value, "_ifname", translate("Cover the following interface"))
+
+sifname.widget = "radio"
+sifname.template = "cbi/network_ifacelist"
+sifname.nobridges = true
+
+
+mifname = m:field(Value, "_ifnames", translate("Cover the following interfaces"))
+
+mifname.widget = "checkbox"
+mifname.template = "cbi/network_ifacelist"
+mifname.nobridges = true
+
+
+local _, p
+for _, p in ipairs(nw:get_protocols()) do
+ if p:is_installed() then
+ newproto:value(p:proto(), p:get_i18n())
+ if not p:is_virtual() then netbridge:depends("_netproto", p:proto()) end
+ if not p:is_floating() then
+ sifname:depends({ _bridge = "", _netproto = p:proto()})
+ mifname:depends({ _bridge = "1", _netproto = p:proto()})
+ end
+ end
+end
+
+function newproto.validate(self, value, section)
+ local name = newnet:formvalue(section)
+ if not name or #name == 0 then
+ newnet:add_error(section, translate("No network name specified"))
+ elseif m:get(name) then
+ newnet:add_error(section, translate("The given network name is not unique"))
+ end
+
+ local proto = nw:get_protocol(value)
+ if proto and not proto:is_floating() then
+ local br = (netbridge:formvalue(section) == "1")
+ local ifn = br and mifname:formvalue(section) or sifname:formvalue(section)
+ for ifn in utl.imatch(ifn) do
+ return value
+ end
+ return nil, translate("The selected protocol needs a device assigned")
+ end
+ return value
+end
+
+function newproto.write(self, section, value)
+ local name = newnet:formvalue(section)
+ if name and #name > 0 then
+ local br = (netbridge:formvalue(section) == "1") and "bridge" or nil
+ local net = nw:add_network(name, { proto = value, type = br })
+ if net then
+ local ifn
+ for ifn in utl.imatch(
+ br and mifname:formvalue(section) or sifname:formvalue(section)
+ ) do
+ net:add_interface(ifn)
+ end
+ nw:save("network")
+ nw:save("wireless")
+ end
+ luci.http.redirect(luci.dispatcher.build_url("admin/network/network", name))
+ end
+end
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/ifaces.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/ifaces.lua
new file mode 100644
index 000000000..9be5ff1a7
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/ifaces.lua
@@ -0,0 +1,527 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008-2011 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+local fs = require "nixio.fs"
+local ut = require "luci.util"
+local pt = require "luci.tools.proto"
+local nw = require "luci.model.network"
+local fw = require "luci.model.firewall"
+
+arg[1] = arg[1] or ""
+
+local has_dnsmasq = fs.access("/etc/config/dhcp")
+local has_firewall = fs.access("/etc/config/firewall")
+
+m = Map("network", translate("Interfaces") .. " - " .. arg[1]:upper(), translate("On this page you can configure the network interfaces. You can bridge several interfaces by ticking the \"bridge interfaces\" field and enter the names of several network interfaces separated by spaces. You can also use <abbr title=\"Virtual Local Area Network\">VLAN</abbr> notation <samp>INTERFACE.VLANNR</samp> (<abbr title=\"for example\">e.g.</abbr>: <samp>eth0.1</samp>)."))
+m:chain("wireless")
+
+if has_firewall then
+ m:chain("firewall")
+end
+
+nw.init(m.uci)
+fw.init(m.uci)
+
+
+local net = nw:get_network(arg[1])
+
+local function backup_ifnames(is_bridge)
+ if not net:is_floating() and not m:get(net:name(), "_orig_ifname") then
+ local ifcs = net:get_interfaces() or { net:get_interface() }
+ if ifcs then
+ local _, ifn
+ local ifns = { }
+ for _, ifn in ipairs(ifcs) do
+ ifns[#ifns+1] = ifn:name()
+ end
+ if #ifns > 0 then
+ m:set(net:name(), "_orig_ifname", table.concat(ifns, " "))
+ m:set(net:name(), "_orig_bridge", tostring(net:is_bridge()))
+ end
+ end
+ end
+end
+
+
+-- redirect to overview page if network does not exist anymore (e.g. after a revert)
+if not net then
+ luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
+ return
+end
+
+-- protocol switch was requested, rebuild interface config and reload page
+if m:formvalue("cbid.network.%s._switch" % net:name()) then
+ -- get new protocol
+ local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-"
+ local proto = nw:get_protocol(ptype, net:name())
+ if proto then
+ -- backup default
+ backup_ifnames()
+
+ -- if current proto is not floating and target proto is not floating,
+ -- then attempt to retain the ifnames
+ --error(net:proto() .. " > " .. proto:proto())
+ if not net:is_floating() and not proto:is_floating() then
+ -- if old proto is a bridge and new proto not, then clip the
+ -- interface list to the first ifname only
+ if net:is_bridge() and proto:is_virtual() then
+ local _, ifn
+ local first = true
+ for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do
+ if first then
+ first = false
+ else
+ net:del_interface(ifn)
+ end
+ end
+ m:del(net:name(), "type")
+ end
+
+ -- if the current proto is floating, the target proto not floating,
+ -- then attempt to restore ifnames from backup
+ elseif net:is_floating() and not proto:is_floating() then
+ -- if we have backup data, then re-add all orphaned interfaces
+ -- from it and restore the bridge choice
+ local br = (m:get(net:name(), "_orig_bridge") == "true")
+ local ifn
+ local ifns = { }
+ for ifn in ut.imatch(m:get(net:name(), "_orig_ifname")) do
+ ifn = nw:get_interface(ifn)
+ if ifn and not ifn:get_network() then
+ proto:add_interface(ifn)
+ if not br then
+ break
+ end
+ end
+ end
+ if br then
+ m:set(net:name(), "type", "bridge")
+ end
+
+ -- in all other cases clear the ifnames
+ else
+ local _, ifc
+ for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do
+ net:del_interface(ifc)
+ end
+ m:del(net:name(), "type")
+ end
+
+ -- clear options
+ local k, v
+ for k, v in pairs(m:get(net:name())) do
+ if k:sub(1,1) ~= "." and
+ k ~= "type" and
+ k ~= "ifname" and
+ k ~= "_orig_ifname" and
+ k ~= "_orig_bridge"
+ then
+ m:del(net:name(), k)
+ end
+ end
+
+ -- set proto
+ m:set(net:name(), "proto", proto:proto())
+ m.uci:save("network")
+ m.uci:save("wireless")
+
+ -- reload page
+ luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
+ return
+ end
+end
+
+-- dhcp setup was requested, create section and reload page
+if m:formvalue("cbid.dhcp._enable._enable") then
+ m.uci:section("dhcp", "dhcp", nil, {
+ interface = arg[1],
+ start = "100",
+ limit = "150",
+ leasetime = "12h"
+ })
+
+ m.uci:save("dhcp")
+ luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
+ return
+end
+
+local ifc = net:get_interface()
+
+s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
+s.addremove = false
+
+s:tab("general", translate("General Setup"))
+s:tab("advanced", translate("Advanced Settings"))
+s:tab("physical", translate("Physical Settings"))
+
+if has_firewall then
+ s:tab("firewall", translate("Firewall Settings"))
+end
+
+
+st = s:taboption("general", DummyValue, "__status", translate("Status"))
+
+local function set_status()
+ -- if current network is empty, print a warning
+ if not net:is_floating() and net:is_empty() then
+ st.template = "cbi/dvalue"
+ st.network = nil
+ st.value = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
+ else
+ st.template = "admin_network/iface_status"
+ st.network = arg[1]
+ st.value = nil
+ end
+end
+
+m.on_init = set_status
+m.on_after_save = set_status
+
+
+p = s:taboption("general", ListValue, "proto", translate("Protocol"))
+p.default = net:proto()
+
+
+if not net:is_installed() then
+ p_install = s:taboption("general", Button, "_install")
+ p_install.title = translate("Protocol support is not installed")
+ p_install.inputtitle = translate("Install package %q" % net:opkg_package())
+ p_install.inputstyle = "apply"
+ p_install:depends("proto", net:proto())
+
+ function p_install.write()
+ return luci.http.redirect(
+ luci.dispatcher.build_url("admin/system/packages") ..
+ "?submit=1&install=%s" % net:opkg_package()
+ )
+ end
+end
+
+
+p_switch = s:taboption("general", Button, "_switch")
+p_switch.title = translate("Really switch protocol?")
+p_switch.inputtitle = translate("Switch protocol")
+p_switch.inputstyle = "apply"
+
+local _, pr
+for _, pr in ipairs(nw:get_protocols()) do
+ p:value(pr:proto(), pr:get_i18n())
+ if pr:proto() ~= net:proto() then
+ p_switch:depends("proto", pr:proto())
+ end
+end
+
+
+auto = s:taboption("advanced", Flag, "auto", translate("Bring up on boot"))
+auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
+
+delegate = s:taboption("advanced", Flag, "delegate", translate("Use builtin IPv6-management"))
+delegate.default = delegate.enabled
+
+
+if not net:is_virtual() then
+ br = s:taboption("physical", Flag, "type", translate("Bridge interfaces"), translate("creates a bridge over specified interface(s)"))
+ br.enabled = "bridge"
+ br.rmempty = true
+ br:depends("proto", "static")
+ br:depends("proto", "dhcp")
+ br:depends("proto", "none")
+
+ stp = s:taboption("physical", Flag, "stp", translate("Enable <abbr title=\"Spanning Tree Protocol\">STP</abbr>"),
+ translate("Enables the Spanning Tree Protocol on this bridge"))
+ stp:depends("type", "bridge")
+ stp.rmempty = true
+end
+
+
+if not net:is_floating() then
+ ifname_single = s:taboption("physical", Value, "ifname_single", translate("Interface"))
+ ifname_single.template = "cbi/network_ifacelist"
+ ifname_single.widget = "radio"
+ ifname_single.nobridges = true
+ ifname_single.rmempty = false
+ ifname_single.network = arg[1]
+ ifname_single:depends("type", "")
+
+ function ifname_single.cfgvalue(self, s)
+ -- let the template figure out the related ifaces through the network model
+ return nil
+ end
+
+ function ifname_single.write(self, s, val)
+ local i
+ local new_ifs = { }
+ local old_ifs = { }
+
+ for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
+ old_ifs[#old_ifs+1] = i:name()
+ end
+
+ for i in ut.imatch(val) do
+ new_ifs[#new_ifs+1] = i
+
+ -- if this is not a bridge, only assign first interface
+ if self.option == "ifname_single" then
+ break
+ end
+ end
+
+ table.sort(old_ifs)
+ table.sort(new_ifs)
+
+ for i = 1, math.max(#old_ifs, #new_ifs) do
+ if old_ifs[i] ~= new_ifs[i] then
+ backup_ifnames()
+ for i = 1, #old_ifs do
+ net:del_interface(old_ifs[i])
+ end
+ for i = 1, #new_ifs do
+ net:add_interface(new_ifs[i])
+ end
+ break
+ end
+ end
+ end
+end
+
+
+if not net:is_virtual() then
+ ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface"))
+ ifname_multi.template = "cbi/network_ifacelist"
+ ifname_multi.nobridges = true
+ ifname_multi.rmempty = false
+ ifname_multi.network = arg[1]
+ ifname_multi.widget = "checkbox"
+ ifname_multi:depends("type", "bridge")
+ ifname_multi.cfgvalue = ifname_single.cfgvalue
+ ifname_multi.write = ifname_single.write
+end
+
+
+if has_firewall then
+ fwzone = s:taboption("firewall", Value, "_fwzone",
+ translate("Create / Assign firewall-zone"),
+ translate("Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it."))
+
+ fwzone.template = "cbi/firewall_zonelist"
+ fwzone.network = arg[1]
+ fwzone.rmempty = false
+
+ function fwzone.cfgvalue(self, section)
+ self.iface = section
+ local z = fw:get_zone_by_network(section)
+ return z and z:name()
+ end
+
+ function fwzone.write(self, section, value)
+ local zone = fw:get_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
+ fw:del_network(section)
+ zone:add_network(section)
+ end
+ end
+end
+
+
+function p.write() end
+function p.remove() end
+function p.validate(self, value, section)
+ if value == net:proto() then
+ if not net:is_floating() and net:is_empty() then
+ local ifn = ((br and (br:formvalue(section) == "bridge"))
+ and ifname_multi:formvalue(section)
+ or ifname_single:formvalue(section))
+
+ for ifn in ut.imatch(ifn) do
+ return value
+ end
+ return nil, translate("The selected protocol needs a device assigned")
+ end
+ end
+ return value
+end
+
+
+local form, ferr = loadfile(
+ ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
+)
+
+if not form then
+ s:taboption("general", DummyValue, "_error",
+ translate("Missing protocol extension for proto %q" % net:proto())
+ ).value = ferr
+else
+ setfenv(form, getfenv(1))(m, s, net)
+end
+
+
+local _, field
+for _, field in ipairs(s.children) do
+ if field ~= st and field ~= p and field ~= p_install and field ~= p_switch then
+ if next(field.deps) then
+ local _, dep
+ for _, dep in ipairs(field.deps) do
+ dep.deps.proto = net:proto()
+ end
+ else
+ field:depends("proto", net:proto())
+ end
+ end
+end
+
+
+--
+-- Display DNS settings if dnsmasq is available
+--
+
+if has_dnsmasq and net:proto() == "static" then
+ m2 = Map("dhcp", "", "")
+
+ local has_section = false
+
+ m2.uci:foreach("dhcp", "dhcp", function(s)
+ if s.interface == arg[1] then
+ has_section = true
+ return false
+ end
+ end)
+
+ if not has_section and has_dnsmasq then
+
+ s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
+ s.anonymous = true
+ s.cfgsections = function() return { "_enable" } end
+
+ x = s:option(Button, "_enable")
+ x.title = translate("No DHCP Server configured for this interface")
+ x.inputtitle = translate("Setup DHCP Server")
+ x.inputstyle = "apply"
+
+ elseif has_section then
+
+ s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
+ s.addremove = false
+ s.anonymous = true
+ s:tab("general", translate("General Setup"))
+ s:tab("advanced", translate("Advanced Settings"))
+ s:tab("ipv6", translate("IPv6 Settings"))
+
+ function s.filter(self, section)
+ return m2.uci:get("dhcp", section, "interface") == arg[1]
+ end
+
+ local ignore = s:taboption("general", Flag, "ignore",
+ translate("Ignore interface"),
+ translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
+ "this interface."))
+
+ local start = s:taboption("general", Value, "start", translate("Start"),
+ translate("Lowest leased address as offset from the network address."))
+ start.optional = true
+ start.datatype = "or(uinteger,ip4addr)"
+ start.default = "100"
+
+ local limit = s:taboption("general", Value, "limit", translate("Limit"),
+ translate("Maximum number of leased addresses."))
+ limit.optional = true
+ limit.datatype = "uinteger"
+ limit.default = "150"
+
+ local ltime = s:taboption("general", Value, "leasetime", translate("Leasetime"),
+ translate("Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>)."))
+ ltime.rmempty = true
+ ltime.default = "12h"
+
+ local dd = s:taboption("advanced", Flag, "dynamicdhcp",
+ translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
+ translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
+ "clients having static leases will be served."))
+ dd.default = dd.enabled
+
+ s:taboption("advanced", Flag, "force", translate("Force"),
+ translate("Force DHCP on this network even if another server is detected."))
+
+ -- XXX: is this actually useful?
+ --s:taboption("advanced", Value, "name", translate("Name"),
+ -- translate("Define a name for this network."))
+
+ mask = s:taboption("advanced", Value, "netmask",
+ translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
+ translate("Override the netmask sent to clients. Normally it is calculated " ..
+ "from the subnet that is served."))
+
+ mask.optional = true
+ mask.datatype = "ip4addr"
+
+ s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
+ translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
+ "192.168.2.2</code>\" which advertises different DNS servers to clients."))
+
+ for i, n in ipairs(s.children) do
+ if n ~= ignore then
+ n:depends("ignore", "")
+ end
+ end
+
+ o = s:taboption("ipv6", ListValue, "ra", translate("Router Advertisement-Service"))
+ o:value("", translate("disabled"))
+ o:value("server", translate("server mode"))
+ o:value("relay", translate("relay mode"))
+ o:value("hybrid", translate("hybrid mode"))
+
+ o = s:taboption("ipv6", ListValue, "dhcpv6", translate("DHCPv6-Service"))
+ o:value("", translate("disabled"))
+ o:value("server", translate("server mode"))
+ o:value("relay", translate("relay mode"))
+ o:value("hybrid", translate("hybrid mode"))
+
+ o = s:taboption("ipv6", ListValue, "ndp", translate("NDP-Proxy"))
+ o:value("", translate("disabled"))
+ o:value("relay", translate("relay mode"))
+ o:value("hybrid", translate("hybrid mode"))
+
+ o = s:taboption("ipv6", ListValue, "ra_management", translate("DHCPv6-Mode"))
+ o:value("", translate("stateless"))
+ o:value("1", translate("stateless + stateful"))
+ o:value("2", translate("stateful-only"))
+ o:depends("dhcpv6", "server")
+ o:depends("dhcpv6", "hybrid")
+ o.default = "1"
+
+ o = s:taboption("ipv6", Flag, "ra_default", translate("Always announce default router"),
+ translate("Announce as default router even if no public prefix is available."))
+ o:depends("ra", "server")
+ o:depends("ra", "hybrid")
+
+ s:taboption("ipv6", DynamicList, "dns", translate("Announced DNS servers"))
+ s:taboption("ipv6", DynamicList, "domain", translate("Announced DNS domains"))
+
+ else
+ m2 = nil
+ end
+end
+
+
+return m, m2
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/network.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/network.lua
new file mode 100644
index 000000000..f2984b581
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/network.lua
@@ -0,0 +1,82 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.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
+
+$Id$
+]]--
+
+local fs = require "nixio.fs"
+
+m = Map("network", translate("Interfaces"))
+m.pageaction = false
+m:section(SimpleSection).template = "admin_network/iface_overview"
+
+-- Show ATM bridge section if we have the capabilities
+if fs.access("/usr/sbin/br2684ctl") then
+ atm = m:section(TypedSection, "atm-bridge", translate("ATM Bridges"),
+ translate("ATM bridges expose encapsulated ethernet in AAL5 " ..
+ "connections as virtual Linux network interfaces which can " ..
+ "be used in conjunction with DHCP or PPP to dial into the " ..
+ "provider network."))
+
+ atm.addremove = true
+ atm.anonymous = true
+
+ atm.create = function(self, section)
+ local sid = TypedSection.create(self, section)
+ local max_unit = -1
+
+ m.uci:foreach("network", "atm-bridge",
+ function(s)
+ local u = tonumber(s.unit)
+ if u ~= nil and u > max_unit then
+ max_unit = u
+ end
+ end)
+
+ m.uci:set("network", sid, "unit", max_unit + 1)
+ m.uci:set("network", sid, "atmdev", 0)
+ m.uci:set("network", sid, "encaps", "llc")
+ m.uci:set("network", sid, "payload", "bridged")
+ m.uci:set("network", sid, "vci", 35)
+ m.uci:set("network", sid, "vpi", 8)
+
+ return sid
+ end
+
+ atm:tab("general", translate("General Setup"))
+ atm:tab("advanced", translate("Advanced Settings"))
+
+ vci = atm:taboption("general", Value, "vci", translate("ATM Virtual Channel Identifier (VCI)"))
+ vpi = atm:taboption("general", Value, "vpi", translate("ATM Virtual Path Identifier (VPI)"))
+ encaps = atm:taboption("general", ListValue, "encaps", translate("Encapsulation mode"))
+ encaps:value("llc", translate("LLC"))
+ encaps:value("vc", translate("VC-Mux"))
+
+ atmdev = atm:taboption("advanced", Value, "atmdev", translate("ATM device number"))
+ unit = atm:taboption("advanced", Value, "unit", translate("Bridge unit number"))
+ payload = atm:taboption("advanced", ListValue, "payload", translate("Forwarding mode"))
+ payload:value("bridged", translate("bridged"))
+ payload:value("routed", translate("routed"))
+ m.pageaction = true
+end
+
+local network = require "luci.model.network"
+if network:has_ipv6() then
+ local s = m:section(NamedSection, "globals", "globals", translate("Global network options"))
+ local o = s:option(Value, "ula_prefix", translate("IPv6 ULA-Prefix"))
+ o.datatype = "ip6addr"
+ o.rmempty = true
+ m.pageaction = true
+end
+
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/proto_ahcp.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/proto_ahcp.lua
new file mode 100644
index 000000000..26dc33abb
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/proto_ahcp.lua
@@ -0,0 +1,76 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2011 Jo-Philipp Wich <xm@subsignal.org>
+
+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 map, section, net = ...
+
+local device, apn, service, pincode, username, password
+local ipv6, maxwait, defaultroute, metric, peerdns, dns,
+ keepalive_failure, keepalive_interval, demand
+
+
+mca = s:taboption("ahcp", Value, "multicast_address", translate("Multicast address"))
+mca.optional = true
+mca.placeholder = "ff02::cca6:c0f9:e182:5359"
+mca.datatype = "ip6addr"
+mca:depends("proto", "ahcp")
+
+port = s:taboption("ahcp", Value, "port", translate("Port"))
+port.optional = true
+port.placeholder = 5359
+port.datatype = "port"
+port:depends("proto", "ahcp")
+
+fam = s:taboption("ahcp", ListValue, "_family", translate("Protocol family"))
+fam:value("", translate("IPv4 and IPv6"))
+fam:value("ipv4", translate("IPv4 only"))
+fam:value("ipv6", translate("IPv6 only"))
+fam:depends("proto", "ahcp")
+
+function fam.cfgvalue(self, section)
+ local v4 = m.uci:get_bool("network", section, "ipv4_only")
+ local v6 = m.uci:get_bool("network", section, "ipv6_only")
+ if v4 then
+ return "ipv4"
+ elseif v6 then
+ return "ipv6"
+ end
+ return ""
+end
+
+function fam.write(self, section, value)
+ if value == "ipv4" then
+ m.uci:set("network", section, "ipv4_only", "true")
+ m.uci:delete("network", section, "ipv6_only")
+ elseif value == "ipv6" then
+ m.uci:set("network", section, "ipv6_only", "true")
+ m.uci:delete("network", section, "ipv4_only")
+ end
+end
+
+function fam.remove(self, section)
+ m.uci:delete("network", section, "ipv4_only")
+ m.uci:delete("network", section, "ipv6_only")
+end
+
+nodns = s:taboption("ahcp", Flag, "no_dns", translate("Disable DNS setup"))
+nodns.optional = true
+nodns.enabled = "true"
+nodns.disabled = "false"
+nodns.default = nodns.disabled
+nodns:depends("proto", "ahcp")
+
+ltime = s:taboption("ahcp", Value, "lease_time", translate("Lease validity time"))
+ltime.optional = true
+ltime.placeholder = 3666
+ltime.datatype = "uinteger"
+ltime:depends("proto", "ahcp")
+
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/routes.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/routes.lua
new file mode 100644
index 000000000..469748c87
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/routes.lua
@@ -0,0 +1,86 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+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
+
+$Id$
+]]--
+
+require("luci.tools.webadmin")
+m = Map("network",
+ translate("Routes"),
+ translate("Routes specify over which interface and gateway a certain host or network " ..
+ "can be reached."))
+
+local routes6 = luci.sys.net.routes6()
+local bit = require "bit"
+
+s = m:section(TypedSection, "route", translate("Static IPv4 Routes"))
+s.addremove = true
+s.anonymous = true
+
+s.template = "cbi/tblsection"
+
+iface = s:option(ListValue, "interface", translate("Interface"))
+luci.tools.webadmin.cbi_add_networks(iface)
+
+t = s:option(Value, "target", translate("Target"), translate("Host-<abbr title=\"Internet Protocol Address\">IP</abbr> or Network"))
+t.datatype = "ip4addr"
+t.rmempty = false
+
+n = s:option(Value, "netmask", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"), translate("if target is a network"))
+n.placeholder = "255.255.255.255"
+n.datatype = "ip4addr"
+n.rmempty = true
+
+g = s:option(Value, "gateway", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Gateway"))
+g.datatype = "ip4addr"
+g.rmempty = true
+
+metric = s:option(Value, "metric", translate("Metric"))
+metric.placeholder = 0
+metric.datatype = "range(0,255)"
+metric.rmempty = true
+
+mtu = s:option(Value, "mtu", translate("MTU"))
+mtu.placeholder = 1500
+mtu.datatype = "range(64,9000)"
+mtu.rmempty = true
+
+if routes6 then
+ s = m:section(TypedSection, "route6", translate("Static IPv6 Routes"))
+ s.addremove = true
+ s.anonymous = true
+
+ s.template = "cbi/tblsection"
+
+ iface = s:option(ListValue, "interface", translate("Interface"))
+ luci.tools.webadmin.cbi_add_networks(iface)
+
+ t = s:option(Value, "target", translate("Target"), translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Address or Network (CIDR)"))
+ t.datatype = "ip6addr"
+ t.rmempty = false
+
+ g = s:option(Value, "gateway", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Gateway"))
+ g.datatype = "ip6addr"
+ g.rmempty = true
+
+ metric = s:option(Value, "metric", translate("Metric"))
+ metric.placeholder = 0
+ metric.datatype = "range(0,65535)" -- XXX: not sure
+ metric.rmempty = true
+
+ mtu = s:option(Value, "mtu", translate("MTU"))
+ mtu.placeholder = 1500
+ mtu.datatype = "range(64,9000)"
+ mtu.rmempty = true
+end
+
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/vlan.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/vlan.lua
new file mode 100644
index 000000000..fbea03af9
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/vlan.lua
@@ -0,0 +1,315 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2010-2011 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+]]--
+
+m = Map("network", translate("Switch"), translate("The network ports on this device can be combined to several <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s in which computers can communicate directly with each other. <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network."))
+
+local fs = require "nixio.fs"
+local switches = { }
+
+m.uci:foreach("network", "switch",
+ function(x)
+ local sid = x['.name']
+ local switch_name = x.name or sid
+ local has_vlan = nil
+ local has_learn = nil
+ local has_vlan4k = nil
+ local has_jumbo3 = nil
+ local has_mirror = nil
+ local min_vid = 0
+ local max_vid = 16
+ local num_vlans = 16
+ local cpu_port = tonumber(fs.readfile("/proc/switch/eth0/cpuport") or 5)
+ local num_ports = cpu_port + 1
+
+ local switch_title
+ local enable_vlan4k = false
+
+ -- Parse some common switch properties from swconfig help output.
+ local swc = io.popen("swconfig dev %q help 2>/dev/null" % switch_name)
+ if swc then
+
+ local is_port_attr = false
+ local is_vlan_attr = false
+
+ while true do
+ local line = swc:read("*l")
+ if not line then break end
+
+ if line:match("^%s+%-%-vlan") then
+ is_vlan_attr = true
+
+ elseif line:match("^%s+%-%-port") then
+ is_vlan_attr = false
+ is_port_attr = true
+
+ elseif line:match("cpu @") then
+ switch_title = line:match("^switch%d: %w+%((.-)%)")
+ num_ports, cpu_port, num_vlans =
+ line:match("ports: (%d+) %(cpu @ (%d+)%), vlans: (%d+)")
+
+ num_ports = tonumber(num_ports) or 6
+ num_vlans = tonumber(num_vlans) or 16
+ cpu_port = tonumber(cpu_port) or 5
+ min_vid = 1
+
+ elseif line:match(": pvid") or line:match(": tag") or line:match(": vid") then
+ if is_vlan_attr then has_vlan4k = line:match(": (%w+)") end
+
+ elseif line:match(": enable_vlan4k") then
+ enable_vlan4k = true
+
+ elseif line:match(": enable_vlan") then
+ has_vlan = "enable_vlan"
+
+ elseif line:match(": enable_learning") then
+ has_learn = "enable_learning"
+
+ elseif line:match(": enable_mirror_rx") then
+ has_mirror = "enable_mirror_rx"
+
+ elseif line:match(": max_length") then
+ has_jumbo3 = "max_length"
+ end
+ end
+
+ swc:close()
+ end
+
+
+ -- Switch properties
+ s = m:section(NamedSection, x['.name'], "switch",
+ switch_title and translatef("Switch %q (%s)", switch_name, switch_title)
+ or translatef("Switch %q", switch_name))
+
+ s.addremove = false
+
+ if has_vlan then
+ s:option(Flag, has_vlan, translate("Enable VLAN functionality"))
+ end
+
+ if has_learn then
+ x = s:option(Flag, has_learn, translate("Enable learning and aging"))
+ x.default = x.enabled
+ end
+
+ if has_jumbo3 then
+ x = s:option(Flag, has_jumbo3, translate("Enable Jumbo Frame passthrough"))
+ x.enabled = "3"
+ x.rmempty = true
+ end
+
+ -- Does this switch support port mirroring?
+ if has_mirror then
+ s:option(Flag, "enable_mirror_rx", translate("Enable mirroring of incoming packets"))
+ s:option(Flag, "enable_mirror_tx", translate("Enable mirroring of outgoing packets"))
+
+ local sp = s:option(ListValue, "mirror_source_port", translate("Mirror source port"))
+ local mp = s:option(ListValue, "mirror_monitor_port", translate("Mirror monitor port"))
+
+ local pt
+ for pt = 0, num_ports - 1 do
+ local name
+
+ name = (pt == cpu_port) and translate("CPU") or translatef("Port %d", pt)
+
+ sp:value(pt, name)
+ mp:value(pt, name)
+ end
+ end
+
+ -- VLAN table
+ s = m:section(TypedSection, "switch_vlan",
+ switch_title and translatef("VLANs on %q (%s)", switch_name, switch_title)
+ or translatef("VLANs on %q", switch_name))
+
+ s.template = "cbi/tblsection"
+ s.addremove = true
+ s.anonymous = true
+
+ -- Filter by switch
+ s.filter = function(self, section)
+ local device = m:get(section, "device")
+ return (device and device == switch_name)
+ end
+
+ -- Override cfgsections callback to enforce row ordering by vlan id.
+ s.cfgsections = function(self)
+ local osections = TypedSection.cfgsections(self)
+ local sections = { }
+ local section
+
+ for _, section in luci.util.spairs(
+ osections,
+ function(a, b)
+ return (tonumber(m:get(osections[a], has_vlan4k or "vlan")) or 9999)
+ < (tonumber(m:get(osections[b], has_vlan4k or "vlan")) or 9999)
+ end
+ ) do
+ sections[#sections+1] = section
+ end
+
+ return sections
+ end
+
+ -- When creating a new vlan, preset it with the highest found vid + 1.
+ s.create = function(self, section, origin)
+ -- Filter by switch
+ if m:get(origin, "device") ~= switch_name then
+ return
+ end
+
+ local sid = TypedSection.create(self, section)
+
+ local max_nr = 0
+ local max_id = 0
+
+ m.uci:foreach("network", "switch_vlan",
+ function(s)
+ if s.device == switch_name then
+ local nr = tonumber(s.vlan)
+ local id = has_vlan4k and tonumber(s[has_vlan4k])
+ if nr ~= nil and nr > max_nr then max_nr = nr end
+ if id ~= nil and id > max_id then max_id = id end
+ end
+ end)
+
+ m:set(sid, "device", switch_name)
+ m:set(sid, "vlan", max_nr + 1)
+
+ if has_vlan4k then
+ m:set(sid, has_vlan4k, max_id + 1)
+ end
+
+ return sid
+ end
+
+
+ local port_opts = { }
+ local untagged = { }
+
+ -- Parse current tagging state from the "ports" option.
+ local portvalue = function(self, section)
+ local pt
+ for pt in (m:get(section, "ports") or ""):gmatch("%w+") do
+ local pc, tu = pt:match("^(%d+)([tu]*)")
+ if pc == self.option then return (#tu > 0) and tu or "u" end
+ end
+ return ""
+ end
+
+ -- Validate port tagging. Ensure that a port is only untagged once,
+ -- bail out if not.
+ local portvalidate = function(self, value, section)
+ -- ensure that the ports appears untagged only once
+ if value == "u" then
+ if not untagged[self.option] then
+ untagged[self.option] = true
+ elseif min_vid > 0 or tonumber(self.option) ~= cpu_port then -- enable multiple untagged cpu ports due to weird broadcom default setup
+ return nil,
+ translatef("Port %d is untagged in multiple VLANs!", tonumber(self.option) + 1)
+ end
+ end
+ return value
+ end
+
+
+ local vid = s:option(Value, has_vlan4k or "vlan", "VLAN ID", "<div id='portstatus-%s'></div>" % switch_name)
+ local mx_vid = has_vlan4k and 4094 or (num_vlans - 1)
+
+ vid.rmempty = false
+ vid.forcewrite = true
+ vid.vlan_used = { }
+ vid.datatype = "and(uinteger,range("..min_vid..","..mx_vid.."))"
+
+ -- Validate user provided VLAN ID, make sure its within the bounds
+ -- allowed by the switch.
+ vid.validate = function(self, value, section)
+ local v = tonumber(value)
+ local m = has_vlan4k and 4094 or (num_vlans - 1)
+ if v ~= nil and v >= min_vid and v <= m then
+ if not self.vlan_used[v] then
+ self.vlan_used[v] = true
+ return value
+ else
+ return nil,
+ translatef("Invalid VLAN ID given! Only unique IDs are allowed")
+ end
+ else
+ return nil,
+ translatef("Invalid VLAN ID given! Only IDs between %d and %d are allowed.", min_vid, m)
+ end
+ end
+
+ -- When writing the "vid" or "vlan" option, serialize the port states
+ -- as well and write them as "ports" option to uci.
+ vid.write = function(self, section, value)
+ local o
+ local p = { }
+
+ for _, o in ipairs(port_opts) do
+ local v = o:formvalue(section)
+ if v == "t" then
+ p[#p+1] = o.option .. v
+ elseif v == "u" then
+ p[#p+1] = o.option
+ end
+ end
+
+ if enable_vlan4k then
+ m:set(sid, "enable_vlan4k", "1")
+ end
+
+ m:set(section, "ports", table.concat(p, " "))
+ return Value.write(self, section, value)
+ end
+
+ -- Fallback to "vlan" option if "vid" option is supported but unset.
+ vid.cfgvalue = function(self, section)
+ return m:get(section, has_vlan4k or "vlan")
+ or m:get(section, "vlan")
+ end
+
+ -- Build per-port off/untagged/tagged choice lists.
+ local pt
+ for pt = 0, num_ports - 1 do
+ local title
+ if pt == cpu_port then
+ title = translate("CPU")
+ else
+ title = translatef("Port %d", pt)
+ end
+
+ local po = s:option(ListValue, tostring(pt), title)
+
+ po:value("", translate("off"))
+ po:value("u", translate("untagged"))
+ po:value("t", translate("tagged"))
+
+ po.cfgvalue = portvalue
+ po.validate = portvalidate
+ po.write = function() end
+
+ port_opts[#port_opts+1] = po
+ end
+
+ switches[#switches+1] = switch_name
+ end
+)
+
+-- Switch status template
+s = m:section(SimpleSection)
+s.template = "admin_network/switch_status"
+s.switches = switches
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua
new file mode 100644
index 000000000..f61fda373
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua
@@ -0,0 +1,956 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+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
+
+$Id$
+]]--
+
+local wa = require "luci.tools.webadmin"
+local nw = require "luci.model.network"
+local ut = require "luci.util"
+local nt = require "luci.sys".net
+local fs = require "nixio.fs"
+
+arg[1] = arg[1] or ""
+
+m = Map("wireless", "",
+ translate("The <em>Device Configuration</em> section covers physical settings of the radio " ..
+ "hardware such as channel, transmit power or antenna selection which are shared among all " ..
+ "defined wireless networks (if the radio hardware is multi-SSID capable). Per network settings " ..
+ "like encryption or operation mode are grouped in the <em>Interface Configuration</em>."))
+
+m:chain("network")
+m:chain("firewall")
+
+local ifsection
+
+function m.on_commit(map)
+ local wnet = nw:get_wifinet(arg[1])
+ if ifsection and wnet then
+ ifsection.section = wnet.sid
+ m.title = luci.util.pcdata(wnet:get_i18n())
+ end
+end
+
+nw.init(m.uci)
+
+local wnet = nw:get_wifinet(arg[1])
+local wdev = wnet and wnet:get_device()
+
+-- redirect to overview page if network does not exist anymore (e.g. after a revert)
+if not wnet or not wdev then
+ luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless"))
+ return
+end
+
+-- wireless toggle was requested, commit and reload page
+function m.parse(map)
+ if m:formvalue("cbid.wireless.%s.__toggle" % wdev:name()) then
+ if wdev:get("disabled") == "1" or wnet:get("disabled") == "1" then
+ wnet:set("disabled", nil)
+ else
+ wnet:set("disabled", "1")
+ end
+ wdev:set("disabled", nil)
+
+ nw:commit("wireless")
+ luci.sys.call("(env -i /bin/ubus call network reload) >/dev/null 2>/dev/null")
+
+ luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless", arg[1]))
+ return
+ end
+ Map.parse(map)
+end
+
+m.title = luci.util.pcdata(wnet:get_i18n())
+
+
+local function txpower_list(iw)
+ local list = iw.txpwrlist or { }
+ local off = tonumber(iw.txpower_offset) or 0
+ local new = { }
+ local prev = -1
+ local _, val
+ for _, val in ipairs(list) do
+ local dbm = val.dbm + off
+ local mw = math.floor(10 ^ (dbm / 10))
+ if mw ~= prev then
+ prev = mw
+ new[#new+1] = {
+ display_dbm = dbm,
+ display_mw = mw,
+ driver_dbm = val.dbm,
+ driver_mw = val.mw
+ }
+ end
+ end
+ return new
+end
+
+local function txpower_current(pwr, list)
+ pwr = tonumber(pwr)
+ if pwr ~= nil then
+ local _, item
+ for _, item in ipairs(list) do
+ if item.driver_dbm >= pwr then
+ return item.driver_dbm
+ end
+ end
+ end
+ return (list[#list] and list[#list].driver_dbm) or pwr or 0
+end
+
+local iw = luci.sys.wifi.getiwinfo(arg[1])
+local hw_modes = iw.hwmodelist or { }
+local tx_power_list = txpower_list(iw)
+local tx_power_cur = txpower_current(wdev:get("txpower"), tx_power_list)
+
+s = m:section(NamedSection, wdev:name(), "wifi-device", translate("Device Configuration"))
+s.addremove = false
+
+s:tab("general", translate("General Setup"))
+s:tab("macfilter", translate("MAC-Filter"))
+s:tab("advanced", translate("Advanced Settings"))
+
+--[[
+back = s:option(DummyValue, "_overview", translate("Overview"))
+back.value = ""
+back.titleref = luci.dispatcher.build_url("admin", "network", "wireless")
+]]
+
+st = s:taboption("general", DummyValue, "__status", translate("Status"))
+st.template = "admin_network/wifi_status"
+st.ifname = arg[1]
+
+en = s:taboption("general", Button, "__toggle")
+
+if wdev:get("disabled") == "1" or wnet:get("disabled") == "1" then
+ en.title = translate("Wireless network is disabled")
+ en.inputtitle = translate("Enable")
+ en.inputstyle = "apply"
+else
+ en.title = translate("Wireless network is enabled")
+ en.inputtitle = translate("Disable")
+ en.inputstyle = "reset"
+end
+
+
+local hwtype = wdev:get("type")
+
+-- NanoFoo
+local nsantenna = wdev:get("antenna")
+
+-- Check whether there are client interfaces on the same radio,
+-- if yes, lock the channel choice as these stations will dicatate the freq
+local found_sta = nil
+local _, net
+if wnet:mode() ~= "sta" then
+ for _, net in ipairs(wdev:get_wifinets()) do
+ if net:mode() == "sta" then
+ if not found_sta then
+ found_sta = {}
+ found_sta.channel = net:channel()
+ found_sta.names = {}
+ end
+ found_sta.names[#found_sta.names+1] = net:shortname()
+ end
+ end
+end
+
+if found_sta then
+ ch = s:taboption("general", DummyValue, "choice", translate("Channel"))
+ ch.value = translatef("Locked to channel %d used by: %s",
+ found_sta.channel, table.concat(found_sta.names, ", "))
+else
+ ch = s:taboption("general", Value, "_mode_freq", '<br />'..translate("Operating frequency"))
+ ch.hwmodes = iw.hwmodelist
+ ch.freqlist = iw.freqlist
+ ch.template = "cbi/wireless_modefreq"
+
+ function ch.cfgvalue(self, section)
+ return {
+ m:get(section, "hwmode") or "",
+ m:get(section, "channel") or "auto",
+ m:get(section, "htmode") or ""
+ }
+ end
+
+ function ch.formvalue(self, section)
+ return {
+ m:formvalue(self:cbid(section) .. ".band") or (iw.hwmodelist.g and "11g" or "11a"),
+ m:formvalue(self:cbid(section) .. ".channel") or "auto",
+ m:formvalue(self:cbid(section) .. ".htmode") or ""
+ }
+ end
+
+ function ch.write(self, section, value)
+ m:set(section, "hwmode", value[1])
+ m:set(section, "channel", value[2])
+ m:set(section, "htmode", value[3])
+ end
+end
+
+------------------- MAC80211 Device ------------------
+
+if hwtype == "mac80211" then
+ if #tx_power_list > 1 then
+ tp = s:taboption("general", ListValue,
+ "txpower", translate("Transmit Power"), "dBm")
+ tp.rmempty = true
+ tp.default = tx_power_cur
+ function tp.cfgvalue(...)
+ return txpower_current(Value.cfgvalue(...), tx_power_list)
+ end
+
+ for _, p in ipairs(tx_power_list) do
+ tp:value(p.driver_dbm, "%i dBm (%i mW)"
+ %{ p.display_dbm, p.display_mw })
+ end
+ end
+
+ local cl = iw and iw.countrylist
+ if cl and #cl > 0 then
+ cc = s:taboption("advanced", ListValue, "country", translate("Country Code"), translate("Use ISO/IEC 3166 alpha2 country codes."))
+ cc.default = tostring(iw and iw.country or "00")
+ for _, c in ipairs(cl) do
+ cc:value(c.alpha2, "%s - %s" %{ c.alpha2, c.name })
+ end
+ else
+ s:taboption("advanced", Value, "country", translate("Country Code"), translate("Use ISO/IEC 3166 alpha2 country codes."))
+ end
+
+ s:taboption("advanced", Value, "distance", translate("Distance Optimization"),
+ translate("Distance to farthest network member in meters."))
+
+ -- external antenna profiles
+ local eal = iw and iw.extant
+ if eal and #eal > 0 then
+ ea = s:taboption("advanced", ListValue, "extant", translate("Antenna Configuration"))
+ for _, eap in ipairs(eal) do
+ ea:value(eap.id, "%s (%s)" %{ eap.name, eap.description })
+ if eap.selected then
+ ea.default = eap.id
+ end
+ end
+ end
+
+ s:taboption("advanced", Value, "frag", translate("Fragmentation Threshold"))
+ s:taboption("advanced", Value, "rts", translate("RTS/CTS Threshold"))
+end
+
+
+------------------- Madwifi Device ------------------
+
+if hwtype == "atheros" then
+ tp = s:taboption("general",
+ (#tx_power_list > 0) and ListValue or Value,
+ "txpower", translate("Transmit Power"), "dBm")
+
+ tp.rmempty = true
+ tp.default = tx_power_cur
+
+ function tp.cfgvalue(...)
+ return txpower_current(Value.cfgvalue(...), tx_power_list)
+ end
+
+ for _, p in ipairs(tx_power_list) do
+ tp:value(p.driver_dbm, "%i dBm (%i mW)"
+ %{ p.display_dbm, p.display_mw })
+ end
+
+ s:taboption("advanced", Flag, "diversity", translate("Diversity")).rmempty = false
+
+ if not nsantenna then
+ ant1 = s:taboption("advanced", ListValue, "txantenna", translate("Transmitter Antenna"))
+ ant1.widget = "radio"
+ ant1.orientation = "horizontal"
+ ant1:depends("diversity", "")
+ ant1:value("0", translate("auto"))
+ ant1:value("1", translate("Antenna 1"))
+ ant1:value("2", translate("Antenna 2"))
+
+ ant2 = s:taboption("advanced", ListValue, "rxantenna", translate("Receiver Antenna"))
+ ant2.widget = "radio"
+ ant2.orientation = "horizontal"
+ ant2:depends("diversity", "")
+ ant2:value("0", translate("auto"))
+ ant2:value("1", translate("Antenna 1"))
+ ant2:value("2", translate("Antenna 2"))
+
+ else -- NanoFoo
+ local ant = s:taboption("advanced", ListValue, "antenna", translate("Transmitter Antenna"))
+ ant:value("auto")
+ ant:value("vertical")
+ ant:value("horizontal")
+ ant:value("external")
+ end
+
+ s:taboption("advanced", Value, "distance", translate("Distance Optimization"),
+ translate("Distance to farthest network member in meters."))
+ s:taboption("advanced", Value, "regdomain", translate("Regulatory Domain"))
+ s:taboption("advanced", Value, "country", translate("Country Code"))
+ s:taboption("advanced", Flag, "outdoor", translate("Outdoor Channels"))
+
+ --s:option(Flag, "nosbeacon", translate("Disable HW-Beacon timer"))
+end
+
+
+
+------------------- Broadcom Device ------------------
+
+if hwtype == "broadcom" then
+ tp = s:taboption("general",
+ (#tx_power_list > 0) and ListValue or Value,
+ "txpower", translate("Transmit Power"), "dBm")
+
+ tp.rmempty = true
+ tp.default = tx_power_cur
+
+ function tp.cfgvalue(...)
+ return txpower_current(Value.cfgvalue(...), tx_power_list)
+ end
+
+ for _, p in ipairs(tx_power_list) do
+ tp:value(p.driver_dbm, "%i dBm (%i mW)"
+ %{ p.display_dbm, p.display_mw })
+ end
+
+ ant1 = s:taboption("advanced", ListValue, "txantenna", translate("Transmitter Antenna"))
+ ant1.widget = "radio"
+ ant1:depends("diversity", "")
+ ant1:value("3", translate("auto"))
+ ant1:value("0", translate("Antenna 1"))
+ ant1:value("1", translate("Antenna 2"))
+
+ ant2 = s:taboption("advanced", ListValue, "rxantenna", translate("Receiver Antenna"))
+ ant2.widget = "radio"
+ ant2:depends("diversity", "")
+ ant2:value("3", translate("auto"))
+ ant2:value("0", translate("Antenna 1"))
+ ant2:value("1", translate("Antenna 2"))
+
+ s:taboption("advanced", Flag, "frameburst", translate("Frame Bursting"))
+
+ s:taboption("advanced", Value, "distance", translate("Distance Optimization"))
+ --s:option(Value, "slottime", translate("Slot time"))
+
+ s:taboption("advanced", Value, "country", translate("Country Code"))
+ s:taboption("advanced", Value, "maxassoc", translate("Connection Limit"))
+end
+
+
+--------------------- HostAP Device ---------------------
+
+if hwtype == "prism2" then
+ s:taboption("advanced", Value, "txpower", translate("Transmit Power"), "att units").rmempty = true
+
+ s:taboption("advanced", Flag, "diversity", translate("Diversity")).rmempty = false
+
+ s:taboption("advanced", Value, "txantenna", translate("Transmitter Antenna"))
+ s:taboption("advanced", Value, "rxantenna", translate("Receiver Antenna"))
+end
+
+
+----------------------- Interface -----------------------
+
+s = m:section(NamedSection, wnet.sid, "wifi-iface", translate("Interface Configuration"))
+ifsection = s
+s.addremove = false
+s.anonymous = true
+s.defaults.device = wdev:name()
+
+s:tab("general", translate("General Setup"))
+s:tab("encryption", translate("Wireless Security"))
+s:tab("macfilter", translate("MAC-Filter"))
+s:tab("advanced", translate("Advanced Settings"))
+
+s:taboption("general", Value, "ssid", translate("<abbr title=\"Extended Service Set Identifier\">ESSID</abbr>"))
+
+mode = s:taboption("general", ListValue, "mode", translate("Mode"))
+mode.override_values = true
+mode:value("ap", translate("Access Point"))
+mode:value("sta", translate("Client"))
+mode:value("adhoc", translate("Ad-Hoc"))
+
+bssid = s:taboption("general", Value, "bssid", translate("<abbr title=\"Basic Service Set Identifier\">BSSID</abbr>"))
+
+network = s:taboption("general", Value, "network", translate("Network"),
+ translate("Choose the network(s) you want to attach to this wireless interface or " ..
+ "fill out the <em>create</em> field to define a new network."))
+
+network.rmempty = true
+network.template = "cbi/network_netlist"
+network.widget = "checkbox"
+network.novirtual = true
+
+function network.write(self, section, value)
+ local i = nw:get_interface(section)
+ if i then
+ if value == '-' then
+ value = m:formvalue(self:cbid(section) .. ".newnet")
+ if value and #value > 0 then
+ local n = nw:add_network(value, {proto="none"})
+ if n then n:add_interface(i) end
+ else
+ local n = i:get_network()
+ if n then n:del_interface(i) end
+ end
+ else
+ 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 not n:is_empty() then
+ n:set("type", "bridge")
+ end
+ n:add_interface(i)
+ end
+ end
+ end
+ end
+end
+
+-------------------- MAC80211 Interface ----------------------
+
+if hwtype == "mac80211" then
+ if fs.access("/usr/sbin/iw") then
+ mode:value("mesh", "802.11s")
+ end
+
+ mode:value("ahdemo", translate("Pseudo Ad-Hoc (ahdemo)"))
+ mode:value("monitor", translate("Monitor"))
+ bssid:depends({mode="adhoc"})
+ bssid:depends({mode="sta"})
+ bssid:depends({mode="sta-wds"})
+
+ mp = s:taboption("macfilter", ListValue, "macfilter", translate("MAC-Address Filter"))
+ mp:depends({mode="ap"})
+ mp:depends({mode="ap-wds"})
+ mp:value("", translate("disable"))
+ mp:value("allow", translate("Allow listed only"))
+ mp:value("deny", translate("Allow all except listed"))
+
+ ml = s:taboption("macfilter", DynamicList, "maclist", translate("MAC-List"))
+ ml.datatype = "macaddr"
+ ml:depends({macfilter="allow"})
+ ml:depends({macfilter="deny"})
+ nt.mac_hints(function(mac, name) ml:value(mac, "%s (%s)" %{ mac, name }) end)
+
+ mode:value("ap-wds", "%s (%s)" % {translate("Access Point"), translate("WDS")})
+ mode:value("sta-wds", "%s (%s)" % {translate("Client"), translate("WDS")})
+
+ function mode.write(self, section, value)
+ if value == "ap-wds" then
+ ListValue.write(self, section, "ap")
+ m.uci:set("wireless", section, "wds", 1)
+ elseif value == "sta-wds" then
+ ListValue.write(self, section, "sta")
+ m.uci:set("wireless", section, "wds", 1)
+ else
+ ListValue.write(self, section, value)
+ m.uci:delete("wireless", section, "wds")
+ end
+ end
+
+ function mode.cfgvalue(self, section)
+ local mode = ListValue.cfgvalue(self, section)
+ local wds = m.uci:get("wireless", section, "wds") == "1"
+
+ if mode == "ap" and wds then
+ return "ap-wds"
+ elseif mode == "sta" and wds then
+ return "sta-wds"
+ else
+ return mode
+ end
+ end
+
+ hidden = s:taboption("general", Flag, "hidden", translate("Hide <abbr title=\"Extended Service Set Identifier\">ESSID</abbr>"))
+ hidden:depends({mode="ap"})
+ hidden:depends({mode="ap-wds"})
+
+ wmm = s:taboption("general", Flag, "wmm", translate("WMM Mode"))
+ wmm:depends({mode="ap"})
+ wmm:depends({mode="ap-wds"})
+ wmm.default = wmm.enabled
+end
+
+
+
+-------------------- Madwifi Interface ----------------------
+
+if hwtype == "atheros" then
+ mode:value("ahdemo", translate("Pseudo Ad-Hoc (ahdemo)"))
+ mode:value("monitor", translate("Monitor"))
+ mode:value("ap-wds", "%s (%s)" % {translate("Access Point"), translate("WDS")})
+ mode:value("sta-wds", "%s (%s)" % {translate("Client"), translate("WDS")})
+ mode:value("wds", translate("Static WDS"))
+
+ function mode.write(self, section, value)
+ if value == "ap-wds" then
+ ListValue.write(self, section, "ap")
+ m.uci:set("wireless", section, "wds", 1)
+ elseif value == "sta-wds" then
+ ListValue.write(self, section, "sta")
+ m.uci:set("wireless", section, "wds", 1)
+ else
+ ListValue.write(self, section, value)
+ m.uci:delete("wireless", section, "wds")
+ end
+ end
+
+ function mode.cfgvalue(self, section)
+ local mode = ListValue.cfgvalue(self, section)
+ local wds = m.uci:get("wireless", section, "wds") == "1"
+
+ if mode == "ap" and wds then
+ return "ap-wds"
+ elseif mode == "sta" and wds then
+ return "sta-wds"
+ else
+ return mode
+ end
+ end
+
+ bssid:depends({mode="adhoc"})
+ bssid:depends({mode="ahdemo"})
+ bssid:depends({mode="wds"})
+
+ wdssep = s:taboption("advanced", Flag, "wdssep", translate("Separate WDS"))
+ wdssep:depends({mode="ap-wds"})
+
+ s:taboption("advanced", Flag, "doth", "802.11h")
+ hidden = s:taboption("general", Flag, "hidden", translate("Hide <abbr title=\"Extended Service Set Identifier\">ESSID</abbr>"))
+ hidden:depends({mode="ap"})
+ hidden:depends({mode="adhoc"})
+ hidden:depends({mode="ap-wds"})
+ hidden:depends({mode="sta-wds"})
+ isolate = s:taboption("advanced", Flag, "isolate", translate("Separate Clients"),
+ translate("Prevents client-to-client communication"))
+ isolate:depends({mode="ap"})
+ s:taboption("advanced", Flag, "bgscan", translate("Background Scan"))
+
+ mp = s:taboption("macfilter", ListValue, "macpolicy", translate("MAC-Address Filter"))
+ mp:value("", translate("disable"))
+ mp:value("allow", translate("Allow listed only"))
+ mp:value("deny", translate("Allow all except listed"))
+
+ ml = s:taboption("macfilter", DynamicList, "maclist", translate("MAC-List"))
+ ml.datatype = "macaddr"
+ ml:depends({macpolicy="allow"})
+ ml:depends({macpolicy="deny"})
+ nt.mac_hints(function(mac, name) ml:value(mac, "%s (%s)" %{ mac, name }) end)
+
+ s:taboption("advanced", Value, "rate", translate("Transmission Rate"))
+ s:taboption("advanced", Value, "mcast_rate", translate("Multicast Rate"))
+ s:taboption("advanced", Value, "frag", translate("Fragmentation Threshold"))
+ s:taboption("advanced", Value, "rts", translate("RTS/CTS Threshold"))
+ s:taboption("advanced", Value, "minrate", translate("Minimum Rate"))
+ s:taboption("advanced", Value, "maxrate", translate("Maximum Rate"))
+ s:taboption("advanced", Flag, "compression", translate("Compression"))
+
+ s:taboption("advanced", Flag, "bursting", translate("Frame Bursting"))
+ s:taboption("advanced", Flag, "turbo", translate("Turbo Mode"))
+ s:taboption("advanced", Flag, "ff", translate("Fast Frames"))
+
+ s:taboption("advanced", Flag, "wmm", translate("WMM Mode"))
+ s:taboption("advanced", Flag, "xr", translate("XR Support"))
+ s:taboption("advanced", Flag, "ar", translate("AR Support"))
+
+ local swm = s:taboption("advanced", Flag, "sw_merge", translate("Disable HW-Beacon timer"))
+ swm:depends({mode="adhoc"})
+
+ local nos = s:taboption("advanced", Flag, "nosbeacon", translate("Disable HW-Beacon timer"))
+ nos:depends({mode="sta"})
+ nos:depends({mode="sta-wds"})
+
+ local probereq = s:taboption("advanced", Flag, "probereq", translate("Do not send probe responses"))
+ probereq.enabled = "0"
+ probereq.disabled = "1"
+end
+
+
+-------------------- Broadcom Interface ----------------------
+
+if hwtype == "broadcom" then
+ mode:value("wds", translate("WDS"))
+ mode:value("monitor", translate("Monitor"))
+
+ hidden = s:taboption("general", Flag, "hidden", translate("Hide <abbr title=\"Extended Service Set Identifier\">ESSID</abbr>"))
+ hidden:depends({mode="ap"})
+ hidden:depends({mode="adhoc"})
+ hidden:depends({mode="wds"})
+
+ isolate = s:taboption("advanced", Flag, "isolate", translate("Separate Clients"),
+ translate("Prevents client-to-client communication"))
+ isolate:depends({mode="ap"})
+
+ s:taboption("advanced", Flag, "doth", "802.11h")
+ s:taboption("advanced", Flag, "wmm", translate("WMM Mode"))
+
+ bssid:depends({mode="wds"})
+ bssid:depends({mode="adhoc"})
+end
+
+
+----------------------- HostAP Interface ---------------------
+
+if hwtype == "prism2" then
+ mode:value("wds", translate("WDS"))
+ mode:value("monitor", translate("Monitor"))
+
+ hidden = s:taboption("general", Flag, "hidden", translate("Hide <abbr title=\"Extended Service Set Identifier\">ESSID</abbr>"))
+ hidden:depends({mode="ap"})
+ hidden:depends({mode="adhoc"})
+ hidden:depends({mode="wds"})
+
+ bssid:depends({mode="sta"})
+
+ mp = s:taboption("macfilter", ListValue, "macpolicy", translate("MAC-Address Filter"))
+ mp:value("", translate("disable"))
+ mp:value("allow", translate("Allow listed only"))
+ mp:value("deny", translate("Allow all except listed"))
+ ml = s:taboption("macfilter", DynamicList, "maclist", translate("MAC-List"))
+ ml:depends({macpolicy="allow"})
+ ml:depends({macpolicy="deny"})
+ nt.mac_hints(function(mac, name) ml:value(mac, "%s (%s)" %{ mac, name }) end)
+
+ s:taboption("advanced", Value, "rate", translate("Transmission Rate"))
+ s:taboption("advanced", Value, "frag", translate("Fragmentation Threshold"))
+ s:taboption("advanced", Value, "rts", translate("RTS/CTS Threshold"))
+end
+
+
+------------------- WiFI-Encryption -------------------
+
+encr = s:taboption("encryption", ListValue, "encryption", translate("Encryption"))
+encr.override_values = true
+encr.override_depends = true
+encr:depends({mode="ap"})
+encr:depends({mode="sta"})
+encr:depends({mode="adhoc"})
+encr:depends({mode="ahdemo"})
+encr:depends({mode="ap-wds"})
+encr:depends({mode="sta-wds"})
+encr:depends({mode="mesh"})
+
+cipher = s:taboption("encryption", ListValue, "cipher", translate("Cipher"))
+cipher:depends({encryption="wpa"})
+cipher:depends({encryption="wpa2"})
+cipher:depends({encryption="psk"})
+cipher:depends({encryption="psk2"})
+cipher:depends({encryption="wpa-mixed"})
+cipher:depends({encryption="psk-mixed"})
+cipher:value("auto", translate("auto"))
+cipher:value("ccmp", translate("Force CCMP (AES)"))
+cipher:value("tkip", translate("Force TKIP"))
+cipher:value("tkip+ccmp", translate("Force TKIP and CCMP (AES)"))
+
+function encr.cfgvalue(self, section)
+ local v = tostring(ListValue.cfgvalue(self, section))
+ if v == "wep" then
+ return "wep-open"
+ elseif v and v:match("%+") then
+ return (v:gsub("%+.+$", ""))
+ end
+ return v
+end
+
+function encr.write(self, section, value)
+ local e = tostring(encr:formvalue(section))
+ local c = tostring(cipher:formvalue(section))
+ if value == "wpa" or value == "wpa2" then
+ self.map.uci:delete("wireless", section, "key")
+ end
+ if e and (c == "tkip" or c == "ccmp" or c == "tkip+ccmp") then
+ e = e .. "+" .. c
+ end
+ self.map:set(section, "encryption", e)
+end
+
+function cipher.cfgvalue(self, section)
+ local v = tostring(ListValue.cfgvalue(encr, section))
+ if v and v:match("%+") then
+ v = v:gsub("^[^%+]+%+", "")
+ if v == "aes" then v = "ccmp"
+ elseif v == "tkip+aes" then v = "tkip+ccmp"
+ elseif v == "aes+tkip" then v = "tkip+ccmp"
+ elseif v == "ccmp+tkip" then v = "tkip+ccmp"
+ end
+ end
+ return v
+end
+
+function cipher.write(self, section)
+ return encr:write(section)
+end
+
+
+encr:value("none", "No Encryption")
+encr:value("wep-open", translate("WEP Open System"), {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}, {mode="ahdemo"}, {mode="wds"})
+encr:value("wep-shared", translate("WEP Shared Key"), {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}, {mode="ahdemo"}, {mode="wds"})
+
+if hwtype == "atheros" or hwtype == "mac80211" or hwtype == "prism2" then
+ local supplicant = fs.access("/usr/sbin/wpa_supplicant")
+ local hostapd = fs.access("/usr/sbin/hostapd")
+
+ -- Probe EAP support
+ local has_ap_eap = (os.execute("hostapd -veap >/dev/null 2>/dev/null") == 0)
+ local has_sta_eap = (os.execute("wpa_supplicant -veap >/dev/null 2>/dev/null") == 0)
+
+ if hostapd and supplicant then
+ encr:value("psk", "WPA-PSK", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+ encr:value("psk2", "WPA2-PSK", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+ encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+ if has_ap_eap and has_sta_eap then
+ encr:value("wpa", "WPA-EAP", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+ encr:value("wpa2", "WPA2-EAP", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+ end
+ elseif hostapd and not supplicant then
+ encr:value("psk", "WPA-PSK", {mode="ap"}, {mode="ap-wds"})
+ encr:value("psk2", "WPA2-PSK", {mode="ap"}, {mode="ap-wds"})
+ encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="ap"}, {mode="ap-wds"})
+ if has_ap_eap then
+ encr:value("wpa", "WPA-EAP", {mode="ap"}, {mode="ap-wds"})
+ encr:value("wpa2", "WPA2-EAP", {mode="ap"}, {mode="ap-wds"})
+ end
+ encr.description = translate(
+ "WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP " ..
+ "and ad-hoc mode) to be installed."
+ )
+ elseif not hostapd and supplicant then
+ encr:value("psk", "WPA-PSK", {mode="sta"}, {mode="sta-wds"})
+ encr:value("psk2", "WPA2-PSK", {mode="sta"}, {mode="sta-wds"})
+ encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="sta"}, {mode="sta-wds"})
+ if has_sta_eap then
+ encr:value("wpa", "WPA-EAP", {mode="sta"}, {mode="sta-wds"})
+ encr:value("wpa2", "WPA2-EAP", {mode="sta"}, {mode="sta-wds"})
+ end
+ encr.description = translate(
+ "WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP " ..
+ "and ad-hoc mode) to be installed."
+ )
+ else
+ encr.description = translate(
+ "WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP " ..
+ "and ad-hoc mode) to be installed."
+ )
+ end
+elseif hwtype == "broadcom" then
+ encr:value("psk", "WPA-PSK")
+ encr:value("psk2", "WPA2-PSK")
+ encr:value("psk+psk2", "WPA-PSK/WPA2-PSK Mixed Mode")
+end
+
+auth_server = s:taboption("encryption", Value, "auth_server", translate("Radius-Authentication-Server"))
+auth_server:depends({mode="ap", encryption="wpa"})
+auth_server:depends({mode="ap", encryption="wpa2"})
+auth_server:depends({mode="ap-wds", encryption="wpa"})
+auth_server:depends({mode="ap-wds", encryption="wpa2"})
+auth_server.rmempty = true
+auth_server.datatype = "host"
+
+auth_port = s:taboption("encryption", Value, "auth_port", translate("Radius-Authentication-Port"), translatef("Default %d", 1812))
+auth_port:depends({mode="ap", encryption="wpa"})
+auth_port:depends({mode="ap", encryption="wpa2"})
+auth_port:depends({mode="ap-wds", encryption="wpa"})
+auth_port:depends({mode="ap-wds", encryption="wpa2"})
+auth_port.rmempty = true
+auth_port.datatype = "port"
+
+auth_secret = s:taboption("encryption", Value, "auth_secret", translate("Radius-Authentication-Secret"))
+auth_secret:depends({mode="ap", encryption="wpa"})
+auth_secret:depends({mode="ap", encryption="wpa2"})
+auth_secret:depends({mode="ap-wds", encryption="wpa"})
+auth_secret:depends({mode="ap-wds", encryption="wpa2"})
+auth_secret.rmempty = true
+auth_secret.password = true
+
+acct_server = s:taboption("encryption", Value, "acct_server", translate("Radius-Accounting-Server"))
+acct_server:depends({mode="ap", encryption="wpa"})
+acct_server:depends({mode="ap", encryption="wpa2"})
+acct_server:depends({mode="ap-wds", encryption="wpa"})
+acct_server:depends({mode="ap-wds", encryption="wpa2"})
+acct_server.rmempty = true
+acct_server.datatype = "host"
+
+acct_port = s:taboption("encryption", Value, "acct_port", translate("Radius-Accounting-Port"), translatef("Default %d", 1813))
+acct_port:depends({mode="ap", encryption="wpa"})
+acct_port:depends({mode="ap", encryption="wpa2"})
+acct_port:depends({mode="ap-wds", encryption="wpa"})
+acct_port:depends({mode="ap-wds", encryption="wpa2"})
+acct_port.rmempty = true
+acct_port.datatype = "port"
+
+acct_secret = s:taboption("encryption", Value, "acct_secret", translate("Radius-Accounting-Secret"))
+acct_secret:depends({mode="ap", encryption="wpa"})
+acct_secret:depends({mode="ap", encryption="wpa2"})
+acct_secret:depends({mode="ap-wds", encryption="wpa"})
+acct_secret:depends({mode="ap-wds", encryption="wpa2"})
+acct_secret.rmempty = true
+acct_secret.password = true
+
+wpakey = s:taboption("encryption", Value, "_wpa_key", translate("Key"))
+wpakey:depends("encryption", "psk")
+wpakey:depends("encryption", "psk2")
+wpakey:depends("encryption", "psk+psk2")
+wpakey:depends("encryption", "psk-mixed")
+wpakey.datatype = "wpakey"
+wpakey.rmempty = true
+wpakey.password = true
+
+wpakey.cfgvalue = function(self, section, value)
+ local key = m.uci:get("wireless", section, "key")
+ if key == "1" or key == "2" or key == "3" or key == "4" then
+ return nil
+ end
+ return key
+end
+
+wpakey.write = function(self, section, value)
+ self.map.uci:set("wireless", section, "key", value)
+ self.map.uci:delete("wireless", section, "key1")
+end
+
+
+wepslot = s:taboption("encryption", ListValue, "_wep_key", translate("Used Key Slot"))
+wepslot:depends("encryption", "wep-open")
+wepslot:depends("encryption", "wep-shared")
+wepslot:value("1", translatef("Key #%d", 1))
+wepslot:value("2", translatef("Key #%d", 2))
+wepslot:value("3", translatef("Key #%d", 3))
+wepslot:value("4", translatef("Key #%d", 4))
+
+wepslot.cfgvalue = function(self, section)
+ local slot = tonumber(m.uci:get("wireless", section, "key"))
+ if not slot or slot < 1 or slot > 4 then
+ return 1
+ end
+ return slot
+end
+
+wepslot.write = function(self, section, value)
+ self.map.uci:set("wireless", section, "key", value)
+end
+
+local slot
+for slot=1,4 do
+ wepkey = s:taboption("encryption", Value, "key" .. slot, translatef("Key #%d", slot))
+ wepkey:depends("encryption", "wep-open")
+ wepkey:depends("encryption", "wep-shared")
+ wepkey.datatype = "wepkey"
+ wepkey.rmempty = true
+ wepkey.password = true
+
+ function wepkey.write(self, section, value)
+ if value and (#value == 5 or #value == 13) then
+ value = "s:" .. value
+ end
+ return Value.write(self, section, value)
+ end
+end
+
+
+if hwtype == "atheros" or hwtype == "mac80211" or hwtype == "prism2" then
+ nasid = s:taboption("encryption", Value, "nasid", translate("NAS ID"))
+ nasid:depends({mode="ap", encryption="wpa"})
+ nasid:depends({mode="ap", encryption="wpa2"})
+ nasid:depends({mode="ap-wds", encryption="wpa"})
+ nasid:depends({mode="ap-wds", encryption="wpa2"})
+ nasid.rmempty = true
+
+ eaptype = s:taboption("encryption", ListValue, "eap_type", translate("EAP-Method"))
+ eaptype:value("tls", "TLS")
+ eaptype:value("ttls", "TTLS")
+ eaptype:value("peap", "PEAP")
+ eaptype:depends({mode="sta", encryption="wpa"})
+ eaptype:depends({mode="sta", encryption="wpa2"})
+ eaptype:depends({mode="sta-wds", encryption="wpa"})
+ eaptype:depends({mode="sta-wds", encryption="wpa2"})
+
+ cacert = s:taboption("encryption", FileUpload, "ca_cert", translate("Path to CA-Certificate"))
+ cacert:depends({mode="sta", encryption="wpa"})
+ cacert:depends({mode="sta", encryption="wpa2"})
+ cacert:depends({mode="sta-wds", encryption="wpa"})
+ cacert:depends({mode="sta-wds", encryption="wpa2"})
+
+ clientcert = s:taboption("encryption", FileUpload, "client_cert", translate("Path to Client-Certificate"))
+ clientcert:depends({mode="sta", encryption="wpa"})
+ clientcert:depends({mode="sta", encryption="wpa2"})
+ clientcert:depends({mode="sta-wds", encryption="wpa"})
+ clientcert:depends({mode="sta-wds", encryption="wpa2"})
+
+ privkey = s:taboption("encryption", FileUpload, "priv_key", translate("Path to Private Key"))
+ privkey:depends({mode="sta", eap_type="tls", encryption="wpa2"})
+ privkey:depends({mode="sta", eap_type="tls", encryption="wpa"})
+ privkey:depends({mode="sta-wds", eap_type="tls", encryption="wpa2"})
+ privkey:depends({mode="sta-wds", eap_type="tls", encryption="wpa"})
+
+ privkeypwd = s:taboption("encryption", Value, "priv_key_pwd", translate("Password of Private Key"))
+ privkeypwd:depends({mode="sta", eap_type="tls", encryption="wpa2"})
+ privkeypwd:depends({mode="sta", eap_type="tls", encryption="wpa"})
+ privkeypwd:depends({mode="sta-wds", eap_type="tls", encryption="wpa2"})
+ privkeypwd:depends({mode="sta-wds", eap_type="tls", encryption="wpa"})
+
+
+ auth = s:taboption("encryption", Value, "auth", translate("Authentication"))
+ auth:value("PAP")
+ auth:value("CHAP")
+ auth:value("MSCHAP")
+ auth:value("MSCHAPV2")
+ auth:depends({mode="sta", eap_type="peap", encryption="wpa2"})
+ auth:depends({mode="sta", eap_type="peap", encryption="wpa"})
+ auth:depends({mode="sta", eap_type="ttls", encryption="wpa2"})
+ auth:depends({mode="sta", eap_type="ttls", encryption="wpa"})
+ auth:depends({mode="sta-wds", eap_type="peap", encryption="wpa2"})
+ auth:depends({mode="sta-wds", eap_type="peap", encryption="wpa"})
+ auth:depends({mode="sta-wds", eap_type="ttls", encryption="wpa2"})
+ auth:depends({mode="sta-wds", eap_type="ttls", encryption="wpa"})
+
+
+ identity = s:taboption("encryption", Value, "identity", translate("Identity"))
+ identity:depends({mode="sta", eap_type="peap", encryption="wpa2"})
+ identity:depends({mode="sta", eap_type="peap", encryption="wpa"})
+ identity:depends({mode="sta", eap_type="ttls", encryption="wpa2"})
+ identity:depends({mode="sta", eap_type="ttls", encryption="wpa"})
+ identity:depends({mode="sta-wds", eap_type="peap", encryption="wpa2"})
+ identity:depends({mode="sta-wds", eap_type="peap", encryption="wpa"})
+ identity:depends({mode="sta-wds", eap_type="ttls", encryption="wpa2"})
+ identity:depends({mode="sta-wds", eap_type="ttls", encryption="wpa"})
+
+ password = s:taboption("encryption", Value, "password", translate("Password"))
+ password:depends({mode="sta", eap_type="peap", encryption="wpa2"})
+ password:depends({mode="sta", eap_type="peap", encryption="wpa"})
+ password:depends({mode="sta", eap_type="ttls", encryption="wpa2"})
+ password:depends({mode="sta", eap_type="ttls", encryption="wpa"})
+ password:depends({mode="sta-wds", eap_type="peap", encryption="wpa2"})
+ password:depends({mode="sta-wds", eap_type="peap", encryption="wpa"})
+ password:depends({mode="sta-wds", eap_type="ttls", encryption="wpa2"})
+ password:depends({mode="sta-wds", eap_type="ttls", encryption="wpa"})
+end
+
+if hwtype == "atheros" or hwtype == "mac80211" or hwtype == "prism2" then
+ local wpasupplicant = fs.access("/usr/sbin/wpa_supplicant")
+ local hostcli = fs.access("/usr/sbin/hostapd_cli")
+ if hostcli and wpasupplicant then
+ wps = s:taboption("encryption", Flag, "wps_pushbutton", translate("Enable WPS pushbutton, requires WPA(2)-PSK"))
+ wps.enabled = "1"
+ wps.disabled = "0"
+ wps.rmempty = false
+ wps:depends("encryption", "psk")
+ wps:depends("encryption", "psk2")
+ wps:depends("encryption", "psk-mixed")
+ end
+end
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi_add.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi_add.lua
new file mode 100644
index 000000000..cb4264886
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi_add.lua
@@ -0,0 +1,184 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2009 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+local fs = require "nixio.fs"
+local nw = require "luci.model.network"
+local fw = require "luci.model.firewall"
+local uci = require "luci.model.uci".cursor()
+local http = require "luci.http"
+
+local iw = luci.sys.wifi.getiwinfo(http.formvalue("device"))
+
+local has_firewall = fs.access("/etc/config/firewall")
+
+if not iw then
+ luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless"))
+ return
+end
+
+m = SimpleForm("network", translate("Join Network: Settings"))
+m.cancel = translate("Back to scan results")
+m.reset = false
+
+function m.on_cancel()
+ local dev = http.formvalue("device")
+ http.redirect(luci.dispatcher.build_url(
+ dev and "admin/network/wireless_join?device=" .. dev
+ or "admin/network/wireless"
+ ))
+end
+
+nw.init(uci)
+fw.init(uci)
+
+m.hidden = {
+ device = http.formvalue("device"),
+ join = http.formvalue("join"),
+ channel = http.formvalue("channel"),
+ mode = http.formvalue("mode"),
+ bssid = http.formvalue("bssid"),
+ wep = http.formvalue("wep"),
+ wpa_suites = http.formvalue("wpa_suites"),
+ wpa_version = http.formvalue("wpa_version")
+}
+
+if iw and iw.mbssid_support then
+ replace = m:field(Flag, "replace", translate("Replace wireless configuration"),
+ translate("An additional network will be created if you leave this unchecked."))
+
+ function replace.cfgvalue() return "1" end
+else
+ replace = m:field(DummyValue, "replace", translate("Replace wireless configuration"))
+ replace.default = translate("The hardware is not multi-SSID capable and the existing " ..
+ "configuration will be replaced if you proceed.")
+
+ function replace.formvalue() return "1" end
+end
+
+if http.formvalue("wep") == "1" then
+ key = m:field(Value, "key", translate("WEP passphrase"),
+ translate("Specify the secret encryption key here."))
+
+ key.password = true
+ key.datatype = "wepkey"
+
+elseif (tonumber(m.hidden.wpa_version) or 0) > 0 and
+ (m.hidden.wpa_suites == "PSK" or m.hidden.wpa_suites == "PSK2")
+then
+ key = m:field(Value, "key", translate("WPA passphrase"),
+ translate("Specify the secret encryption key here."))
+
+ key.password = true
+ key.datatype = "wpakey"
+ --m.hidden.wpa_suite = (tonumber(http.formvalue("wpa_version")) or 0) >= 2 and "psk2" or "psk"
+end
+
+newnet = m:field(Value, "_netname_new", translate("Name of the new network"),
+ translate("The allowed characters are: <code>A-Z</code>, <code>a-z</code>, " ..
+ "<code>0-9</code> and <code>_</code>"
+ ))
+
+newnet.default = m.hidden.mode == "Ad-Hoc" and "mesh" or "wwan"
+newnet.datatype = "uciname"
+
+if has_firewall then
+ fwzone = m:field(Value, "_fwzone",
+ translate("Create / Assign firewall-zone"),
+ translate("Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it."))
+
+ fwzone.template = "cbi/firewall_zonelist"
+ fwzone.default = m.hidden.mode == "Ad-Hoc" and "mesh" or "wan"
+end
+
+function newnet.parse(self, section)
+ local net, zone
+
+ if has_firewall then
+ local zval = fwzone:formvalue(section)
+ zone = fw:get_zone(zval)
+
+ 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
+
+ local wdev = nw:get_wifidev(m.hidden.device)
+
+ wdev:set("disabled", false)
+ wdev:set("channel", m.hidden.channel)
+
+ if replace:formvalue(section) then
+ local n
+ for _, n in ipairs(wdev:get_wifinets()) do
+ wdev:del_wifinet(n)
+ end
+ end
+
+ local wconf = {
+ device = m.hidden.device,
+ ssid = m.hidden.join,
+ mode = (m.hidden.mode == "Ad-Hoc" and "adhoc" or "sta")
+ }
+
+ if m.hidden.wep == "1" then
+ wconf.encryption = "wep-open"
+ wconf.key = "1"
+ wconf.key1 = key and key:formvalue(section) or ""
+ elseif (tonumber(m.hidden.wpa_version) or 0) > 0 then
+ wconf.encryption = (tonumber(m.hidden.wpa_version) or 0) >= 2 and "psk2" or "psk"
+ wconf.key = key and key:formvalue(section) or ""
+ else
+ wconf.encryption = "none"
+ end
+
+ if wconf.mode == "adhoc" or wconf.mode == "sta" then
+ wconf.bssid = m.hidden.bssid
+ end
+
+ local value = self:formvalue(section)
+ net = nw:add_network(value, { proto = "dhcp" })
+
+ if not net then
+ self.error = { [section] = "missing" }
+ else
+ wconf.network = net:name()
+
+ local wnet = wdev:add_wifinet(wconf)
+ if wnet then
+ if zone then
+ fw:del_network(net:name())
+ zone:add_network(net:name())
+ end
+
+ uci:save("wireless")
+ uci:save("network")
+ uci:save("firewall")
+
+ luci.http.redirect(wnet:adminlink())
+ end
+ end
+end
+
+if has_firewall then
+ function fwzone.cfgvalue(self, section)
+ self.iface = section
+ local z = fw:get_zone_by_network(section)
+ return z and z:name()
+ end
+end
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_status/processes.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_status/processes.lua
new file mode 100644
index 000000000..c34c70ffa
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_status/processes.lua
@@ -0,0 +1,44 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.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
+
+$Id$
+]]--
+f = SimpleForm("processes", translate("Processes"), translate("This list gives an overview over currently running system processes and their status."))
+f.reset = false
+f.submit = false
+
+t = f:section(Table, luci.sys.process.list())
+t:option(DummyValue, "PID", translate("PID"))
+t:option(DummyValue, "USER", translate("Owner"))
+t:option(DummyValue, "COMMAND", translate("Command"))
+t:option(DummyValue, "%CPU", translate("CPU usage (%)"))
+t:option(DummyValue, "%MEM", translate("Memory usage (%)"))
+
+hup = t:option(Button, "_hup", translate("Hang Up"))
+hup.inputstyle = "reload"
+function hup.write(self, section)
+ null, self.tag_error[section] = luci.sys.process.signal(section, 1)
+end
+
+term = t:option(Button, "_term", translate("Terminate"))
+term.inputstyle = "remove"
+function term.write(self, section)
+ null, self.tag_error[section] = luci.sys.process.signal(section, 15)
+end
+
+kill = t:option(Button, "_kill", translate("Kill"))
+kill.inputstyle = "reset"
+function kill.write(self, section)
+ null, self.tag_error[section] = luci.sys.process.signal(section, 9)
+end
+
+return f \ No newline at end of file
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/admin.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/admin.lua
new file mode 100644
index 000000000..a3da6beb6
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/admin.lua
@@ -0,0 +1,130 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2011 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+local fs = require "nixio.fs"
+
+m = Map("system", translate("Router Password"),
+ translate("Changes the administrator password for accessing the device"))
+
+s = m:section(TypedSection, "_dummy", "")
+s.addremove = false
+s.anonymous = true
+
+pw1 = s:option(Value, "pw1", translate("Password"))
+pw1.password = true
+
+pw2 = s:option(Value, "pw2", translate("Confirmation"))
+pw2.password = true
+
+function s.cfgsections()
+ return { "_pass" }
+end
+
+function m.on_commit(map)
+ local v1 = pw1:formvalue("_pass")
+ local v2 = pw2:formvalue("_pass")
+
+ if v1 and v2 and #v1 > 0 and #v2 > 0 then
+ if v1 == v2 then
+ if luci.sys.user.setpasswd(luci.dispatcher.context.authuser, v1) == 0 then
+ m.message = translate("Password successfully changed!")
+ else
+ m.message = translate("Unknown Error, password not changed!")
+ end
+ else
+ m.message = translate("Given password confirmation did not match, password not changed!")
+ end
+ end
+end
+
+
+if fs.access("/etc/config/dropbear") then
+
+m2 = Map("dropbear", translate("SSH Access"),
+ translate("Dropbear offers <abbr title=\"Secure Shell\">SSH</abbr> network shell access and an integrated <abbr title=\"Secure Copy\">SCP</abbr> server"))
+
+s = m2:section(TypedSection, "dropbear", translate("Dropbear Instance"))
+s.anonymous = true
+s.addremove = true
+
+
+ni = s:option(Value, "Interface", translate("Interface"),
+ translate("Listen only on the given interface or, if unspecified, on all"))
+
+ni.template = "cbi/network_netlist"
+ni.nocreate = true
+ni.unspecified = true
+
+
+pt = s:option(Value, "Port", translate("Port"),
+ translate("Specifies the listening port of this <em>Dropbear</em> instance"))
+
+pt.datatype = "port"
+pt.default = 22
+
+
+pa = s:option(Flag, "PasswordAuth", translate("Password authentication"),
+ translate("Allow <abbr title=\"Secure Shell\">SSH</abbr> password authentication"))
+
+pa.enabled = "on"
+pa.disabled = "off"
+pa.default = pa.enabled
+pa.rmempty = false
+
+
+ra = s:option(Flag, "RootPasswordAuth", translate("Allow root logins with password"),
+ translate("Allow the <em>root</em> user to login with password"))
+
+ra.enabled = "on"
+ra.disabled = "off"
+ra.default = ra.enabled
+
+
+gp = s:option(Flag, "GatewayPorts", translate("Gateway ports"),
+ translate("Allow remote hosts to connect to local SSH forwarded ports"))
+
+gp.enabled = "on"
+gp.disabled = "off"
+gp.default = gp.disabled
+
+
+s2 = m2:section(TypedSection, "_dummy", translate("SSH-Keys"),
+ translate("Here you can paste public SSH-Keys (one per line) for SSH public-key authentication."))
+s2.addremove = false
+s2.anonymous = true
+s2.template = "cbi/tblsection"
+
+function s2.cfgsections()
+ return { "_keys" }
+end
+
+keys = s2:option(TextValue, "_data", "")
+keys.wrap = "off"
+keys.rows = 3
+keys.rmempty = false
+
+function keys.cfgvalue()
+ return fs.readfile("/etc/dropbear/authorized_keys") or ""
+end
+
+function keys.write(self, section, value)
+ if value then
+ fs.writefile("/etc/dropbear/authorized_keys", value:gsub("\r\n", "\n"))
+ end
+end
+
+end
+
+return m, m2
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/backupfiles.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/backupfiles.lua
new file mode 100644
index 000000000..a063ec0aa
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/backupfiles.lua
@@ -0,0 +1,92 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2011 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+
+if luci.http.formvalue("cbid.luci.1._list") then
+ luci.http.redirect(luci.dispatcher.build_url("admin/system/flashops/backupfiles") .. "?display=list")
+elseif luci.http.formvalue("cbid.luci.1._edit") then
+ luci.http.redirect(luci.dispatcher.build_url("admin/system/flashops/backupfiles") .. "?display=edit")
+ return
+end
+
+m = SimpleForm("luci", translate("Backup file list"))
+m:append(Template("admin_system/backupfiles"))
+
+if luci.http.formvalue("display") ~= "list" then
+ f = m:section(SimpleSection, nil, translate("This is a list of shell glob patterns for matching files and directories to include during sysupgrade. Modified files in /etc/config/ and certain other configurations are automatically preserved."))
+
+ l = f:option(Button, "_list", translate("Show current backup file list"))
+ l.inputtitle = translate("Open list...")
+ l.inputstyle = "apply"
+
+ c = f:option(TextValue, "_custom")
+ c.rmempty = false
+ c.cols = 70
+ c.rows = 30
+
+ c.cfgvalue = function(self, section)
+ return nixio.fs.readfile("/etc/sysupgrade.conf")
+ end
+
+ c.write = function(self, section, value)
+ value = value:gsub("\r\n?", "\n")
+ return nixio.fs.writefile("/etc/sysupgrade.conf", value)
+ end
+else
+ m.submit = false
+ m.reset = false
+
+ f = m:section(SimpleSection, nil, translate("Below is the determined list of files to backup. It consists of changed configuration files marked by opkg, essential base files and the user defined backup patterns."))
+
+ l = f:option(Button, "_edit", translate("Back to configuration"))
+ l.inputtitle = translate("Close list...")
+ l.inputstyle = "link"
+
+
+ d = f:option(DummyValue, "_detected")
+ d.rawhtml = true
+ d.cfgvalue = function(s)
+ local list = io.popen(
+ "( find $(sed -ne '/^[[:space:]]*$/d; /^#/d; p' /etc/sysupgrade.conf " ..
+ "/lib/upgrade/keep.d/* 2>/dev/null) -type f 2>/dev/null; " ..
+ "opkg list-changed-conffiles ) | sort -u"
+ )
+
+ if list then
+ local files = { "<ul>" }
+
+ while true do
+ local ln = list:read("*l")
+ if not ln then
+ break
+ else
+ files[#files+1] = "<li>"
+ files[#files+1] = luci.util.pcdata(ln)
+ files[#files+1] = "</li>"
+ end
+ end
+
+ list:close()
+ files[#files+1] = "</ul>"
+
+ return table.concat(files, "")
+ end
+
+ return "<em>" .. translate("No files found") .. "</em>"
+ end
+
+end
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/buttons.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/buttons.lua
new file mode 100644
index 000000000..d7581830d
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/buttons.lua
@@ -0,0 +1,38 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+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
+
+$Id$
+]]--
+
+m = Map("system", translate("Buttons"),
+ translate("This page allows the configuration of custom button actions"))
+
+s = m:section(TypedSection, "button", "")
+s.anonymous = true
+s.addremove = true
+
+s:option(Value, "button", translate("Name"))
+
+act = s:option(ListValue, "action",
+ translate("Action"),
+ translate("Specifies the button state to handle"))
+
+act:value("released")
+
+s:option(Value, "handler",
+ translate("Handler"),
+ translate("Path to executable which handles the button event"))
+
+min = s:option(Value, "min", translate("Minimum hold time"))
+min.rmempty = true
+
+max = s:option(Value, "max", translate("Maximum hold time"))
+max.rmempty = true
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/crontab.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/crontab.lua
new file mode 100644
index 000000000..3e1b9d85d
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/crontab.lua
@@ -0,0 +1,38 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008-2013 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+local fs = require "nixio.fs"
+local cronfile = "/etc/crontabs/root"
+
+f = SimpleForm("crontab", translate("Scheduled Tasks"), translate("This is the system crontab in which scheduled tasks can be defined."))
+
+t = f:field(TextValue, "crons")
+t.rmempty = true
+t.rows = 10
+function t.cfgvalue()
+ return fs.readfile(cronfile) or ""
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ if data.crons then
+ fs.writefile(cronfile, data.crons:gsub("\r\n", "\n"))
+ luci.sys.call("/usr/bin/crontab %q" % cronfile)
+ end
+ end
+ return true
+end
+
+return f
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab.lua
new file mode 100644
index 000000000..8ac3d6d5e
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab.lua
@@ -0,0 +1,156 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+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
+
+$Id$
+]]--
+
+require("luci.tools.webadmin")
+
+local fs = require "nixio.fs"
+local util = require "nixio.util"
+
+local devices = {}
+util.consume((fs.glob("/dev/sd*")), devices)
+util.consume((fs.glob("/dev/hd*")), devices)
+util.consume((fs.glob("/dev/scd*")), devices)
+util.consume((fs.glob("/dev/mmc*")), devices)
+
+local size = {}
+for i, dev in ipairs(devices) do
+ local s = tonumber((fs.readfile("/sys/class/block/%s/size" % dev:sub(6))))
+ size[dev] = s and math.floor(s / 2048)
+end
+
+
+m = Map("fstab", translate("Mount Points"))
+
+local mounts = luci.sys.mounts()
+
+v = m:section(Table, mounts, translate("Mounted file systems"))
+
+fs = v:option(DummyValue, "fs", translate("Filesystem"))
+
+mp = v:option(DummyValue, "mountpoint", translate("Mount Point"))
+
+avail = v:option(DummyValue, "avail", translate("Available"))
+function avail.cfgvalue(self, section)
+ return luci.tools.webadmin.byte_format(
+ ( tonumber(mounts[section].available) or 0 ) * 1024
+ ) .. " / " .. luci.tools.webadmin.byte_format(
+ ( tonumber(mounts[section].blocks) or 0 ) * 1024
+ )
+end
+
+used = v:option(DummyValue, "used", translate("Used"))
+function used.cfgvalue(self, section)
+ return ( mounts[section].percent or "0%" ) .. " (" ..
+ luci.tools.webadmin.byte_format(
+ ( tonumber(mounts[section].used) or 0 ) * 1024
+ ) .. ")"
+end
+
+
+
+mount = m:section(TypedSection, "mount", translate("Mount Points"), translate("Mount Points define at which point a memory device will be attached to the filesystem"))
+mount.anonymous = true
+mount.addremove = true
+mount.template = "cbi/tblsection"
+mount.extedit = luci.dispatcher.build_url("admin/system/fstab/mount/%s")
+
+mount.create = function(...)
+ local sid = TypedSection.create(...)
+ if sid then
+ luci.http.redirect(mount.extedit % sid)
+ return
+ end
+end
+
+
+mount:option(Flag, "enabled", translate("Enabled")).rmempty = false
+
+dev = mount:option(DummyValue, "device", translate("Device"))
+dev.cfgvalue = function(self, section)
+ local v
+
+ v = m.uci:get("fstab", section, "uuid")
+ if v then return "UUID: %s" % v end
+
+ v = m.uci:get("fstab", section, "label")
+ if v then return "Label: %s" % v end
+
+ v = Value.cfgvalue(self, section) or "?"
+ return size[v] and "%s (%s MB)" % {v, size[v]} or v
+end
+
+mp = mount:option(DummyValue, "target", translate("Mount Point"))
+mp.cfgvalue = function(self, section)
+ if m.uci:get("fstab", section, "is_rootfs") == "1" then
+ return "/overlay"
+ else
+ return Value.cfgvalue(self, section) or "?"
+ end
+end
+
+fs = mount:option(DummyValue, "fstype", translate("Filesystem"))
+fs.cfgvalue = function(self, section)
+ return Value.cfgvalue(self, section) or "?"
+end
+
+op = mount:option(DummyValue, "options", translate("Options"))
+op.cfgvalue = function(self, section)
+ return Value.cfgvalue(self, section) or "defaults"
+end
+
+rf = mount:option(DummyValue, "is_rootfs", translate("Root"))
+rf.cfgvalue = function(self, section)
+ return Value.cfgvalue(self, section) == "1"
+ and translate("yes") or translate("no")
+end
+
+ck = mount:option(DummyValue, "enabled_fsck", translate("Check"))
+ck.cfgvalue = function(self, section)
+ return Value.cfgvalue(self, section) == "1"
+ and translate("yes") or translate("no")
+end
+
+
+swap = m:section(TypedSection, "swap", "SWAP", translate("If your physical memory is insufficient unused data can be temporarily swapped to a swap-device resulting in a higher amount of usable <abbr title=\"Random Access Memory\">RAM</abbr>. Be aware that swapping data is a very slow process as the swap-device cannot be accessed with the high datarates of the <abbr title=\"Random Access Memory\">RAM</abbr>."))
+swap.anonymous = true
+swap.addremove = true
+swap.template = "cbi/tblsection"
+swap.extedit = luci.dispatcher.build_url("admin/system/fstab/swap/%s")
+
+swap.create = function(...)
+ local sid = TypedSection.create(...)
+ if sid then
+ luci.http.redirect(swap.extedit % sid)
+ return
+ end
+end
+
+
+swap:option(Flag, "enabled", translate("Enabled")).rmempty = false
+
+dev = swap:option(DummyValue, "device", translate("Device"))
+dev.cfgvalue = function(self, section)
+ local v
+
+ v = m.uci:get("fstab", section, "uuid")
+ if v then return "UUID: %s" % v end
+
+ v = m.uci:get("fstab", section, "label")
+ if v then return "Label: %s" % v end
+
+ v = Value.cfgvalue(self, section) or "?"
+ return size[v] and "%s (%s MB)" % {v, size[v]} or v
+end
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab/mount.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab/mount.lua
new file mode 100644
index 000000000..bac886888
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab/mount.lua
@@ -0,0 +1,108 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2010 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+local fs = require "nixio.fs"
+local util = require "nixio.util"
+
+local has_extroot = fs.access("/sbin/block")
+local has_fscheck = fs.access("/usr/sbin/e2fsck")
+
+local devices = {}
+util.consume((fs.glob("/dev/sd*")), devices)
+util.consume((fs.glob("/dev/hd*")), devices)
+util.consume((fs.glob("/dev/scd*")), devices)
+util.consume((fs.glob("/dev/mmc*")), devices)
+
+local size = {}
+for i, dev in ipairs(devices) do
+ local s = tonumber((fs.readfile("/sys/class/block/%s/size" % dev:sub(6))))
+ size[dev] = s and math.floor(s / 2048)
+end
+
+
+m = Map("fstab", translate("Mount Points - Mount Entry"))
+m.redirect = luci.dispatcher.build_url("admin/system/fstab")
+
+if not arg[1] or m.uci:get("fstab", arg[1]) ~= "mount" then
+ luci.http.redirect(m.redirect)
+ return
+end
+
+
+
+mount = m:section(NamedSection, arg[1], "mount", translate("Mount Entry"))
+mount.anonymous = true
+mount.addremove = false
+
+mount:tab("general", translate("General Settings"))
+mount:tab("advanced", translate("Advanced Settings"))
+
+
+mount:taboption("general", Flag, "enabled", translate("Enable this mount")).rmempty = false
+
+
+o = mount:taboption("general", Value, "device", translate("Device"),
+ translate("The device file of the memory or partition (<abbr title=\"for example\">e.g.</abbr> <code>/dev/sda1</code>)"))
+
+for i, d in ipairs(devices) do
+ o:value(d, size[d] and "%s (%s MB)" % {d, size[d]})
+end
+
+o = mount:taboption("advanced", Value, "uuid", translate("UUID"),
+ translate("If specified, mount the device by its UUID instead of a fixed device node"))
+
+o = mount:taboption("advanced", Value, "label", translate("Label"),
+ translate("If specified, mount the device by the partition label instead of a fixed device node"))
+
+
+o = mount:taboption("general", Value, "target", translate("Mount point"),
+ translate("Specifies the directory the device is attached to"))
+
+o:depends("is_rootfs", "")
+
+
+o = mount:taboption("general", Value, "fstype", translate("Filesystem"),
+ translate("The filesystem that was used to format the memory (<abbr title=\"for example\">e.g.</abbr> <samp><abbr title=\"Third Extended Filesystem\">ext3</abbr></samp>)"))
+
+local fs
+for fs in io.lines("/proc/filesystems") do
+ fs = fs:match("%S+")
+ if fs ~= "nodev" then
+ o:value(fs)
+ end
+end
+
+
+o = mount:taboption("advanced", Value, "options", translate("Mount options"),
+ translate("See \"mount\" manpage for details"))
+
+o.placeholder = "defaults"
+
+
+if has_extroot then
+ o = mount:taboption("general", Flag, "is_rootfs", translate("Use as root filesystem"),
+ translate("Configures this mount as overlay storage for block-extroot"))
+
+ o:depends("fstype", "jffs")
+ o:depends("fstype", "ext2")
+ o:depends("fstype", "ext3")
+ o:depends("fstype", "ext4")
+end
+
+if has_fscheck then
+ o = mount:taboption("general", Flag, "enabled_fsck", translate("Run filesystem check"),
+ translate("Run a filesystem check before mounting the device"))
+end
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab/swap.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab/swap.lua
new file mode 100644
index 000000000..01d0ff90a
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/fstab/swap.lua
@@ -0,0 +1,65 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2010 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+local fs = require "nixio.fs"
+local util = require "nixio.util"
+
+local devices = {}
+util.consume((fs.glob("/dev/sd*")), devices)
+util.consume((fs.glob("/dev/hd*")), devices)
+util.consume((fs.glob("/dev/scd*")), devices)
+util.consume((fs.glob("/dev/mmc*")), devices)
+
+local size = {}
+for i, dev in ipairs(devices) do
+ local s = tonumber((fs.readfile("/sys/class/block/%s/size" % dev:sub(6))))
+ size[dev] = s and math.floor(s / 2048)
+end
+
+
+m = Map("fstab", translate("Mount Points - Swap Entry"))
+m.redirect = luci.dispatcher.build_url("admin/system/fstab")
+
+if not arg[1] or m.uci:get("fstab", arg[1]) ~= "swap" then
+ luci.http.redirect(m.redirect)
+ return
+end
+
+
+mount = m:section(NamedSection, arg[1], "swap", translate("Swap Entry"))
+mount.anonymous = true
+mount.addremove = false
+
+mount:tab("general", translate("General Settings"))
+mount:tab("advanced", translate("Advanced Settings"))
+
+
+mount:taboption("general", Flag, "enabled", translate("Enable this swap")).rmempty = false
+
+
+o = mount:taboption("general", Value, "device", translate("Device"),
+ translate("The device file of the memory or partition (<abbr title=\"for example\">e.g.</abbr> <code>/dev/sda1</code>)"))
+
+for i, d in ipairs(devices) do
+ o:value(d, size[d] and "%s (%s MB)" % {d, size[d]})
+end
+
+o = mount:taboption("advanced", Value, "uuid", translate("UUID"),
+ translate("If specified, mount the device by its UUID instead of a fixed device node"))
+
+o = mount:taboption("advanced", Value, "label", translate("Label"),
+ translate("If specified, mount the device by the partition label instead of a fixed device node"))
+
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/ipkg.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/ipkg.lua
new file mode 100644
index 000000000..e9ff5b146
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/ipkg.lua
@@ -0,0 +1,36 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008-2011 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+local ipkgfile = "/etc/opkg.conf"
+
+f = SimpleForm("ipkgconf", translate("OPKG-Configuration"))
+
+f:append(Template("admin_system/ipkg"))
+
+t = f:field(TextValue, "lines")
+t.rows = 10
+function t.cfgvalue()
+ return nixio.fs.readfile(ipkgfile) or ""
+end
+
+function t.write(self, section, data)
+ return nixio.fs.writefile(ipkgfile, data:gsub("\r\n", "\n"))
+end
+
+function f.handle(self, state, data)
+ return true
+end
+
+return f
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/leds.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/leds.lua
new file mode 100644
index 000000000..b912f5ece
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/leds.lua
@@ -0,0 +1,129 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+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
+
+$Id$
+]]--
+m = Map("system", translate("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), translate("Customizes the behaviour of the device <abbr title=\"Light Emitting Diode\">LED</abbr>s if possible."))
+
+local sysfs_path = "/sys/class/leds/"
+local leds = {}
+
+local fs = require "nixio.fs"
+local util = require "nixio.util"
+
+if fs.access(sysfs_path) then
+ leds = util.consume((fs.dir(sysfs_path)))
+end
+
+if #leds == 0 then
+ return m
+end
+
+
+s = m:section(TypedSection, "led", "")
+s.anonymous = true
+s.addremove = true
+
+function s.parse(self, ...)
+ TypedSection.parse(self, ...)
+ os.execute("/etc/init.d/led enable")
+end
+
+
+s:option(Value, "name", translate("Name"))
+
+
+sysfs = s:option(ListValue, "sysfs", translate("<abbr title=\"Light Emitting Diode\">LED</abbr> Name"))
+for k, v in ipairs(leds) do
+ sysfs:value(v)
+end
+
+s:option(Flag, "default", translate("Default state")).rmempty = false
+
+
+trigger = s:option(ListValue, "trigger", translate("Trigger"))
+
+local triggers = fs.readfile(sysfs_path .. leds[1] .. "/trigger")
+for t in triggers:gmatch("[%w-]+") do
+ trigger:value(t, translate(t:gsub("-", "")))
+end
+
+
+delayon = s:option(Value, "delayon", translate ("On-State Delay"))
+delayon:depends("trigger", "timer")
+
+delayoff = s:option(Value, "delayoff", translate ("Off-State Delay"))
+delayoff:depends("trigger", "timer")
+
+
+dev = s:option(ListValue, "_net_dev", translate("Device"))
+dev.rmempty = true
+dev:value("")
+dev:depends("trigger", "netdev")
+
+function dev.cfgvalue(self, section)
+ return m.uci:get("system", section, "dev")
+end
+
+function dev.write(self, section, value)
+ m.uci:set("system", section, "dev", value)
+end
+
+function dev.remove(self, section)
+ local t = trigger:formvalue(section)
+ if t ~= "netdev" and t ~= "usbdev" then
+ m.uci:delete("system", section, "dev")
+ end
+end
+
+for k, v in pairs(luci.sys.net.devices()) do
+ if v ~= "lo" then
+ dev:value(v)
+ end
+end
+
+
+mode = s:option(MultiValue, "mode", translate("Trigger Mode"))
+mode.rmempty = true
+mode:depends("trigger", "netdev")
+mode:value("link", translate("Link On"))
+mode:value("tx", translate("Transmit"))
+mode:value("rx", translate("Receive"))
+
+
+usbdev = s:option(ListValue, "_usb_dev", translate("USB Device"))
+usbdev:depends("trigger", "usbdev")
+usbdev.rmempty = true
+usbdev:value("")
+
+function usbdev.cfgvalue(self, section)
+ return m.uci:get("system", section, "dev")
+end
+
+function usbdev.write(self, section, value)
+ m.uci:set("system", section, "dev", value)
+end
+
+function usbdev.remove(self, section)
+ local t = trigger:formvalue(section)
+ if t ~= "netdev" and t ~= "usbdev" then
+ m.uci:delete("system", section, "dev")
+ end
+end
+
+for p in nixio.fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer") do
+ local id = p:match("%d+-%d+")
+ local mf = nixio.fs.readfile("/sys/bus/usb/devices/" .. id .. "/manufacturer") or "?"
+ local pr = nixio.fs.readfile("/sys/bus/usb/devices/" .. id .. "/product") or "?"
+ usbdev:value(id, "%s (%s - %s)" %{ id, mf, pr })
+end
+
+return m
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/startup.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/startup.lua
new file mode 100644
index 000000000..fc35f774a
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/startup.lua
@@ -0,0 +1,109 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+Copyright 2010 Manuel Munz <freifunk at somakoma dot de>
+
+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
+
+$Id$
+]]--
+
+require "luci.fs"
+require "luci.sys"
+require "luci.util"
+
+local inits = { }
+
+for _, name in ipairs(luci.sys.init.names()) do
+ local index = luci.sys.init.index(name)
+ local enabled = luci.sys.init.enabled(name)
+
+ if index < 255 then
+ inits["%02i.%s" % { index, name }] = {
+ name = name,
+ index = tostring(index),
+ enabled = enabled
+ }
+ end
+end
+
+
+m = SimpleForm("initmgr", translate("Initscripts"), translate("You can enable or disable installed init scripts here. Changes will applied after a device reboot.<br /><strong>Warning: If you disable essential init scripts like \"network\", your device might become inaccessible!</strong>"))
+m.reset = false
+m.submit = false
+
+
+s = m:section(Table, inits)
+
+i = s:option(DummyValue, "index", translate("Start priority"))
+n = s:option(DummyValue, "name", translate("Initscript"))
+
+
+e = s:option(Button, "endisable", translate("Enable/Disable"))
+
+e.render = function(self, section, scope)
+ if inits[section].enabled then
+ self.title = translate("Enabled")
+ self.inputstyle = "save"
+ else
+ self.title = translate("Disabled")
+ self.inputstyle = "reset"
+ end
+
+ Button.render(self, section, scope)
+end
+
+e.write = function(self, section)
+ if inits[section].enabled then
+ inits[section].enabled = false
+ return luci.sys.init.disable(inits[section].name)
+ else
+ inits[section].enabled = true
+ return luci.sys.init.enable(inits[section].name)
+ end
+end
+
+
+start = s:option(Button, "start", translate("Start"))
+start.inputstyle = "apply"
+start.write = function(self, section)
+ luci.sys.call("/etc/init.d/%s %s >/dev/null" %{ inits[section].name, self.option })
+end
+
+restart = s:option(Button, "restart", translate("Restart"))
+restart.inputstyle = "reload"
+restart.write = start.write
+
+stop = s:option(Button, "stop", translate("Stop"))
+stop.inputstyle = "remove"
+stop.write = start.write
+
+
+
+f = SimpleForm("rc", translate("Local Startup"),
+ translate("This is the content of /etc/rc.local. Insert your own commands here (in front of 'exit 0') to execute them at the end of the boot process."))
+
+t = f:field(TextValue, "rcs")
+t.rmempty = true
+t.rows = 20
+
+function t.cfgvalue()
+ return luci.fs.readfile("/etc/rc.local") or ""
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ if data.rcs then
+ luci.fs.writefile("/etc/rc.local", data.rcs:gsub("\r\n", "\n"))
+ end
+ end
+ return true
+end
+
+return m, f
diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/system.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/system.lua
new file mode 100644
index 000000000..5e82289bd
--- /dev/null
+++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/system.lua
@@ -0,0 +1,228 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2011 Jo-Philipp Wich <xm@subsignal.org>
+
+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
+
+$Id$
+]]--
+
+require("luci.sys")
+require("luci.sys.zoneinfo")
+require("luci.tools.webadmin")
+require("luci.fs")
+require("luci.config")
+
+local m, s, o
+local has_ntpd = luci.fs.access("/usr/sbin/ntpd")
+
+m = Map("system", translate("System"), translate("Here you can configure the basic aspects of your device like its hostname or the timezone."))
+m:chain("luci")
+
+
+s = m:section(TypedSection, "system", translate("System Properties"))
+s.anonymous = true
+s.addremove = false
+
+s:tab("general", translate("General Settings"))
+s:tab("logging", translate("Logging"))
+s:tab("language", translate("Language and Style"))
+
+
+--
+-- System Properties
+--
+
+o = s:taboption("general", DummyValue, "_systime", translate("Local Time"))
+o.template = "admin_system/clock_status"
+
+
+o = s:taboption("general", Value, "hostname", translate("Hostname"))
+o.datatype = "hostname"
+
+function o.write(self, section, value)
+ Value.write(self, section, value)
+ luci.sys.hostname(value)
+end
+
+
+o = s:taboption("general", ListValue, "zonename", translate("Timezone"))
+o:value("UTC")
+
+for i, zone in ipairs(luci.sys.zoneinfo.TZ) do
+ o:value(zone[1])
+end
+
+function o.write(self, section, value)
+ local function lookup_zone(title)
+ for _, zone in ipairs(luci.sys.zoneinfo.TZ) do
+ if zone[1] == title then return zone[2] end
+ end
+ end
+
+ AbstractValue.write(self, section, value)
+ local timezone = lookup_zone(value) or "GMT0"
+ self.map.uci:set("system", section, "timezone", timezone)
+ luci.fs.writefile("/etc/TZ", timezone .. "\n")
+end
+
+
+--
+-- Logging
+--
+
+o = s:taboption("logging", Value, "log_size", translate("System log buffer size"), "kiB")
+o.optional = true
+o.placeholder = 16
+o.datatype = "uinteger"
+
+o = s:taboption("logging", Value, "log_ip", translate("External system log server"))
+o.optional = true
+o.placeholder = "0.0.0.0"
+o.datatype = "ip4addr"
+
+o = s:taboption("logging", Value, "log_port", translate("External system log server port"))
+o.optional = true
+o.placeholder = 514
+o.datatype = "port"
+
+o = s:taboption("logging", ListValue, "conloglevel", translate("Log output level"))
+o:value(8, translate("Debug"))
+o:value(7, translate("Info"))
+o:value(6, translate("Notice"))
+o:value(5, translate("Warning"))
+o:value(4, translate("Error"))
+o:value(3, translate("Critical"))
+o:value(2, translate("Alert"))
+o:value(1, translate("Emergency"))
+
+o = s:taboption("logging", ListValue, "cronloglevel", translate("Cron Log Level"))
+o.default = 8
+o:value(5, translate("Debug"))
+o:value(8, translate("Normal"))
+o:value(9, translate("Warning"))
+
+
+--
+-- Langauge & Style
+--
+
+o = s:taboption("language", ListValue, "_lang", translate("Language"))
+o:value("auto")
+
+local i18ndir = luci.i18n.i18ndir .. "base."
+for k, v in luci.util.kspairs(luci.config.languages) do
+ local file = i18ndir .. k:gsub("_", "-")
+ if k:sub(1, 1) ~= "." and luci.fs.access(file .. ".lmo") then
+ o:value(k, v)
+ end
+end
+
+function o.cfgvalue(...)
+ return m.uci:get("luci", "main", "lang")
+end
+
+function o.write(self, section, value)
+ m.uci:set("luci", "main", "lang", value)
+end
+
+
+o = s:taboption("language", ListValue, "_mediaurlbase", translate("Design"))
+for k, v in pairs(luci.config.themes) do
+ if k:sub(1, 1) ~= "." then
+ o:value(v, k)
+ end
+end
+
+function o.cfgvalue(...)
+ return m.uci:get("luci", "main", "mediaurlbase")
+end
+
+function o.write(self, section, value)
+ m.uci:set("luci", "main", "mediaurlbase", value)
+end
+
+
+--
+-- NTP
+--
+
+if has_ntpd then
+
+ -- timeserver setup was requested, create section and reload page
+ if m:formvalue("cbid.system._timeserver._enable") then
+ m.uci:section("system", "timeserver", "ntp",
+ {
+ server = { "0.openwrt.pool.ntp.org", "1.openwrt.pool.ntp.org", "2.openwrt.pool.ntp.org", "3.openwrt.pool.ntp.org" }
+ }
+ )
+
+ m.uci:save("system")
+ luci.http.redirect(luci.dispatcher.build_url("admin/system", arg[1]))
+ return
+ end
+
+ local has_section = false
+ m.uci:foreach("system", "timeserver",
+ function(s)
+ has_section = true
+ return false
+ end)
+
+ if not has_section then
+
+ s = m:section(TypedSection, "timeserver", translate("Time Synchronization"))
+ s.anonymous = true
+ s.cfgsections = function() return { "_timeserver" } end
+
+ x = s:option(Button, "_enable")
+ x.title = translate("Time Synchronization is not configured yet.")
+ x.inputtitle = translate("Set up Time Synchronization")
+ x.inputstyle = "apply"
+
+ else
+
+ s = m:section(TypedSection, "timeserver", translate("Time Synchronization"))
+ s.anonymous = true
+ s.addremove = false
+
+ o = s:option(Flag, "enable", translate("Enable NTP client"))
+ o.rmempty = false
+
+ function o.cfgvalue(self)
+ return luci.sys.init.enabled("sysntpd")
+ and self.enabled or self.disabled
+ end
+
+ function o.write(self, section, value)
+ if value == self.enabled then
+ luci.sys.init.enable("sysntpd")
+ luci.sys.call("env -i /etc/init.d/sysntpd start >/dev/null")
+ else
+ luci.sys.call("env -i /etc/init.d/sysntpd stop >/dev/null")
+ luci.sys.init.disable("sysntpd")
+ end
+ end
+
+
+ o = s:option(Flag, "enable_server", translate("Provide NTP server"))
+ o:depends("enable", "1")
+
+
+ o = s:option(DynamicList, "server", translate("NTP server candidates"))
+ o.datatype = "host"
+ o:depends("enable", "1")
+
+ -- retain server list even if disabled
+ function o.remove() end
+
+ end
+end
+
+return m