treewide: always include cbi.js
[project/luci.git] / modules / luci-mod-admin-full / luasrc / view / admin_status / index.htm
index d29a89427687738fca1b2493134ac8c01ecc8f9f..03155dbb62247d26272024fb9f66c3ddd1a63f1a 100644 (file)
@@ -6,6 +6,7 @@
 
 <%
        local fs = require "nixio.fs"
+       local ipc = require "luci.ip"
        local util = require "luci.util"
        local stat = require "luci.tools.status"
        local ver = require "luci.version"
                local wan6 = ntm:get_wan6net()
 
                local conn_count = tonumber(
-                       fs.readfile("/proc/sys/net/netfilter/nf_conntrack_count")) or 0
+                       fs.readfile("/proc/sys/net/netfilter/nf_conntrack_count") or "") or 0
 
-               local conn_max = tonumber((
-                       luci.sys.exec("sysctl net.nf_conntrack_max") or
-                       luci.sys.exec("sysctl net.ipv4.netfilter.ip_conntrack_max") or
-                       ""):match("%d+")) or 4096
+               local conn_max = tonumber(luci.sys.exec(
+                       "sysctl -n -e net.nf_conntrack_max net.ipv4.netfilter.ip_conntrack_max"
+               ):match("%d+")) or 4096
 
                local rv = {
                        uptime     = sysinfo.uptime or 0,
                        swap       = swapinfo,
                        connmax    = conn_max,
                        conncount  = conn_count,
-                       leases     = stat.dhcp_leases(),
-                       leases6    = stat.dhcp6_leases(),
                        wifinets   = stat.wifi_networks()
                }
 
                if wan then
+                       local dev = wan:get_interface()
+                       local link = dev and ipc.link(dev:name())
                        rv.wan = {
                                ipaddr  = wan:ipaddr(),
                                gwaddr  = wan:gwaddr(),
                                expires = wan:expires(),
                                uptime  = wan:uptime(),
                                proto   = wan:proto(),
+                               i18n    = wan:get_i18n(),
                                ifname  = wan:ifname(),
-                               link    = wan:adminlink()
+                               link    = wan:adminlink(),
+                               mac     = dev and dev:mac(),
+                               type    = dev and dev:type(),
+                               name    = dev and dev:get_i18n(),
+                               ether   = link and link.type == 1
                        }
                end
 
                if wan6 then
+                       local dev = wan6:get_interface()
+                       local link = dev and ipc.link(dev:name())
                        rv.wan6 = {
                                ip6addr   = wan6:ip6addr(),
                                gw6addr   = wan6:gw6addr(),
                                ip6prefix = wan6:ip6prefix(),
                                uptime    = wan6:uptime(),
                                proto     = wan6:proto(),
+                               i18n      = wan6:get_i18n(),
                                ifname    = wan6:ifname(),
-                               link      = wan6:adminlink()
+                               link      = wan6:adminlink(),
+                               mac       = dev and dev:mac(),
+                               type      = dev and dev:type(),
+                               name      = dev and dev:get_i18n(),
+                               ether     = link and link.type == 1
                        }
                end
 
                luci.http.prepare_content("application/json")
                luci.http.write_json(rv)
 
-               return
-       elseif luci.http.formvalue("hosts") == "1" then
-               luci.http.prepare_content("application/json")
-               luci.http.write_json(luci.sys.net.host_hints())
-
                return
        end
 -%>
 
 <%+header%>
 
-<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
 <script type="text/javascript">//<![CDATA[
        function progressbar(v, m)
        {
                var pc = Math.floor((100 / mn) * vn);
 
                return String.format(
-                       '<div style="width:200px; position:relative; border:1px solid #999999">' +
+                       '<div style="width:100%%; max-width:200px; position:relative; border:1px solid #999999">' +
                                '<div style="background-color:#CCCCCC; width:%d%%; height:15px">' +
                                        '<div style="position:absolute; left:0; top:0; text-align:center; width:100%%; color:#000000">' +
                                                '<small>%s / %s (%d%%)</small>' +
                );
        }
 
-       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%>';
-               }
+       function labelList(items, offset) {
+               var rv = [ ];
 
-               return s;
-       }
+               for (var i = offset || 0; i < items.length; i += 2) {
+                       var label = items[i],
+                           value = items[i+1];
+
+                       if (value === undefined || value === null)
+                               continue;
 
-       function duid2mac(duid) {
-               // DUID-LLT / Ethernet
-               if (duid.length === 28 && duid.substr(0, 8) === '00010001')
-                       return duid.substr(16).replace(/(..)(?=..)/g, '$1:').toUpperCase();
+                       if (label)
+                               rv.push(E('strong', [label, ': ']));
 
-               // DUID-LL / Ethernet
-               if (duid.length === 24 && duid.substr(0, 8) === '00030001')
-                       return duid.substr(8).replace(/(..)(?=..)/g, '$1:').toUpperCase();
+                       rv.push(value, E('br'));
+               }
 
-               return null;
+               return rv;
        }
 
