modules/admin-full: initial work on site survey and network join
authorJo-Philipp Wich <jow@openwrt.org>
Sat, 26 Sep 2009 11:10:01 +0000 (11:10 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Sat, 26 Sep 2009 11:10:01 +0000 (11:10 +0000)
modules/admin-full/luasrc/controller/admin/network.lua
modules/admin-full/luasrc/view/admin_network/wifi_join.htm [new file with mode: 0644]
modules/admin-full/luasrc/view/admin_network/wifi_join_settings.htm [new file with mode: 0644]
modules/admin-full/luasrc/view/admin_network/wifi_overview.htm [new file with mode: 0644]

index 08a5dd0..12dd559 100644 (file)
@@ -29,7 +29,7 @@ function index()
        page.title  = i18n("a_n_switch")
        page.order  = 20
 
        page.title  = i18n("a_n_switch")
        page.order  = 20
 
-       local page = entry({"admin", "network", "wireless"}, arcombine(cbi("admin_network/wireless"), cbi("admin_network/wifi")), i18n("wifi"), 15)
+       local page = entry({"admin", "network", "wireless"}, arcombine(template("admin_network/wifi_overview"), cbi("admin_network/wifi")), i18n("wifi"), 15)
        page.i18n   = "wifi"
        page.leaf = true
        page.subindex = true
        page.i18n   = "wifi"
        page.leaf = true
        page.subindex = true
@@ -43,6 +43,10 @@ function index()
                end
        )
 
                end
        )
 
+       local page = entry({"admin", "network", "wireless_join"}, call("wifi_join"), nil, 16)
+       page.i18n = "wifi"
+       page.leaf = true
+
        local page = entry({"admin", "network", "network"}, arcombine(cbi("admin_network/network"), cbi("admin_network/ifaces")), i18n("interfaces", "Schnittstellen"), 10)
        page.leaf   = true
        page.subindex = true
        local page = entry({"admin", "network", "network"}, arcombine(cbi("admin_network/network"), cbi("admin_network/ifaces")), i18n("interfaces", "Schnittstellen"), 10)
        page.leaf   = true
        page.subindex = true
@@ -81,3 +85,89 @@ function index()
        page.order  = 50
 
 end
        page.order  = 50
 
 end
