summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-base/luasrc
diff options
context:
space:
mode:
Diffstat (limited to 'modules/luci-base/luasrc')
-rw-r--r--modules/luci-base/luasrc/controller/admin/index.lua122
-rw-r--r--modules/luci-base/luasrc/controller/admin/uci.lua109
-rw-r--r--modules/luci-base/luasrc/dispatcher.lua92
-rw-r--r--modules/luci-base/luasrc/model/network.lua37
-rw-r--r--modules/luci-base/luasrc/view/admin_uci/changelog.htm66
-rw-r--r--modules/luci-base/luasrc/view/admin_uci/changes.htm45
-rw-r--r--modules/luci-base/luasrc/view/admin_uci/revert.htm33
-rw-r--r--modules/luci-base/luasrc/view/cbi/wireless_modefreq.htm168
-rw-r--r--modules/luci-base/luasrc/view/empty_node_placeholder.htm11
-rw-r--r--modules/luci-base/luasrc/view/lease_status.htm95
-rw-r--r--modules/luci-base/luasrc/view/wifi_assoclist.htm82
11 files changed, 824 insertions, 36 deletions
diff --git a/modules/luci-base/luasrc/controller/admin/index.lua b/modules/luci-base/luasrc/controller/admin/index.lua
new file mode 100644
index 0000000000..7e0a207437
--- /dev/null
+++ b/modules/luci-base/luasrc/controller/admin/index.lua
@@ -0,0 +1,122 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Licensed to the public under the Apache License 2.0.
+
+module("luci.controller.admin.index", package.seeall)
+
+function index()
+ function toplevel_page(page, preflookup, preftarget)
+ if preflookup and preftarget then
+ if lookup(preflookup) then
+ page.target = preftarget
+ end
+ end
+
+ if not page.target then
+ page.target = firstchild()
+ end
+ end
+
+ local uci = require("luci.model.uci").cursor()
+
+ local root = node()
+ if not root.target then
+ root.target = alias("admin")
+ root.index = true
+ end
+
+ local page = node("admin")
+
+ page.title = _("Administration")
+ page.order = 10
+ page.sysauth = "root"
+ page.sysauth_authenticator = "htmlauth"
+ page.ucidata = true
+ page.index = true
+ page.target = firstnode()
+
+ -- Empty menu tree to be populated by addons and modules
+
+ page = node("admin", "status")
+ page.title = _("Status")
+ page.order = 10
+ page.index = true
+ -- overview is from mod-admin-full
+ toplevel_page(page, "admin/status/overview", alias("admin", "status", "overview"))
+
+ page = node("admin", "system")
+ page.title = _("System")
+ page.order = 20
+ page.index = true
+ -- system/system is from mod-admin-full
+ toplevel_page(page, "admin/system/system", alias("admin", "system", "system"))
+
+ -- Only used if applications add items
+ page = node("admin", "services")
+ page.title = _("Services")
+ page.order = 40
+ page.index = true
+ toplevel_page(page, false, false)
+
+ -- Even for mod-admin-full network just uses first submenu item as landing
+ page = node("admin", "network")
+ page.title = _("Network")
+ page.order = 50
+ page.index = true
+ toplevel_page(page, false, false)
+
+ if nixio.fs.access("/etc/config/dhcp") then
+ page = entry({"admin", "dhcplease_status"}, call("lease_status"), nil)
+ page.leaf = true
+ end
+
+ local has_wifi = false
+
+ uci:foreach("wireless", "wifi-device",
+ function(s)
+ has_wifi = true
+ return false
+ end)
+
+ if has_wifi then
+ page = entry({"admin", "wireless_assoclist"}, call("wifi_assoclist"), nil)
+ page.leaf = true
+ end
+
+ -- Logout is last
+ entry({"admin", "logout"}, call("action_logout"), _("Logout"), 999)
+end
+
+function action_logout()
+ local dsp = require "luci.dispatcher"
+ local utl = require "luci.util"
+ local sid = dsp.context.authsession
+
+ if sid then
+ utl.ubus("session", "destroy", { ubus_rpc_session = sid })
+
+ luci.http.header("Set-Cookie", "sysauth=%s; expires=%s; path=%s/" %{
+ sid, 'Thu, 01 Jan 1970 01:00:00 GMT', dsp.build_url()
+ })
+ end
+
+ luci.http.redirect(dsp.build_url())
+end
+
+
+function lease_status()
+ local s = require "luci.tools.status"
+
+ luci.http.prepare_content("application/json")
+ luci.http.write('[')
+ luci.http.write_json(s.dhcp_leases())
+ luci.http.write(',')
+ luci.http.write_json(s.dhcp6_leases())
+ luci.http.write(']')
+end
+
+function wifi_assoclist()
+ local s = require "luci.tools.status"
+
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(s.wifi_assoclist())
+end
diff --git a/modules/luci-base/luasrc/controller/admin/uci.lua b/modules/luci-base/luasrc/controller/admin/uci.lua
new file mode 100644
index 0000000000..1d955dd982
--- /dev/null
+++ b/modules/luci-base/luasrc/controller/admin/uci.lua
@@ -0,0 +1,109 @@
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Copyright 2010-2015 Jo-Philipp Wich <jow@openwrt.org>
+-- Licensed to the public under the Apache License 2.0.
+
+module("luci.controller.admin.uci", package.seeall)
+
+function index()
+ local redir = luci.http.formvalue("redir", true)
+ or table.concat(luci.dispatcher.context.request, "/")
+
+ entry({"admin", "uci"}, nil, _("Configuration"))
+ entry({"admin", "uci", "changes"}, post_on({ trigger_apply = true }, "action_changes"), _("Changes"), 40).query = {redir=redir}
+ entry({"admin", "uci", "revert"}, post("action_revert"), _("Revert"), 30).query = {redir=redir}
+
+ local node
+ local authen = function(checkpass, allowed_users)
+ return "root", luci.http.formvalue("sid")
+ end
+
+ node = entry({"admin", "uci", "apply_rollback"}, post("action_apply_rollback"), nil)
+ node.cors = true
+ node.sysauth_authenticator = authen
+
+ node = entry({"admin", "uci", "apply_unchecked"}, post("action_apply_unchecked"), nil)
+ node.cors = true
+ node.sysauth_authenticator = authen
+
+ node = entry({"admin", "uci", "confirm"}, call("action_confirm"), nil)
+ node.cors = true
+ node.sysauth = false
+end
+
+
+function action_changes()
+ local uci = require "luci.model.uci"
+ local changes = uci:changes()
+
+ luci.template.render("admin_uci/changes", {
+ changes = next(changes) and changes,
+ timeout = timeout,
+ trigger_apply = luci.http.formvalue("trigger_apply") and true or false
+ })
+end
+
+function action_revert()
+ local uci = require "luci.model.uci"
+ local changes = uci:changes()
+
+ -- Collect files to be reverted
+ local r, tbl
+ for r, tbl in pairs(changes) do
+ uci:revert(r)
+ end
+
+ luci.template.render("admin_uci/revert", {
+ changes = next(changes) and changes,
+ trigger_revert = true
+ })
+end
+
+
+local function ubus_state_to_http(errstr)
+ local map = {
+ ["Invalid command"] = 400,
+ ["Invalid argument"] = 400,
+ ["Method not found"] = 404,
+ ["Entry not found"] = 404,
+ ["No data"] = 204,
+ ["Permission denied"] = 403,
+ ["Timeout"] = 504,
+ ["Not supported"] = 500,
+ ["Unknown error"] = 500,
+ ["Connection failed"] = 503
+ }
+
+ local code = map[errstr] or 200
+ local msg = errstr or "OK"
+
+ luci.http.status(code, msg)
+
+ if code ~= 204 then
+ luci.http.prepare_content("text/plain")
+ luci.http.write(msg)
+ end
+end
+
+function action_apply_rollback()
+ local uci = require "luci.model.uci"
+ local token, errstr = uci:apply(true)
+ if token then
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({ token = token })
+ else
+ ubus_state_to_http(errstr)
+ end
+end
+
+function action_apply_unchecked()
+ local uci = require "luci.model.uci"
+ local _, errstr = uci:apply(false)
+ ubus_state_to_http(errstr)
+end
+
+function action_confirm()
+ local uci = require "luci.model.uci"
+ local token = luci.http.formvalue("token")
+ local _, errstr = uci:confirm(token)
+ ubus_state_to_http(errstr)
+end
diff --git a/modules/luci-base/luasrc/dispatcher.lua b/modules/luci-base/luasrc/dispatcher.lua
index 6cf2712eb4..8b9003d207 100644
--- a/modules/luci-base/luasrc/dispatcher.lua
+++ b/modules/luci-base/luasrc/dispatcher.lua
@@ -724,32 +724,82 @@ end
-- Subdispatchers --
-function _firstchild()
- local path = { unpack(context.path) }
- local name = table.concat(path, ".")
- local node = context.treecache[name]
-
- local lowest
- if node and node.nodes and next(node.nodes) then
- local k, v
- for k, v in pairs(node.nodes) do
- if not lowest or
- (v.order or 100) < (node.nodes[lowest].order or 100)
- then
- lowest = k
- end
- end
- end
+function _find_eligible_node(root, prefix, deep, types, descend)
+ local _, cur_name, cur_node
+ local childs = { }
+
+ for cur_name, cur_node in pairs(root.nodes) do
+ childs[#childs+1] = {
+ node = cur_node,
+ name = cur_name,
+ order = cur_node.order or 100
+ }
+ end
+
+ table.sort(childs, function(a, b)
+ if a.order == b.order then
+ return a.name < b.name
+ else
+ return a.order < b.order
+ end
+ end)
+
+ if not root.leaf and deep ~= nil then
+ local sub_path = { unpack(prefix) }
+
+ if deep == false then
+ deep = nil
+ end
+
+ for _, cur_node in ipairs(childs) do
+ sub_path[#prefix+1] = cur_node.name
+
+ local res_path = _find_eligible_node(cur_node.node, sub_path,
+ deep, types, true)
+
+ if res_path then
+ return res_path
+ end
+ end
+ end
- assert(lowest ~= nil,
- "The requested node contains no childs, unable to redispatch")
+ if descend and
+ (not types or
+ (type(root.target) == "table" and
+ util.contains(types, root.target.type)))
+ then
+ return prefix
+ end
+end
+
+function _find_node(recurse, types)
+ local path = { unpack(context.path) }
+ local name = table.concat(path, ".")
+ local node = context.treecache[name]
+
+ path = _find_eligible_node(node, path, recurse, types)
- path[#path+1] = lowest
- dispatch(path)
+ if path then
+ dispatch(path)
+ else
+ require "luci.template".render("empty_node_placeholder")
+ end
+end
+
+function _firstchild()
+ return _find_node(false, nil)
end
function firstchild()
- return { type = "firstchild", target = _firstchild }
+ return { type = "firstchild", target = _firstchild }
+end
+
+function _firstnode()
+ return _find_node(true, { "cbi", "form", "template", "arcombine" })
+end
+
+function firstnode()
+ return { type = "firstnode", target = _firstnode }
end
function alias(...)
diff --git a/modules/luci-base/luasrc/model/network.lua b/modules/luci-base/luasrc/model/network.lua
index cce559aab1..7f7397032f 100644
--- a/modules/luci-base/luasrc/model/network.lua
+++ b/modules/luci-base/luasrc/model/network.lua
@@ -813,6 +813,7 @@ function del_wifinet(self, net)
end
function get_status_by_route(self, addr, mask)
+ local route_statuses = { }
local _, object
for _, object in ipairs(utl.ubus()) do
local net = object:match("^network%.interface%.(.+)")
@@ -822,12 +823,14 @@ function get_status_by_route(self, addr, mask)
local rt
for _, rt in ipairs(s.route) do
if not rt.table and rt.target == addr and rt.mask == mask then
- return net, s
+ route_statuses[net] = s
end
end
end
end
end
+
+ return route_statuses
end
function get_status_by_address(self, addr)
@@ -856,24 +859,28 @@ function get_status_by_address(self, addr)
end
end
-function get_wannet(self)
- local net, stat = self:get_status_by_route("0.0.0.0", 0)
- return net and network(net, stat.proto)
-end
+function get_wan_networks(self)
+ local k, v
+ local wan_nets = { }
+ local route_statuses = self:get_status_by_route("0.0.0.0", 0)
-function get_wandev(self)
- local _, stat = self:get_status_by_route("0.0.0.0", 0)
- return stat and interface(stat.l3_device or stat.device)
-end
+ for k, v in pairs(route_statuses) do
+ wan_nets[#wan_nets+1] = network(k, v.proto)
+ end
-function get_wan6net(self)
- local net, stat = self:get_status_by_route("::", 0)
- return net and network(net, stat.proto)
+ return wan_nets
end
-function get_wan6dev(self)
- local _, stat = self:get_status_by_route("::", 0)
- return stat and interface(stat.l3_device or stat.device)
+function get_wan6_networks(self)
+ local k, v
+ local wan6_nets = { }
+ local route_statuses = self:get_status_by_route("::", 0)
+
+ for k, v in pairs(route_statuses) do
+ wan6_nets[#wan6_nets+1] = network(k, v.proto)
+ end
+
+ return wan6_nets
end
function get_switch_topologies(self)
diff --git a/modules/luci-base/luasrc/view/admin_uci/changelog.htm b/modules/luci-base/luasrc/view/admin_uci/changelog.htm
new file mode 100644
index 0000000000..8a162c88b6
--- /dev/null
+++ b/modules/luci-base/luasrc/view/admin_uci/changelog.htm
@@ -0,0 +1,66 @@
+<%#
+ Copyright 2010 Jo-Philipp Wich <jo@mein.io>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<% export("uci_changelog", function(changes) -%>
+<div class="cbi-section">
+ <strong><%:Legend:%></strong>
+ <div class="uci-change-legend">
+ <div class="uci-change-legend-label"><ins>&#160;</ins> <%:Section added%></div>
+ <div class="uci-change-legend-label"><del>&#160;</del> <%:Section removed%></div>
+ <div class="uci-change-legend-label"><var><ins>&#160;</ins></var> <%:Option changed%></div>
+ <div class="uci-change-legend-label"><var><del>&#160;</del></var> <%:Option removed%></div>
+ <br style="clear:both" />
+ </div>
+ <br />
+
+ <div class="uci-change-list"><%
+ local util = luci.util
+ local tpl = {
+ ["add-3"] = "<ins>uci add %0 <strong>%3</strong> # =%2</ins>",
+ ["set-3"] = "<ins>uci set %0.<strong>%2</strong></ins>=%3",
+ ["set-4"] = "<var><ins>uci set %0.%2.%3=<strong>%4</strong></ins></var>",
+ ["remove-2"] = "<del>uci del %0.<strong>%2</strong></del>",
+ ["remove-3"] = "<var><del>uci del %0.%2.<strong>%3</strong></del></var>",
+ ["order-3"] = "<var>uci reorder %0.%2=<strong>%3</strong></var>",
+ ["list-add-4"] = "<var><ins>uci add_list %0.%2.%3=<strong>%4</strong></ins></var>",
+ ["list-del-4"] = "<var><del>uci del_list %0.%2.%3=<strong>%4</strong></del></var>",
+ ["rename-3"] = "<var>uci rename %0.%2=<strong>%3</strong></var>",
+ ["rename-4"] = "<var>uci rename %0.%2.%3=<strong>%4</strong></var>"
+ }
+
+ local conf, deltas
+ for conf, deltas in util.kspairs(changes) do
+ write("<h3># /etc/config/%s</h3>" % conf)
+
+ local _, delta, added
+ for _, delta in pairs(deltas) do
+ local t = tpl["%s-%d" %{ delta[1], #delta }]
+
+ write(t:gsub("%%(%d)", function(n)
+ if n == "0" then
+ return conf
+ elseif n == "2" then
+ if added and delta[2] == added[1] then
+ return "@%s[-1]" % added[2]
+ else
+ return delta[2]
+ end
+ elseif n == "4" then
+ return util.shellquote(delta[4])
+ else
+ return delta[tonumber(n)]
+ end
+ end))
+
+ if delta[1] == "add" then
+ added = { delta[2], delta[3] }
+ end
+ end
+
+ write("<br />")
+ end
+ %></div>
+</div>
+<%- end) %>
diff --git a/modules/luci-base/luasrc/view/admin_uci/changes.htm b/modules/luci-base/luasrc/view/admin_uci/changes.htm
new file mode 100644
index 0000000000..43bd7c23fb
--- /dev/null
+++ b/modules/luci-base/luasrc/view/admin_uci/changes.htm
@@ -0,0 +1,45 @@
+<%#
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008-2018 Jo-Philipp Wich <jo@mein.io>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%+header%>
+
+<%-
+ local node, redir_url = luci.dispatcher.lookup(luci.http.formvalue("redir"))
+ export("redirect", redir_url or url("admin/uci/changes"))
+
+ include("admin_uci/changelog")
+-%>
+
+<h2 name="content"><%:Configuration%> / <%:Changes%></h2>
+
+<% if changes then %>
+ <%- uci_changelog(changes) -%>
+<% else %>
+ <p><strong><%:There are no pending changes!%></strong></p>
+<% end %>
+
+<div class="alert-message" id="cbi_apply_status" style="display:none"></div>
+
+<div class="cbi-page-actions">
+ <% if redir_url then %>
+ <form method="get" action="<%=luci.util.pcdata(redir_url)%>">
+ <input class="cbi-button cbi-button-link" type="submit" value="<%:Back%>" />
+ </form>
+ <% end %>
+
+ <form method="post" action="<%=url("admin/uci/changes")%>">
+ <input type="hidden" name="token" value="<%=token%>" />
+ <input type="hidden" name="redir" value="<%=pcdata(luci.http.formvalue("redir"))%>" />
+ <input class="cbi-button cbi-button-save" type="submit" name="trigger_apply" value="<%:Save & Apply%>" />
+ </form>
+ <form method="post" action="<%=url("admin/uci/revert")%>">
+ <input type="hidden" name="token" value="<%=token%>" />
+ <input type="hidden" name="redir" value="<%=pcdata(luci.http.formvalue("redir"))%>" />
+ <input class="cbi-button cbi-button-reset" type="submit" value="<%:Revert%>" />
+ </form>
+</div>
+
+<%+footer%>
diff --git a/modules/luci-base/luasrc/view/admin_uci/revert.htm b/modules/luci-base/luasrc/view/admin_uci/revert.htm
new file mode 100644
index 0000000000..d8fd3de01e
--- /dev/null
+++ b/modules/luci-base/luasrc/view/admin_uci/revert.htm
@@ -0,0 +1,33 @@
+<%#
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008-2018 Jo-Philipp Wich <jo@mein.io>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%+header%>
+
+<%-
+ local node, redir_url = luci.dispatcher.lookup(luci.http.formvalue("redir"))
+ export("redirect", redir_url or url("admin/uci/changes"))
+
+ include("admin_uci/changelog")
+-%>
+
+<h2 name="content"><%:Configuration%> / <%:Revert%></h2>
+
+<% if changes then %>
+ <p><strong><%:The following changes have been reverted%>:</strong></p>
+ <%- uci_changelog(changes) -%>
+<% else %>
+ <p><strong><%:There are no pending changes to revert!%></strong></p>
+<% end %>
+
+<% if redir_url then %>
+ <div class="cbi-page-actions">
+ <form class="inline" method="get" action="<%=luci.util.pcdata(redir_url)%>">
+ <input class="cbi-button cbi-button-link" style="margin:0" type="submit" value="<%:Back%>" />
+ </form>
+ </div>
+<% end %>
+
+<%+footer%>
diff --git a/modules/luci-base/luasrc/view/cbi/wireless_modefreq.htm b/modules/luci-base/luasrc/view/cbi/wireless_modefreq.htm
new file mode 100644
index 0000000000..ebb02e489b
--- /dev/null
+++ b/modules/luci-base/luasrc/view/cbi/wireless_modefreq.htm
@@ -0,0 +1,168 @@
+<%+cbi/valueheader%>
+
+<script type="text/javascript">//<![CDATA[
+ var freqlist = <%= luci.http.write_json(self.iwinfo.freqlist) %>;
+ var hwmodes = <%= luci.http.write_json(self.iwinfo.hwmodelist or {}) %>;
+ var htmodes = <%= luci.http.write_json(self.iwinfo.htmodelist) %>;
+
+ var channels = {
+ '11g': [
+ 'auto', 'auto', true
+ ],
+ '11a': [
+ 'auto', 'auto', true
+ ]
+ };
+
+ for (var i = 0; i < freqlist.length; i++)
+ channels[(freqlist[i].mhz > 2484) ? '11a' : '11g'].push(
+ freqlist[i].channel,
+ '%d (%d MHz)'.format(freqlist[i].channel, freqlist[i].mhz),
+ !freqlist[i].restricted
+ );
+
+ var modes = [
+ '', 'Legacy', true,
+ 'n', 'N', hwmodes.n,
+ 'ac', 'AC', hwmodes.ac
+ ];
+
+ var htmodes = {
+ '': [
+ '', '-', true
+ ],
+ 'n': [
+ 'HT20', '20 MHz', htmodes.HT20,
+ 'HT40', '40 MHz', htmodes.HT40
+ ],
+ 'ac': [
+ 'VHT20', '20 MHz', htmodes.VHT20,
+ 'VHT40', '40 MHz', htmodes.VHT40,
+ 'VHT80', '80 MHz', htmodes.VHT80,
+ 'VHT160', '160 MHz', htmodes.VHT160
+ ]
+ };
+
+ var bands = {
+ '': [
+ '11g', '2.4 GHz', (channels['11g'].length > 3),
+ '11a', '5 GHz', (channels['11a'].length > 3)
+ ],
+ 'n': [
+ '11g', '2.4 GHz', (channels['11g'].length > 3),
+ '11a', '5 GHz', (channels['11a'].length > 3)
+ ],
+ 'ac': [
+ '11a', '5 GHz', true
+ ]
+ };
+
+ function cbi_set_values(sel, vals)
+ {
+ if (sel.vals)
+ sel.vals.selected = sel.selectedIndex;
+
+ while (sel.options[0])
+ sel.remove(0);
+
+ for (var i = 0; vals && i < vals.length; i += 3)
+ {
+ if (!vals[i+2])
+ continue;
+
+ var opt = document.createElement('option');
+ opt.value = vals[i+0];
+ opt.text = vals[i+1];
+
+ sel.add(opt);
+ }
+
+ if (!isNaN(vals.selected))
+ sel.selectedIndex = vals.selected;
+
+ sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
+ sel.vals = vals;
+ }
+
+ function cbi_toggle_wifi_mode(id)
+ {
+ cbi_toggle_wifi_htmode(id);
+ cbi_toggle_wifi_band(id);
+ }
+
+ function cbi_toggle_wifi_htmode(id)
+ {
+ var mode = document.getElementById(id + '.mode');
+ var bwdt = document.getElementById(id + '.htmode');
+
+ cbi_set_values(bwdt, htmodes[mode.value]);
+ }
+
+ function cbi_toggle_wifi_band(id)
+ {
+ var mode = document.getElementById(id + '.mode');
+ var band = document.getElementById(id + '.band');
+
+ cbi_set_values(band, bands[mode.value]);
+ cbi_toggle_wifi_channel(id);
+ }
+
+ function cbi_toggle_wifi_channel(id)
+ {
+ var band = document.getElementById(id + '.band');
+ var chan = document.getElementById(id + '.channel');
+
+ cbi_set_values(chan, channels[band.value]);
+ }
+
+ function cbi_init_wifi(id)
+ {
+ var mode = document.getElementById(id + '.mode');
+ var band = document.getElementById(id + '.band');
+ var chan = document.getElementById(id + '.channel');
+ var bwdt = document.getElementById(id + '.htmode');
+
+ cbi_set_values(mode, modes);
+
+ if (/VHT20|VHT40|VHT80|VHT160/.test(<%= luci.http.write_json(self.map:get(section, "htmode")) %>))
+ mode.value = 'ac';
+ else if (/HT20|HT40/.test(<%= luci.http.write_json(self.map:get(section, "htmode")) %>))
+ mode.value = 'n';
+ else
+ mode.value = '';
+
+ cbi_toggle_wifi_mode(id);
+
+ if (/a/.test(<%= luci.http.write_json(self.map:get(section, "hwmode")) %>))
+ band.value = '11a';
+ else
+ band.value = '11g';
+
+ cbi_toggle_wifi_band(id);
+
+ bwdt.value = <%= luci.http.write_json(self.map:get(section, "htmode")) %>;
+ chan.value = <%= luci.http.write_json(self.map:get(section, "channel")) %>;
+ }
+//]]></script>
+
+<label style="float:left; margin-right:3px">
+ <%:Mode%><br />
+ <select style="width:auto" id="<%= cbid %>.mode" name="<%= cbid %>.mode" onchange="cbi_toggle_wifi_mode('<%= cbid %>')"></select>
+</label>
+<label style="float:left; margin-right:3px">
+ <%:Band%><br />
+ <select style="width:auto" id="<%= cbid %>.band" name="<%= cbid %>.band" onchange="cbi_toggle_wifi_band('<%= cbid %>')"></select>
+</label>
+<label style="float:left; margin-right:3px">
+ <%:Channel%><br />
+ <select style="width:auto" id="<%= cbid %>.channel" name="<%= cbid %>.channel"></select>
+</label>
+<label style="float:left; margin-right:3px">
+ <%:Width%><br />
+ <select style="width:auto" id="<%= cbid %>.htmode" name="<%= cbid %>.htmode"></select>
+</label>
+<br style="clear:left" />
+
+<script type="text/javascript">cbi_init_wifi('<%= cbid %>');</script>
+
+<%+cbi/valuefooter%>
diff --git a/modules/luci-base/luasrc/view/empty_node_placeholder.htm b/modules/luci-base/luasrc/view/empty_node_placeholder.htm
new file mode 100644
index 0000000000..b7e276b960
--- /dev/null
+++ b/modules/luci-base/luasrc/view/empty_node_placeholder.htm
@@ -0,0 +1,11 @@
+<%#
+ Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
+ Copyright 2018 Daniel F. Dickinson <cshored@thecshore.com>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%+header%>
+
+<p>Component not present.</p>
+
+<%+footer%>
diff --git a/modules/luci-base/luasrc/view/lease_status.htm b/modules/luci-base/luasrc/view/lease_status.htm
new file mode 100644
index 0000000000..15b6b6908e
--- /dev/null
+++ b/modules/luci-base/luasrc/view/lease_status.htm
@@ -0,0 +1,95 @@
+<script type="text/javascript">//<![CDATA[
+ XHR.poll(5, '<%=url('admin/dhcplease_status')%>', null,
+ function(x, st)
+ {
+ var tb = document.getElementById('lease_status_table');
+ if (st && st[0] && tb)
+ {
+ var rows = [];
+
+ for (var i = 0; i < st[0].length; i++)
+ {
+ var timestr;
+
+ if (st[0][i].expires === false)
+ timestr = '<em><%:unlimited%></em>';
+ else if (st[0][i].expires <= 0)
+ timestr = '<em><%:expired%></em>';
+ else
+ timestr = String.format('%t', st[0][i].expires);
+
+ rows.push([
+ st[0][i].hostname || '?',
+ st[0][i].ipaddr,
+ st[0][i].macaddr,
+ timestr
+ ]);
+ }
+
+ cbi_update_table(tb, rows, '<em><%:There are no active leases.%></em>');
+ }
+
+ var tb6 = document.getElementById('lease6_status_table');
+ if (st && st[1] && tb6)
+ {
+ tb6.parentNode.style.display = 'block';
+
+ var rows = [];
+
+ for (var i = 0; i < st[1].length; i++)
+ {
+ var timestr;
+
+ if (st[1][i].expires === false)
+ timestr = '<em><%:unlimited%></em>';
+ else if (st[1][i].expires <= 0)
+ timestr = '<em><%:expired%></em>';
+ else
+ timestr = String.format('%t', st[1][i].expires);
+
+ var name = st[1][i].hostname,
+ hint = st[1][i].host_hint;
+
+ rows.push([
+ hint ? '%h (%h)'.format(name || '?', hint) : (name || '?'),
+ st[1][i].ip6addr,
+ st[1][i].duid,
+ timestr
+ ]);
+ }
+
+ cbi_update_table(tb6, rows, '<em><%:There are no active leases.%></em>');
+ }
+ }
+ );
+//]]></script>
+
+<div class="cbi-section">
+ <h3><%:Active DHCP Leases%></h3>
+ <div class="table" id="lease_status_table">
+ <div class="tr table-titles">
+ <div class="th"><%:Hostname%></div>
+ <div class="th"><%:IPv4-Address%></div>
+ <div class="th"><%:MAC-Address%></div>
+ <div class="th"><%:Leasetime remaining%></div>
+ </div>
+ <div class="tr placeholder">
+ <div class="td"><em><%:Collecting data...%></em></div>
+ </div>
+ </div>
+</div>
+
+<div class="cbi-section" style="display:none">
+ <h3><%:Active DHCPv6 Leases%></h3>
+ <div class="table" id="lease6_status_table">
+ <div class="tr table-titles">
+ <div class="th"><%:Host%></div>
+ <div class="th"><%:IPv6-Address%></div>
+ <div class="th"><%:DUID%></div>
+ <div class="th"><%:Leasetime remaining%></div>
+ </div>
+ <div class="tr placeholder">
+ <div class="td"><em><%:Collecting data...%></em></div>
+ </div>
+ </div>
+</div>
diff --git a/modules/luci-base/luasrc/view/wifi_assoclist.htm b/modules/luci-base/luasrc/view/wifi_assoclist.htm
new file mode 100644
index 0000000000..700d998ad8
--- /dev/null
+++ b/modules/luci-base/luasrc/view/wifi_assoclist.htm
@@ -0,0 +1,82 @@
+<script type="text/javascript">//<![CDATA[
+ function wifirate(bss, rx) {
+ var p = rx ? 'rx_' : 'tx_',
+ s = '%.1f <%:Mbit/s%>, %d<%:MHz%>'
+ .format(bss[p+'rate'] / 1000, bss[p+'mhz']),
+ ht = bss[p+'ht'], vht = bss[p+'vht'],
+ mhz = bss[p+'mhz'], nss = bss[p+'nss'],
+ mcs = bss[p+'mcs'], sgi = bss[p+'short_gi'];
+
+ if (ht || vht) {
+ if (vht) s += ', VHT-MCS %d'.format(mcs);
+ if (nss) s += ', VHT-NSS %d'.format(nss);
+ if (ht) s += ', MCS %s'.format(mcs);
+ if (sgi) s += ', <%:Short GI%>';
+ }
+
+ return s;
+ }
+
+ XHR.poll(5, '<%=url('admin/wireless_assoclist')%>', null,
+ function(x, st)
+ {
+ var tb = document.getElementById('wifi_assoclist_table');
+ if (st && tb)
+ {
+ var rows = [];
+
+ st.forEach(function(bss) {
+ var icon;
+ var q = (-1 * (bss.noise - bss.signal)) / 5;
+ if (q < 1)
+ icon = "<%=resource%>/icons/signal-0.png";
+ else if (q < 2)
+ icon = "<%=resource%>/icons/signal-0-25.png";
+ else if (q < 3)
+ icon = "<%=resource%>/icons/signal-25-50.png";
+ else if (q < 4)
+ icon = "<%=resource%>/icons/signal-50-75.png";
+ else
+ icon = "<%=resource%>/icons/signal-75-100.png";
+
+ rows.push([
+ '<span class="ifacebadge" title="%q"><img src="<%=resource%>/icons/wifi.png" /> <a href="%s">%h</a><small>&#160;(%h)</small></span>'.format(
+ bss.radio,
+ bss.link,
+ bss.name,
+ bss.ifname),
+ bss.bssid,
+ bss.host_hint ? '%h (%h)'.format(bss.host_name || '?', bss.host_hint) : (bss.host_name || '?'),
+ '<span class="ifacebadge" title="<%:Signal%>: %d <%:dBm%> / <%:Noise%>: %d <%:dBm%> / <%:SNR%>: %d"><img src="%s" /> %d / %d <%:dBm%></span>'.format(
+ bss.signal,
+ bss.noise,
+ bss.signal - bss.noise,
+ icon,
+ bss.signal,
+ bss.noise),
+ E('span', {}, [
+ E('span', wifirate(bss, true)),
+ E('br'),
+ E('span', wifirate(bss, false))
+ ])
+ ]);
+ });
+
+ cbi_update_table(tb, rows, '<em><%:No information available%></em>');
+ }
+ }
+ );
+//]]></script>
+
+<div class="table" id="wifi_assoclist_table">
+ <div class="tr table-titles">
+ <div class="th nowrap"><%:Network%></div>
+ <div class="th hide-xs"><%:MAC-Address%></div>
+ <div class="th nowrap"><%:Host%></div>
+ <div class="th nowrap"><%:Signal%> / <%:Noise%></div>
+ <div class="th nowrap"><%:RX Rate%> / <%:TX Rate%></div>
+ </div>
+ <div class="tr placeholder">
+ <div class="td"><em><%:Collecting data...%></em></div>
+ </div>
+</div>