-       var npoll = 1;
-       var hosts = <%=luci.http.write_json(luci.sys.net.host_hints())%>;
+       function renderBox(title, active, childs) {
+               childs = childs || [];
+               childs.unshift(E('span', labelList(arguments, 3)));
 
-       function updateHosts() {
-               XHR.get('<%=REQUEST_URI%>', { hosts: 1 }, function(x, data) {
-                       hosts = data;
-               });
+               return E('div', { class: 'ifacebox' }, [
+                       E('div', { class: 'ifacebox-head center ' + (active ? 'active' : '') },
+                               E('strong', title)),
+                       E('div', { class: 'ifacebox-body left' }, childs)
+               ]);
+       }
+
+       function renderBadge(icon, title) {
+               return E('span', { class: 'ifacebadge' }, [
+                       E('img', { src: icon, title: title || '' }),
+                       E('span', labelList(arguments, 2))
+               ]);
        }
 
        XHR.poll(5, '<%=REQUEST_URI%>', { status: 1 },
                function(x, info)
                {
-                       if (!(npoll++ % 5))
-                               updateHosts();
-
-                       var si = document.getElementById('wan4_i');
-                       var ss = document.getElementById('wan4_s');
-                       var ifc = info.wan;
-
-                       if (ifc && ifc.ifname && ifc.proto != 'none')
-                       {
-                               var s = String.format(
-                                       '<strong><%:Type%>: </strong>%s<br />' +
-                                       '<strong><%:Address%>: </strong>%s<br />' +
-                                       '<strong><%:Netmask%>: </strong>%s<br />' +
-                                       '<strong><%:Gateway%>: </strong>%s<br />',
-                                               ifc.proto,
-                                               (ifc.ipaddr) ? ifc.ipaddr : '0.0.0.0',
-                                               (ifc.netmask && ifc.netmask != ifc.ipaddr) ? ifc.netmask : '255.255.255.255',
-                                               (ifc.gwaddr) ? ifc.gwaddr : '0.0.0.0'
-                               );
-
-                               for (var i = 0; i < ifc.dns.length; i++)
-                               {
-                                       s += String.format(
-                                               '<strong><%:DNS%> %d: </strong>%s<br />',
-                                               i + 1, ifc.dns[i]
-                                       );
-                               }
-
-                               if (ifc.expires > -1)
-                               {
-                                       s += String.format(
-                                               '<strong><%:Expires%>: </strong>%t<br />',
-                                               ifc.expires
-                                       );
-                               }
-
-                               if (ifc.uptime > 0)
-                               {
-                                       s += String.format(
-                                               '<strong><%:Connected%>: </strong>%t<br />',
-                                               ifc.uptime
-                                       );
-                               }
-
-                               ss.innerHTML = String.format('<small>%s</small>', s);
-                               si.innerHTML = String.format(
-                                       '<img src="<%=resource%>/icons/ethernet.png" />' +
-                                       '<br /><small><a href="%s">%s</a></small>',
-                                               ifc.link, ifc.ifname
-                               );
-                       }
-                       else
-                       {
-                               si.innerHTML = '<img src="<%=resource%>/icons/ethernet_disabled.png" /><br /><small>?</small>';
-                               ss.innerHTML = '<em><%:Not connected%></em>';
-                       }
+                       var us = document.getElementById('upstream_status_table');
+
+                       while (us.lastElementChild)
+                               us.removeChild(us.lastElementChild);
+
+                       var ifc = info.wan || {};
+
+                       us.appendChild(renderBox(
+                               '<%:IPv4 Upstream%>',
+                               (ifc.ifname && ifc.proto != 'none'),
+                               [ E('div', {}, renderBadge(
+                                       '<%=resource%>/icons/%s.png'.format((ifc && ifc.type) ? ifc.type : 'ethernet_disabled'), null,
+                                       '<%:Device%>', ifc ? (ifc.name || ifc.ifname || '-') : '-',
+                                       '<%:MAC-Address%>', (ifc && ifc.ether) ? ifc.mac : null)) ],
+                               '<%:Protocol%>', ifc.i18n || E('em', '<%:Not connected%>'),
+                               '<%:Address%>', (ifc.ipaddr) ? ifc.ipaddr : '0.0.0.0',
+                               '<%:Netmask%>', (ifc.netmask && ifc.netmask != ifc.ipaddr) ? ifc.netmask : '255.255.255.255',
+                               '<%:Gateway%>', (ifc.gwaddr) ? ifc.gwaddr : '0.0.0.0',
+                               '<%:DNS%> 1', (ifc.dns) ? ifc.dns[0] : null,
+                               '<%:DNS%> 2', (ifc.dns) ? ifc.dns[1] : null,
+                               '<%:DNS%> 3', (ifc.dns) ? ifc.dns[2] : null,
+                               '<%:DNS%> 4', (ifc.dns) ? ifc.dns[3] : null,
+                               '<%:DNS%> 5', (ifc.dns) ? ifc.dns[4] : null,
+                               '<%:Expires%>', (ifc.expires > -1) ? '%t'.format(ifc.expires) : null,
+                               '<%:Connected%>', (ifc.uptime > 0) ? '%t'.format(ifc.uptime) : null));
 
                        <% if has_ipv6 then %>
-                       var si6 = document.getElementById('wan6_i');
-                       var ss6 = document.getElementById('wan6_s');
-                       var ifc6 = info.wan6;
-
-                       if (ifc6 && ifc6.ifname && ifc6.proto != 'none')
-                       {
-                               var s = String.format(
-                                       '<strong><%:Type%>: </strong>%s%s<br />',
-                                               ifc6.proto, (ifc6.ip6prefix) ? '-pd' : ''
-                               );
-                               
-                               if (!ifc6.ip6prefix)
-                               {
-                                       s += String.format(
-                                               '<strong><%:Address%>: </strong>%s<br />',
-                                               (ifc6.ip6addr) ? ifc6.ip6addr : '::'
-                                       );
-                               }
-                               else
-                               {
-                                       s += String.format(
-                                               '<strong><%:Prefix Delegated%>: </strong>%s<br />',
-                                               ifc6.ip6prefix
-                                       );
-                                       if (ifc6.ip6addr)
-                                       {
-                                               s += String.format(
-                                                       '<strong><%:Address%>: </strong>%s<br />',
-                                                       ifc6.ip6addr
-                                               );
-                                       }
-                               }
-
-                               s += String.format(
-                                       '<strong><%:Gateway%>: </strong>%s<br />',
-                                               (ifc6.gw6addr) ? ifc6.gw6addr : '::'
-                               );
-
-                               for (var i = 0; i < ifc6.dns.length; i++)
-                               {
-                                       s += String.format(
-                                               '<strong><%:DNS%> %d: </strong>%s<br />',
-                                               i + 1, ifc6.dns[i]
-                                       );
-                               }
-
-                               if (ifc6.uptime > 0)
-                               {
-                                       s += String.format(
-                                               '<strong><%:Connected%>: </strong>%t<br />',
-                                               ifc6.uptime
-                                       );
-                               }
-
-                               ss6.innerHTML = String.format('<small>%s</small>', s);
-                               si6.innerHTML = String.format(
-                                       '<img src="<%=resource%>/icons/ethernet.png" />' +
-                                       '<br /><small><a href="%s">%s</a></small>',
-                                               ifc6.link, ifc6.ifname
-                               );
-                       }
-                       else
-                       {
-                               si6.innerHTML = '<img src="<%=resource%>/icons/ethernet_disabled.png" /><br /><small>?</small>';
-                               ss6.innerHTML = '<em><%:Not connected%></em>';
-                       }
+                       var ifc6 = info.wan6 || {};
+
+                       us.appendChild(renderBox(
+                               '<%:IPv6 Upstream%>',
+                               (ifc6.ifname && ifc6.proto != 'none'),
+                               [ E('div', {}, renderBadge(
+                                       '<%=resource%>/icons/%s.png'.format(ifc6.type || 'ethernet_disabled'), null,
+                                       '<%:Device%>', ifc6 ? (ifc6.name || ifc6.ifname || '-') : '-',
+                                       '<%:MAC-Address%>', (ifc6 && ifc6.ether) ? ifc6.mac : null)) ],
+                               '<%:Protocol%>', ifc6.i18n ? (ifc6.i18n + (ifc6.proto === 'dhcp' && ifc6.ip6prefix ? '-PD' : '')) : E('em', '<%:Not connected%>'),
+                               '<%:Prefix Delegated%>', ifc6.ip6prefix,
+                               '<%:Address%>', (ifc6.ip6prefix) ? (ifc6.ip6addr || null) : (ifc6.ipaddr || '::'),
+                               '<%:Gateway%>', (ifc6.gw6addr) ? ifc6.gw6addr : '::',
+                               '<%:DNS%> 1', (ifc6.dns) ? ifc6.dns[0] : null,
+                               '<%:DNS%> 2', (ifc6.dns) ? ifc6.dns[1] : null,
+                               '<%:DNS%> 3', (ifc6.dns) ? ifc6.dns[2] : null,
+                               '<%:DNS%> 4', (ifc6.dns) ? ifc6.dns[3] : null,
+                               '<%:DNS%> 5', (ifc6.dns) ? ifc6.dns[4] : null,
+                               '<%:Connected%>', (ifc6.uptime > 0) ? '%t'.format(ifc6.uptime) : null));
                        <% end %>
 
                        <% if has_dsl then %>
                                );
                        <% end %>
 
-                       <% if has_dhcp then %>
-                       var ls = document.getElementById('lease_status_table');
-                       if (ls)
-                       {
-                               /* clear all rows */
-                               while( ls.rows.length > 1 )
-                                       ls.rows[0].parentNode.deleteRow(1);
-
-                               for( var i = 0; i < info.leases.length; i++ )
-                               {
-                                       var timestr;
-
-                                       if (info.leases[i].expires === false)
-                                               timestr = '<em><%:unlimited%></em>';
-                                       else if (info.leases[i].expires <= 0)
-                                               timestr = '<em><%:expired%></em>';
-                                       else
-                                               timestr = String.format('%t', info.leases[i].expires);
-
-                                       var tr = ls.rows[0].parentNode.insertRow(-1);
-                                               tr.className = 'cbi-section-table-row cbi-rowstyle-' + ((i % 2) + 1);
-
-                                       tr.insertCell(-1).innerHTML = info.leases[i].hostname ? info.leases[i].hostname : '?';
-                                       tr.insertCell(-1).innerHTML = info.leases[i].ipaddr;
-                                       tr.insertCell(-1).innerHTML = info.leases[i].macaddr;
-                                       tr.insertCell(-1).innerHTML = timestr;
-                               }
-
-                               if( ls.rows.length == 1 )
-                               {
-                                       var tr = ls.rows[0].parentNode.insertRow(-1);
-                                               tr.className = 'cbi-section-table-row';
-
-                                       var td = tr.insertCell(-1);
-                                               td.colSpan = 4;
-                                               td.innerHTML = '<em><br /><%:There are no active leases.%></em>';
-                               }
-                       }
-
-                       var ls6 = document.getElementById('lease6_status_table');
-                       if (ls6 && info.leases6)
-                       {
-                               ls6.parentNode.style.display = 'block';
-
-                               /* clear all rows */
-                               while( ls6.rows.length > 1 )
-                                       ls6.rows[0].parentNode.deleteRow(1);
-
-                               for( var i = 0; i < info.leases6.length; i++ )
-                               {
-                                       var timestr;
-
-                                       if (info.leases6[i].expires === false)
-                                               timestr = '<em><%:unlimited%></em>';
-                                       else if (info.leases6[i].expires <= 0)
-                                               timestr = '<em><%:expired%></em>';
-                                       else
-                                               timestr = String.format('%t', info.leases6[i].expires);
-
-                                       var tr = ls6.rows[0].parentNode.insertRow(-1);
-                                               tr.className = 'cbi-section-table-row cbi-rowstyle-' + ((i % 2) + 1);
-
-                                       var host = hosts[duid2mac(info.leases6[i].duid)];
-                                       if (host)
-                                               tr.insertCell(-1).innerHTML = String.format(
-                                                       '<div style="max-width:200px;overflow:hidden;text-overflow:ellipsis">%s</div>',
-                                                       ((host.name && (host.ipv4 || host.ipv6))
-                                                               ? '%h (%s)'.format(host.name, host.ipv4 || host.ipv6)
-                                                               : '%h'.format(host.name || host.ipv4 || host.ipv6)).nobr()
-                                               );
-                                       else
-                                               tr.insertCell(-1).innerHTML = info.leases6[i].hostname ? info.leases6[i].hostname : '?';
-
-                                       tr.insertCell(-1).innerHTML = info.leases6[i].ip6addr;
-                                       tr.insertCell(-1).innerHTML = info.leases6[i].duid;
-                                       tr.insertCell(-1).innerHTML = timestr;
-                               }
-
-                               if( ls6.rows.length == 1 )
-                               {
-                                       var tr = ls6.rows[0].parentNode.insertRow(-1);
-                                               tr.className = 'cbi-section-table-row';
-
-                                       var td = tr.insertCell(-1);
-                                               td.colSpan = 4;
-                                               td.innerHTML = '<em><br /><%:There are no active leases.%></em>';
-                               }
-                       }
-                       <% end %>
-
                        <% if has_wifi then %>
-                       var assoclist = [ ];
-
                        var ws = document.getElementById('wifi_status_table');
                        if (ws)
                        {
-                               var wsbody = ws.rows[0].parentNode;
-                               while (ws.rows.length > 0)
-                                       wsbody.deleteRow(0);
+                               while (ws.lastElementChild)
+                                       ws.removeChild(ws.lastElementChild);
 
                                for (var didx = 0; didx < info.wifinets.length; didx++)
                                {
                                        var dev = info.wifinets[didx];
-
-                                       var tr = wsbody.insertRow(-1);
-                                       var td;
-
-                                       td = tr.insertCell(-1);
-                                       td.width     = "33%";
-                                       td.innerHTML = dev.name;
-                                       td.style.verticalAlign = "top";
-
-                                       td = tr.insertCell(-1);
-
-                                       var s = '';
+                                       var net0 = (dev.networks && dev.networks[0]) ? dev.networks[0] : {};
+                                       var vifs = [];
 
                                        for (var nidx = 0; nidx < dev.networks.length; nidx++)
                                        {
                                                else
                                                        icon = "<%=resource%>/icons/signal-75-100.png";
 
-                                               s += String.format(
-                                                       '<table><tr><td style="text-align:center; width:32px; padding:3px">' +
-                                                               '<img src="%s" title="<%:Signal%>: %d dBm / <%:Noise%>: %d dBm" />' +
-                                                               '<br /><small>%d%%</small>' +
-                                                       '</td><td style="text-align:left; padding:3px"><small>' +
-                                                               '<strong><%:SSID%>:</strong> <a href="%s">%h</a><br />' +
-                                                               '<strong><%:Mode%>:</strong> %s<br />' +
-                                                               '<strong><%:Channel%>:</strong> %d (%.3f <%:GHz%>)<br />' +
-                                                               '<strong><%:Bitrate%>:</strong> %s <%:Mbit/s%><br />',
-                                                               icon, net.signal, net.noise,
-                                                               net.quality,
-                                                               net.link, net.ssid || '?',
-                                                               net.mode,
-                                                               net.channel, net.frequency,
-                                                               net.bitrate || '?'
-                                               );
-
-                                               if (is_assoc)
-                                               {
-                                                       s += String.format(
-                                                               '<strong><%:BSSID%>:</strong> %s<br />' +
-                                                               '<strong><%:Encryption%>:</strong> %s',
-                                                                       net.bssid || '?',
-                                                                       net.encryption
-                                                       );
-                                               }
-                                               else
-                                               {
-                                                       s += '<em><%:Wireless is disabled or not associated%></em>';
-                                               }
-
-                                               s += '</small></td></tr></table>';
-
-                                               for (var bssid in net.assoclist)
-                                               {
-                                                       var bss = net.assoclist[bssid];
-
-                                                       bss.bssid  = bssid;
-                                                       bss.link   = net.link;
-                                                       bss.name   = net.name;
-                                                       bss.ifname = net.ifname;
-                                                       bss.radio  = dev.name;
-
-                                                       assoclist.push(bss);
-                                               }
+                                               vifs.push(renderBadge(
+                                                       icon,
+                                                       '<%:Signal%>: %d dBm / <%:Quality%>: %d%%'.format(net.signal, net.quality),
+                                                       '<%:SSID%>', E('a', { href: net.link }, [ net.ssid || '?' ]),
+                                                       '<%:Mode%>', net.mode,
+                                                       '<%:BSSID%>', is_assoc ? (net.bssid || '-') : null,
+                                                       '<%:Encryption%>', is_assoc ? net.encryption : null,
+                                                       '<%:Associations%>', is_assoc ? (net.num_assoc || '-') : null,
+                                                       null, is_assoc ? null : E('em', '<%:Wireless is disabled or not associated%>')));
                                        }
 
-                                       if (!s)
-                                               s = '<em><%:No information available%></em>';
-
-                                       td.innerHTML = s;
-                               }
-                       }
-
-                       var ac = document.getElementById('wifi_assoc_table');
-                       if (ac)
-                       {
-                               /* clear all rows */
-                               while( ac.rows.length > 1 )
-                                       ac.rows[0].parentNode.deleteRow(1);
-
-                               assoclist.sort(function(a, b) {
-                                       return (a.name == b.name)
-                                               ? (a.bssid < b.bssid)
-                                               : (a.name  > b.name )
-                                       ;
-                               });
-
-                               for( var i = 0; i < assoclist.length; i++ )
-                               {
-                                       var tr = ac.rows[0].parentNode.insertRow(-1);
-                                               tr.className = 'cbi-section-table-row cbi-rowstyle-' + (1 + (i % 2));
-
-                                       var icon;
-                                       var q = (-1 * (assoclist[i].noise - assoclist[i].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";
-
-                                       tr.insertCell(-1).innerHTML = String.format(
-                                               '<span class="ifacebadge" title="%q"><img src="<%=resource%>/icons/wifi.png" /> %h</span>',
-                                               assoclist[i].radio, assoclist[i].ifname
-                                       );
-
-                                       tr.insertCell(-1).innerHTML = String.format(
-                                               '<a href="%s">%s</a>',
-                                                       assoclist[i].link,
-                                                       '%h'.format(assoclist[i].name).nobr()
-                                       );
-
-                                       tr.insertCell(-1).innerHTML = assoclist[i].bssid;
-
-                                       var host = hosts[assoclist[i].bssid];
-                                       if (host)
-                                               tr.insertCell(-1).innerHTML = String.format(
-                                                       '<div style="max-width:200px;overflow:hidden;text-overflow:ellipsis">%s</div>',
-                                                       ((host.name && (host.ipv4 || host.ipv6))
-                                                               ? '%h (%s)'.format(host.name, host.ipv4 || host.ipv6)
-                                                               : '%h'.format(host.name || host.ipv4 || host.ipv6)).nobr()
-                                               );
-                                       else
-                                               tr.insertCell(-1).innerHTML = '?';
-
-                                       tr.insertCell(-1).innerHTML = String.format(
-                                               '<span class="ifacebadge" title="<%:Signal%>: %d <%:dBm%> / <%:Noise%>: %d <%:dBm%> / <%:SNR%>: %d"><img src="%s" /> %d / %d <%:dBm%></span>',
-                                               assoclist[i].signal, assoclist[i].noise, assoclist[i].signal - assoclist[i].noise,
-                                               icon,
-                                               assoclist[i].signal, assoclist[i].noise
-                                       );
-
-                                       tr.insertCell(-1).innerHTML = wifirate(assoclist[i], true).nobr() + '<br />' + wifirate(assoclist[i], false).nobr();
+                                       ws.appendChild(renderBox(
+                                               dev.device, dev.up || net0.up,
+                                               [ E('div', vifs) ],
+                                               '<%:Type%>', dev.name.replace(/^Generic | Wireless Controller .+$/g, ''),
+                                               '<%:Channel%>', net0.channel ? '%d (%.3f <%:GHz%>)'.format(net0.channel, net0.frequency) : '-',
+                                               '<%:Bitrate%>', net0.bitrate ? '%d <%:Mbit/s%>'.format(net0.bitrate) : '-'));
                                }
 
-                               if (ac.rows.length == 1)
-                               {
-                                       var tr = ac.rows[0].parentNode.insertRow(-1);
-                                               tr.className = 'cbi-section-table-row';
-
-                                       var td = tr.insertCell(-1);
-                                               td.colSpan = 7;
-                                               td.innerHTML = '<br /><em><%:No information available%></em>';
-                               }
+                               if (!ws.lastElementChild)
+                                       ws.appendChild(E('<em><%:No information available%></em>'));
                        }
                        <% end %>
 
 
 <h2 name="content"><%:Status%></h2>
 
-<fieldset class="cbi-section">
-       <legend><%:System%></legend>
+<div class="cbi-section">
+       <h3><%:System%></h3>
 
-       <table width="100%" cellspacing="10">
-               <tr><td width="33%"><%:Hostname%></td><td><%=luci.sys.hostname() or "?"%></td></tr>
-               <tr><td width="33%"><%:Model%></td><td><%=pcdata(boardinfo.model or boardinfo.system or "?")%></td></tr>
-               <tr><td width="33%"><%:Firmware Version%></td><td>
+       <div class="table" width="100%">
+               <div class="tr"><div class="td left" width="33%"><%:Hostname%></div><div class="td left"><%=luci.sys.hostname() or "?"%></div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Model%></div><div class="td left"><%=pcdata(boardinfo.model or "?")%></div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Architecture%></div><div class="td left"><%=pcdata(boardinfo.system or "?")%></div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Firmware Version%></div><div class="td left">
                        <%=pcdata(ver.distname)%> <%=pcdata(ver.distversion)%> /
                        <%=pcdata(ver.luciname)%> (<%=pcdata(ver.luciversion)%>)
-               </td></tr>
-               <tr><td width="33%"><%:Kernel Version%></td><td><%=unameinfo.release or "?"%></td></tr>
-               <tr><td width="33%"><%:Local Time%></td><td id="localtime">-</td></tr>
-               <tr><td width="33%"><%:Uptime%></td><td id="uptime">-</td></tr>
-               <tr><td width="33%"><%:Load Average%></td><td id="loadavg">-</td></tr>
-       </table>
-</fieldset>
-
-<fieldset class="cbi-section">
-       <legend><%:Memory%></legend>
-
-       <table width="100%" cellspacing="10">
-               <tr><td width="33%"><%:Total Available%></td><td id="memtotal">-</td></tr>
-               <tr><td width="33%"><%:Free%></td><td id="memfree">-</td></tr>
-               <tr><td width="33%"><%:Buffered%></td><td id="membuff">-</td></tr>
-       </table>
-</fieldset>
+               </div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Kernel Version%></div><div class="td left"><%=unameinfo.release or "?"%></div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Local Time%></div><div class="td left" id="localtime">-</div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Uptime%></div><div class="td left" id="uptime">-</div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Load Average%></div><div class="td left" id="loadavg">-</div></div>
+       </div>
+</div>
+
+<div class="cbi-section">
+       <h3><%:Memory%></h3>
+
+       <div class="table" width="100%">
+               <div class="tr"><div class="td left" width="33%"><%:Total Available%></div><div class="td left" id="memtotal">-</div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Free%></div><div class="td left" id="memfree">-</div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Buffered%></div><div class="td left" id="membuff">-</div></div>
+       </div>
+</div>
 
 <% if swapinfo.total > 0 then %>
-<fieldset class="cbi-section">
-       <legend><%:Swap%></legend>
-
-       <table width="100%" cellspacing="10">
-               <tr><td width="33%"><%:Total Available%></td><td id="swaptotal">-</td></tr>
-               <tr><td width="33%"><%:Free%></td><td id="swapfree">-</td></tr>
-       </table>
-</fieldset>
+<div class="cbi-section">
+       <h3><%:Swap%></h3>
+
+       <div class="table" width="100%">
+               <div class="tr"><div class="td left" width="33%"><%:Total Available%></div><div class="td left" id="swaptotal">-</div></div>
+               <div class="tr"><div class="td left" width="33%"><%:Free%></div><div class="td left" id="swapfree">-</div></div>
+       </div>
+</div>
 <% end %>
 
-<fieldset class="cbi-section">
-       <legend><%:Network%></legend>
-
-       <table width="100%" cellspacing="10">
-               <tr><td width="33%" style="vertical-align:top"><%:IPv4 WAN Status%></td><td>
-                       <table><tr>
-                               <td id="wan4_i" style="width:16px; text-align:center; padding:3px"><img src="<%=resource%>/icons/ethernet_disabled.png" /><br /><small>?</small></td>
-                               <td id="wan4_s" style="vertical-align:middle; padding: 3px"><em><%:Collecting data...%></em></td>
-                       </tr></table>
-               </td></tr>
-               <% if has_ipv6 then %>
-               <tr><td width="33%" style="vertical-align:top"><%:IPv6 WAN Status%></td><td>
-                       <table><tr>
-                               <td id="wan6_i" style="width:16px; text-align:center; padding:3px"><img src="<%=resource%>/icons/ethernet_disabled.png" /><br /><small>?</small></td>
-                               <td id="wan6_s" style="vertical-align:middle; padding: 3px"><em><%:Collecting data...%></em></td>
-                       </tr></table>
-               </td></tr>
-               <% end %>
-               <tr><td width="33%"><%:Active Connections%></td><td id="conns">-</td></tr>
-       </table>
-</fieldset>
-
-<% if has_dhcp then %>
-<fieldset class="cbi-section">
-       <legend><%:DHCP Leases%></legend>
-
-       <table class="cbi-section-table" id="lease_status_table">
-               <tr class="cbi-section-table-titles">
-                       <th class="cbi-section-table-cell"><%:Hostname%></th>
-                       <th class="cbi-section-table-cell"><%:IPv4-Address%></th>
-                       <th class="cbi-section-table-cell"><%:MAC-Address%></th>
-                       <th class="cbi-section-table-cell"><%:Leasetime remaining%></th>
-               </tr>
-               <tr class="cbi-section-table-row">
-                       <td colspan="4"><em><br /><%:Collecting data...%></em></td>
-               </tr>
-       </table>
-</fieldset>
-
-<fieldset class="cbi-section" style="display:none">
-       <legend><%:DHCPv6 Leases%></legend>
-
-       <table class="cbi-section-table" id="lease6_status_table">
-               <tr class="cbi-section-table-titles">
-                       <th class="cbi-section-table-cell"><%:Host%></th>
-                       <th class="cbi-section-table-cell"><%:IPv6-Address%></th>
-                       <th class="cbi-section-table-cell"><%:DUID%></th>
-                       <th class="cbi-section-table-cell"><%:Leasetime remaining%></th>
-               </tr>
-               <tr class="cbi-section-table-row">
-                       <td colspan="4"><em><br /><%:Collecting data...%></em></td>
-               </tr>
-       </table>
-</fieldset>
-<% end %>
+<div class="cbi-section">
+       <h3><%:Network%></h3>
+
+       <div id="upstream_status_table" class="network-status-table">
+               <p><em><%:Collecting data...%></em></p>
+       </div>
+
+       <div class="table" width="100%">
+               <div class="tr"><div class="td left" width="33%"><%:Active Connections%></div><div class="td left" id="conns">-</div></div>
+       </div>
+</div>
+
+<%
+       if has_dhcp then
+               include("admin_network/lease_status")
+       end
+%>
 
 <% if has_dsl then %>
-<fieldset class="cbi-section">
-       <legend><%:DSL%></legend>
-       <table width="100%" cellspacing="10">
-               <tr><td width="33%" style="vertical-align:top"><%:DSL Status%></td><td>
-                       <table><tr>
-                               <td id="dsl_i" style="width:16px; text-align:center; padding:3px"><img src="<%=resource%>/icons/ethernet_disabled.png" /><br /><small>?</small></td>
-                               <td id="dsl_s" style="vertical-align:middle; padding: 3px"><em><%:Collecting data...%></em></td>
-                       </tr></table>
-               </td></tr>
-       </table>
-</fieldset>
+<div class="cbi-section">
+       <h3><%:DSL%></h3>
+
+       <div class="table" width="100%">
+               <div class="tr">
+                       <div class="td left" width="33%" style="vertical-align:top"><%:DSL Status%></div>
+                       <div class="td">
+                               <div class="table">
+                                       <div class="tr">
+                                               <div class="td" id="dsl_i" style="width:16px; text-align:center; padding:3px"><img src="<%=resource%>/icons/ethernet_disabled.png" /><br /><small>?</small></div>
+                                               <div class="td left" id="dsl_s" style="vertical-align:middle; padding: 3px"><em><%:Collecting data...%></em></div>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+</div>
 <% end %>
 
 <% if has_wifi then %>
-<fieldset class="cbi-section">
-       <legend><%:Wireless%></legend>
-
-       <table id="wifi_status_table" width="100%" cellspacing="10">
-               <tr><td><em><%:Collecting data...%></em></td></tr>
-       </table>
-</fieldset>
-
-<fieldset class="cbi-section">
-       <legend><%:Associated Stations%></legend>
-
-       <table class="cbi-section-table valign-middle" id="wifi_assoc_table">
-               <tr class="cbi-section-table-titles">
-                       <th class="cbi-section-table-cell">&#160;</th>
-                       <th class="cbi-section-table-cell"><%:Network%></th>
-                       <th class="cbi-section-table-cell"><%:MAC-Address%></th>
-                       <th class="cbi-section-table-cell"><%:Host%></th>
-                       <th class="cbi-section-table-cell"><%:Signal%> / <%:Noise%></th>
-                       <th class="cbi-section-table-cell"><%:RX Rate%> / <%:TX Rate%></th>
-               </tr>
-               <tr class="cbi-section-table-row">
-                       <td colspan="6"><em><br /><%:Collecting data...%></em></td>
-               </tr>
-       </table>
-</fieldset>
+<div class="cbi-section">
+       <h3><%:Wireless%></h3>
+
+       <div id="wifi_status_table" class="network-status-table">
+               <p><em><%:Collecting data...%></em></p>
+       </div>
+</div>
+
+<div class="cbi-section">
+       <h3><%:Associated Stations%></h3>
+
+       <%+admin_network/wifi_assoclist%>
+</div>
 <% end %>
 
 <%-