+
+function wifi_join()
+       local function param(x)
+               return luci.http.formvalue(x)
+       end
+
+       local function ptable(x)
+               x = param(x)
+               return x and (type(x) ~= "table" and { x } or x) or {}
+       end
+
+       local dev  = param("device")
+       local ssid = param("join")
+
+       if dev and ssid then
+               local wep     = (tonumber(param("wep")) == 1)
+               local wpa     = tonumber(param("wpa_version")) or 0
+               local channel = tonumber(param("channel"))
+               local mode    = param("mode")
+               local bssid   = param("bssid")
+
+               local confirm = (param("confirm") == "1")
+               local cancel  = param("cancel") and true or false
+
+               if confirm and not cancel then
+                       local fixed_bssid = (param("fixed_bssid") == "1")
+                       local replace_net = (param("replace_net") == "1")
+                       local autoconnect = (param("autoconnect") == "1")
+                       local attach_intf = param("attach_intf")
+
+                       local uci = require "luci.model.uci".cursor()
+
+                       if replace_net then
+                               uci:delete_all("wireless", "wifi-iface")
+                       end
+
+                       local wificonf = {
+                               device  = dev,
+                               mode    = (mode == "Ad-Hoc" and "adhoc" or "sta"),
+                               ssid    = ssid
+                       }
+
+                       if attach_intf and uci:get("network", attach_intf, "ifname") then
+                               -- target network already has a interface, make it a bridge
+                               uci:set("network", attach_intf, "type", "bridge")
+                               uci:save("network")
+                               uci:commit("network")
+
+                               if autoconnect then
+                                       require "luci.sys".call("/sbin/ifup " .. attach_intf)
+                               end
+                       end
+
+                       if fixed_bssid then
+                               wificonf.bssid = bssid
+                       end
+
+                       if wep then
+                               wificonf.encryption = "wep"
+                               wificonf.key = param("key")
+                       elseif wpa > 0 then
+                               wificonf.encryption = param("wpa_suite")
+                               wificonf.key = param("key")
+                       end
+
+                       uci:section("wireless", "wifi-iface", nil, wificonf)
+                       uci:delete("wireless", dev, "disabled")
+                       uci:set("wireless", dev, "channel", channel)
+
+                       uci:save("wireless")
+                       uci:commit("wireless")
+
+                       if autoconnect then
+                               require "luci.sys".call("/sbin/wifi")
+                       end
+
+                       luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless", dev))
+               elseif cancel then
+                       luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless_join?device=" .. dev))
+               else
+                       luci.template.render("admin_network/wifi_join_settings")
+               end
+       else
+               luci.template.render("admin_network/wifi_join")
+       end
+end
diff --git a/modules/admin-full/luasrc/view/admin_network/wifi_join.htm b/modules/admin-full/luasrc/view/admin_network/wifi_join.htm
new file mode 100644 (file)
index 0000000..3963a5a
--- /dev/null
@@ -0,0 +1,129 @@
+<%#
+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 sys = require "luci.sys"
+       local utl = require "luci.util"
+
+       function guess_wifi_signal(info)
+               local scale = (100 / (info.quality_max or 100) * (info.quality or 0))
+               local icon
+
+               if not info.bssid or info.bssid == "00:00:00:00:00:00" then
+                       icon = resource .. "/icons/signal-none.png"
+               elseif scale < 15 then
+                       icon = resource .. "/icons/signal-0.png"
+               elseif scale < 35 then
+                       icon = resource .. "/icons/signal-0-25.png"
+               elseif scale < 55 then
+                       icon = resource .. "/icons/signal-25-50.png"
+               elseif scale < 75 then
+                       icon = resource .. "/icons/signal-50-75.png"
+               else
+                       icon = resource .. "/icons/signal-75-100.png"
+               end
+
+               return icon
+       end
+
+       function percent_wifi_signal(info)
+               local qc = info.quality or 0
+               local qm = info.quality_max or 0
+
+               if info.bssid and qc > 0 and qm > 0 then
+                       return math.floor((100 / qm) * qc)
+               else
+                       return 0
+               end
+       end
+
+       function format_wifi_encryption(info)
+               if info.wep == true and not info.wpa_version then
+                       return "WEP"
+               elseif info.wpa_version then
+                       return "<abbr title='Pairwise: %s / Group: %s'>%s - %s</abbr>" % {
+                               table.concat(info.pair_ciphers, ", "),
+                               table.concat(info.group_ciphers, ", "),
+                               (info.wpa_version == 3) and "mixed WPA/WPA2"
+                                       or (info.wpa_version == 2 and "WPA2" or "WPA"),
+                               table.concat(info.auth_suites, ", ")
+                       }
+               else
+                       return "<em>None</em>"
+               end
+       end
+
+       local dev = luci.http.formvalue("device")
+       local iw = luci.sys.wifi.getiwinfo(dev)
+-%>
+
+<%+header%>
+
+<h2><a id="content" name="content"><%:a_s_iw_scan Wireless Scan%></a></h2>
+
+<div class="cbi-map">
+       <fieldset class="cbi-section">
+               <table class="cbi-section-table" style="empty-cells:hide">
+                       <!-- scan list -->
+                       <% for i, net in ipairs(iw.scanlist) do %>
+                       <tr class="cbi-section-table-row cbi-rowstyle-<%=1 + ((i-1) % 2)%>">
+                               <td class="cbi-value-field" style="width:16px; padding:3px">
+                                       <abbr title="Signal: <%=net.signal%> dB / Quality: <%=net.quality%>/<%=net.quality_max%>">
+                                               <img src="<%=guess_wifi_signal(net)%>" /><br />
+                                               <small><%=percent_wifi_signal(net)%>%</small>
+                                       </abbr>
+                               </td>
+                               <td class="cbi-value-field" style="vertical-align:middle; text-align:left; padding:3px">
+                                       <big><strong><%=net.ssid and utl.pcdata(net.ssid) or "<em>hidden</em>"%></strong></big><br />
+                                       <strong>Channel:</strong> <%=net.channel%> |
+                                       <strong>Mode:</strong> <%=net.mode%> |
+                                       <strong>BSSID:</strong> <%=net.bssid%> |
+                                       <strong>Encryption:</strong> <%=format_wifi_encryption(net)%>
+                               </td>
+                               <td class="cbi-value-field" style="width:40px">
+                                       <form action="<%=REQUEST_URI%>" method="post">
+                                               <input type="hidden" name="device" value="<%=utl.pcdata(dev)%>" />
+                                               <input type="hidden" name="join" value="<%=utl.pcdata(net.ssid)%>" />
+                                               <input type="hidden" name="mode" value="<%=net.mode%>" />
+                                               <input type="hidden" name="bssid" value="<%=net.bssid%>" />
+                                               <input type="hidden" name="channel" value="<%=net.channel%>" />
+                                               <input type="hidden" name="wep" value="<%=net.wep and 1 or 0%>" />
+                                               <% if net.wpa_version then %>
+                                               <input type="hidden" name="wpa_version" value="<%=net.wpa_version%>" />
+                                               <% for _, v in ipairs(net.auth_suites) do %><input type="hidden" name="wpa_suites" value="<%=v%>" />
+                                               <% end; for _, v in ipairs(net.group_ciphers) do %><input type="hidden" name="wpa_group" value="<%=v%>" />
+                                               <% end; for _, v in ipairs(net.pair_ciphers) do %><input type="hidden" name="wpa_pairwise" value="<%=v%>" />
+                                               <% end; end %>
+
+                                               <input class="cbi-button-apply" type="submit" value="Join Network" />
+                                       </form>
+                               </td>
+                       </tr>
+                       <% end %>
+                       <!-- /scan list -->
+               </table>
+       </fieldset>
+</div>
+<div class="cbi-page-actions right">
+       <form class="inline" action="<%=luci.dispatcher.build_url("admin/network/wireless")%>" method="get">
+               <input class="cbi-button-reset" type="submit" value="<%:a_s_iw_back_overview Back to overview%>" />
+       </form>
+       <form class="inline" action="<%=REQUEST_URI%>" method="get">
+               <input type="hidden" name="device" value="<%=utl.pcdata(dev)%>" />
+               <input class="cbi-input-find" type="submit" value="<%:a_s_iw_scan_repeat Repeat scan%>" />
+       </form>
+</div>
+
+<%+footer%>
diff --git a/modules/admin-full/luasrc/view/admin_network/wifi_join_settings.htm b/modules/admin-full/luasrc/view/admin_network/wifi_join_settings.htm
new file mode 100644 (file)
index 0000000..c914f3e
--- /dev/null
@@ -0,0 +1,112 @@
+<%#
+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 sys = require "luci.sys"
+       local utl = require "luci.util"
+       local uci = require "luci.model.uci".cursor_state()
+
+       local ifaces = { }
+       uci:foreach("network", "interface", function(i)
+               if i.ifname ~= "lo" then
+                       ifaces[#ifaces+1] = { i['.name'], i.ifname, i.ipaddr }
+               end
+       end)
+
+       local dev = luci.http.formvalue("device")
+       local iw = luci.sys.wifi.getiwinfo(dev)
+
+-%>
+
+<%+header%>
+
+<h2><a id="content" name="content"><%:a_s_iw_join Join Network%></a></h2>
+
+<form method="post" action="<%=REQUEST_URI%>">
+       <div class="cbi-map">
+               <div class="cbi-map-descr">
+                       <%=luci.i18n.translatef("a_s_iw_join_desc", "You are about to join the wireless network <em><strong>%s</strong></em>. " ..
+                               "In order to complete the process, you need to provide some additional details.",
+                               utl.pcdata(luci.http.formvalue("join") or "(hidden)")
+                       )%>
+               </div>
+
+               <fieldset class="cbi-section">
+                       <input type="hidden" name="confirm" value="1" />
+                       <input type="hidden" name="join" value="<%=utl.pcdata(luci.http.formvalue("join"))%>" />
+                       <input type="hidden" name="device" value="<%=utl.pcdata(luci.http.formvalue("device"))%>" />
+                       <input type="hidden" name="mode" value="<%=luci.http.formvalue("mode")%>" />
+                       <input type="hidden" name="bssid" value="<%=luci.http.formvalue("bssid")%>" />
+                       <input type="hidden" name="channel" value="<%=luci.http.formvalue("channel")%>" />
+                       <input type="hidden" name="wep" value="<%=luci.http.formvalue("wep")%>" />
+                       <input type="hidden" name="wpa_version" value="<%=luci.http.formvalue("wpa_version")%>" />
+
+                       <% if luci.http.formvalue("wep") == "1" then %>
+                               <label for="pw_key">WEP passphrase</label><br />
+                               <input class="cbi-input-password" type="password" name="key" id="pw_key" />
+                               <br /><br />
+                       <% elseif tonumber(luci.http.formvalue("wpa_version") or 0) > 0 and luci.http.formvalue("wpa_suites") == "PSK" then %>
+                               <label for="pw_key">WPA passphrase</label><br />
+                               <input class="cbi-input-password" type="password" name="key" id="pw_key" />
+
+                               <% if tonumber(luci.http.formvalue("wpa_version") or 0) == 3 then %>
+                                       <select name="wpa_suite">
+                                               <option value="psk">WPA-PSK</option>
+                                               <option value="psk2" selected="selected">WPA2-PSK</option>
+                                               <option value="psk+psk2">WPA/WPA2-PSK mixed mode</option>
+                                       </select>
+                               <% else %>
+                                       <input type="hidden" name="wpa_suite" value="psk<%=tonumber(luci.http.formvalue("wpa_version") or 0) == 2 and 2%>" />
+                               <% end %>
+
+                               <br /><br />
+                       <% end %>
+
+                       <label for="sel_attach_intf">Attach wireless to</label><br />
+                       <select name="attach_intf" id="sel_attach_intf">
+                               <% for _, i in ipairs(ifaces) do %>
+                                       <option<% if i[1] == "wan" then %> selected="selected"<% end %> value="<%=i[1]%>"><%=i[1]%> (<%=i[2]%><% if i[3] then %> - <%=i[3]%><% end %>)</option>
+                               <% end %>
+                               <option value="">-- no interface --</option>
+                       </select>
+
+                       <br/><br />
+                       <hr /><br />
+
+                       <% if luci.http.formvalue("mode") == "Ad-Hoc" then %>
+                       <input type="checkbox" name="fixed_bssid" value="1" id="cb_fixed_bssid" checked="checked" />
+                       <label for="cb_fixed_bssid">Lock to BSSID <%=luci.http.formvalue("bssid")%></label>
+                       <br />
+                       <% end %>
+
+                       <% if iw.mbssid_support then %>
+                               <input type="checkbox" name="replace_net" value="1" id="cb_replace_net" checked="checked" />
+                               <label for="cb_replace_net">Overwrite existing wireless configuration</label>
+                               <br />
+                       <% else %>
+                               <input type="hidden" name="replace_net" value="1" />
+                       <% end %>
+
+                       <input type="checkbox" name="autoconnect" value="1" id="cb_autoconnect" checked="checked" />
+                       <label for="cb_autoconnect">Automatically connect</label>
+               </fieldset>
+       </div>
+       <div class="cbi-page-actions">
+               <input class="cbi-button-apply" type="submit" value="<%:a_s_iw_join_confirm Join network%>" />
+               <input class="cbi-button-reset" type="submit" name="cancel" value="<%:a_s_iw_back_scan Back to scan results%>" />
+       </div>
+</form>
+
+<%+footer%>
diff --git a/modules/admin-full/luasrc/view/admin_network/wifi_overview.htm b/modules/admin-full/luasrc/view/admin_network/wifi_overview.htm
new file mode 100644 (file)
index 0000000..251f767
--- /dev/null
@@ -0,0 +1,250 @@
+<%#
+LuCI - Lua Configuration Interface
+Copyright 2008-2009 Steven Barth <steven@midlink.org>
+Copyright 2008-2009 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 sys = require "luci.sys"
+       local utl = require "luci.util"
+       local uci = require "luci.model.uci".cursor_state()
+
+       function guess_wifi_hw(ifname)
+               local name, idx = ifname:match("^([a-z]+)(%d+)")
+               idx = tonumber(idx)
+
+               -- wl.o
+               if name == "wl" then
+                       local name = "Broadcom 802.11 Wireless Controller"
+                       local nm   = 0
+
+                       local fd = nixio.open("/proc/bus/pci/devices", "r")
+                       if fd then
+                               local ln
+                               for ln in fd:linesource() do
+                                       if ln:match("wl$") then
+                                               if nm == idx then
+                                                       local version = ln:match("^%S+%s+%S%S%S%S([0-9a-f]+)")
+                                                       name = string.format(
+                                                               "Broadcom BCM%04x 802.11 Wireless Controller",
+                                                               tonumber(version, 16)
+                                                       )
+
+                                                       break
+                                               else
+                                                       nm = nm + 1
+                                               end
+                                       end
+                               end
+                               fd:close()
+                       end
+
+                       return name
+
+               -- madwifi
+               elseif name == "ath" or name == "wifi" then
+                       return "Atheros 802.11 Wireless Controller"
+
+               -- ralink
+               elseif name == "ra" then
+                       return "RaLink 802.11 Wireless Controller"
+
+               -- prism?
+               elseif name == "eth" then
+                       return "Prism 802.11 Wireless Controller"
+
+               -- dunno yet
+               else
+                       return "Unkown 802.11 Wireless Controller"
+               end
+       end
+
+       function guess_wifi_signal(info)
+               local snr = -1 * ((info.noise or 0) - (info.signal or 0))
+               local scale = math.floor(snr / 5)
+               local icon
+
+               if not info.bssid or info.bssid == "00:00:00:00:00:00" then
+                       icon = resource .. "/icons/signal-none.png"
+               elseif scale < 1 then
+                       icon = resource .. "/icons/signal-0.png"
+               elseif scale < 2 then
+                       icon = resource .. "/icons/signal-0-25.png"
+               elseif scale < 3 then
+                       icon = resource .. "/icons/signal-25-50.png"
+               elseif scale < 4 then
+                       icon = resource .. "/icons/signal-50-75.png"
+               else
+                       icon = resource .. "/icons/signal-75-100.png"
+               end
+
+               return icon
+       end
+
+       function percent_wifi_signal(info)
+               local qc = info.quality or 0
+               local qm = info.quality_max or 0
+
+               if info.bssid and qc > 0 and qm > 0 then
+                       return math.floor((100 / qm) * qc)
+               else
+                       return 0
+               end
+       end
+
+       function find_wifi_devices()
+               local devs = { }
+               uci:foreach("wireless", "wifi-device",
+                       function(s)
+                               local dev = s['.name']
+                               local act = 0
+                               devs[dev] = { active = 0, networks = { } }
+
+                               uci:foreach("wireless", "wifi-iface",
+                                       function(s)
+                                               if s.device == dev then
+                                                       if s.up == "1" then act = act + 1 end
+                                                       devs[dev].networks[#devs[dev].networks+1] = {
+                                                               active = (s.up == "1"),
+                                                               ifname = s.ifname,
+                                                               info   = sys.wifi.getiwinfo(s.ifname or s.device)
+                                                       }
+                                               end
+                                       end)
+
+                               devs[dev].hwname = guess_wifi_hw(dev)
+                               devs[dev].active = (act > 0)
+                       end)
+
+               return devs
+       end
+
+       function find_wifi_frequency(state)
+               if state.active then
+                       return string.format("%d (%.03f GHz)",
+                               state.networks[1].info.channel,
+                               state.networks[1].info.frequency / 1000);
+               else
+                       return "n/a"
+               end
+       end
+
+
+       local devices  = find_wifi_devices()
+       local arpcache = { }
+       sys.net.arptable(function(e) arpcache[e["HW address"]] = e["IP address"] end)
+-%>
+
+<%+header%>
+
+<h2><a id="content" name="content"><%:a_s_iw_overview Wireless Overview%></a></h2>
+
+<div class="cbi-map">
+
+       <% for dev, state in utl.kspairs(devices) do %>
+       <!-- device <%=dev%> -->
+       <fieldset class="cbi-section">
+               <table class="cbi-section-table" style="margin:10px; empty-cells:hide">
+                       <!-- physical device -->
+                       <tr>
+                               <td style="width:34px"><img src="<%=resource%>/icons/wifi<%=state.active and "" or "_disabled"%>.png" style="float:left; margin-right:10px" /></td>
+                               <td colspan="2" style="text-align:left">
+                                       <big><strong><%=state.hwname%> (<%=dev%>)</strong></big><br />
+                                       <strong>Channel:</strong> <%=find_wifi_frequency(state)%> |
+                                       <strong>Bitrate:</strong> <%=state.active and (state.networks[1].info.bitrate / 1000) .. " Mb/s" or "n/a"%>
+                               </td>
+                               <td style="width:40px">
+                                       <a href="<%=luci.dispatcher.build_url("admin/network/wireless_join?device="..dev)%>"><img style="border:none" src="<%=resource%>/cbi/find.gif" alt="Find and join network" title="Find and join network" /></a>
+                                       <a href="#"><img style="border:none" src="<%=resource%>/cbi/add.gif" alt="Provide new network" title="Provide new network" /></a>
+                               </td>
+                       </tr>
+                       <!-- /physical device -->
+
+                       <!-- network list -->
+                       <% if #state.networks > 0 then %>
+                               <% for i, net in ipairs(state.networks) do %>
+                               <tr class="cbi-section-table-row cbi-rowstyle-<%=1 + ((i-1) % 2)%>">
+                                       <td></td>
+                                       <td class="cbi-value-field" style="width:16px; padding:3px">
+                                               <img src="<%=guess_wifi_signal(net.info)%>" title="Signal: <%=net.info.signal%> dBm / Noise: <%=net.info.noise%> dBm" /><br />
+                                               <small><%=percent_wifi_signal(net.info)%>%</small>
+                                       </td>
+                                       <td class="cbi-value-field" style="vertical-align:middle; text-align:left; padding:3px">
+                                               <strong>SSID:</strong> <%=utl.pcdata(net.info.ssid)%> |
+                                               <strong>Mode:</strong> <%=net.info.mode%><br />
+                                               <strong>BSSID:</strong> <%=net.info.bssid%> |
+                                               <strong>Encryption:</strong> <%=net.info.enctype%>
+                                       </td>
+                                       <td class="cbi-value-field" style="width:40px">
+                                               <a href="<%=REQUEST_URI%>/<%=dev%>"><img style="border:none" src="<%=resource%>/cbi/edit.gif" alt="Edit this network" title="Edit this network" /></a>
+                                               <a href="#"><img style="border:none" src="<%=resource%>/cbi/remove.gif" alt="Delete this network" title="Delete this network" /></a>
+                                       </td>
+                               </tr>
+                               <% end %>
+                       <% else %>
+                               <tr class="cbi-section-table-row cbi-rowstyle-2">
+                                       <td></td>
+                                       <td colspan="3" class="cbi-value-field" style="vertical-align:middle; text-align:left; padding:3px">
+                                               <em>(No network configured on this device)</em>
+                                       </td>
+                               </tr>
+                       <% end %>
+                       <!-- /network list -->
+               </table>
+       </fieldset>
+       <!-- /device <%=dev%> -->
+       <% end %>
+
+
+
+
+       <h2><a id="content" name="content"><%:a_s_iw_overview2 Associated Stations%></a></h2>
+
+       <fieldset class="cbi-section">
+               <table class="cbi-section-table" style="margin:10px; width:50%">
+                       <tr class="cbi-section-table-titles">
+                               <th class="cbi-section-table-cell"></th>
+                               <th class="cbi-section-table-cell">SSID</th>
+                               <th class="cbi-section-table-cell">MAC</th>
+                               <th class="cbi-section-table-cell">Address</th>
+                               <th class="cbi-section-table-cell">Signal</th>
+                               <th class="cbi-section-table-cell">Noise</th>
+                       </tr>
+
+                       <% local count = -1 %>
+                       <% for dev, state in utl.kspairs(devices) do %>
+                               <% for _, net in ipairs(state.networks) do %>
+                                       <% for mac, info in utl.kspairs(net.info.assoclist) do info.bssid = mac; count = count + 1 %>
+                                       <tr class="cbi-section-table-row cbi-rowstyle-<%=1 + (count % 2)%>">
+                                               <td class="cbi-value-field"><img src="<%=guess_wifi_signal(info)%>" title="Signal: <%=info.signal%> dBm / Noise: <%=info.noise%> dBm" /></td>
+                                               <td class="cbi-value-field"><%=net.info.ssid%></td>
+                                               <td class="cbi-value-field"><%=mac%></td>
+                                               <td class="cbi-value-field"><%=arpcache[mac] or "n/a"%></td>
+                                               <td class="cbi-value-field"><%=info.signal%> dBm</td>
+                                               <td class="cbi-value-field"><%=info.noise%> dBm</td>
+                                       </tr>
+                                       <% end %>
+                               <% end %>
+                       <% end %>
+                       <% if count <= 0 then %>
+                       <tr class="cbi-section-table-row cbi-rowstyle-2">
+                               <td class="cbi-value-field" colspan="6">
+                                       <em>No information available</em>
+                               </td>
+                       </tr>
+                       <% end %>
+               </table>
+       </fieldset>
+</div>
+
+<%+footer%>