summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-base/htdocs
diff options
context:
space:
mode:
authorJo-Philipp Wich <jow@openwrt.org>2014-12-03 15:17:05 +0100
committerJo-Philipp Wich <jow@openwrt.org>2015-01-08 16:26:20 +0100
commit1bb4822dca6113f73e3bc89e2acf15935e6f8e92 (patch)
tree35e16f100466e4e00657199b38bb3d87d52bf73f /modules/luci-base/htdocs
parent9edd0e46c3f880727738ce8ca6ff1c8b85f99ef4 (diff)
Rework LuCI build system
* Rename subdirectories to their repective OpenWrt package names * Make each LuCI module its own standalone package * Deploy a shared luci.mk which is used by each module Makefile Signed-off-by: Jo-Philipp Wich <jow@openwrt.org>
Diffstat (limited to 'modules/luci-base/htdocs')
-rwxr-xr-xmodules/luci-base/htdocs/cgi-bin/luci5
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi.js1339
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/add.gifbin0 -> 378 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/apply.gifbin0 -> 268 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/arrow.gifbin0 -> 135 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/down.gifbin0 -> 131 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/download.gifbin0 -> 189 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/edit.gifbin0 -> 280 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/fieldadd.gifbin0 -> 379 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/file.gifbin0 -> 371 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/find.gifbin0 -> 273 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/folder.gifbin0 -> 698 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/help.gifbin0 -> 266 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/key.gifbin0 -> 230 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/link.gifbin0 -> 279 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/reload.gifbin0 -> 248 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/remove.gifbin0 -> 385 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/reset.gifbin0 -> 258 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/save.gifbin0 -> 263 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/up.gifbin0 -> 130 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi/user.gifbin0 -> 246 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/bridge.pngbin0 -> 838 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/bridge_disabled.pngbin0 -> 517 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/encryption.pngbin0 -> 1224 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/encryption_disabled.pngbin0 -> 1197 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/ethernet.pngbin0 -> 814 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/ethernet_disabled.pngbin0 -> 508 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/loading.gifbin0 -> 1787 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/port_down.pngbin0 -> 982 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/port_up.pngbin0 -> 1401 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/signal-0-25.pngbin0 -> 495 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/signal-0.pngbin0 -> 478 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/signal-25-50.pngbin0 -> 501 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/signal-50-75.pngbin0 -> 500 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/signal-75-100.pngbin0 -> 485 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/signal-none.pngbin0 -> 681 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/switch.pngbin0 -> 794 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/switch_disabled.pngbin0 -> 505 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/tunnel.pngbin0 -> 533 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/tunnel_disabled.pngbin0 -> 546 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/vlan.pngbin0 -> 794 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/vlan_disabled.pngbin0 -> 505 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/wifi.pngbin0 -> 1037 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/wifi_big.pngbin0 -> 2865 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/wifi_big_disabled.pngbin0 -> 2853 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/icons/wifi_disabled.pngbin0 -> 980 bytes
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/xhr.js241
47 files changed, 1585 insertions, 0 deletions
diff --git a/modules/luci-base/htdocs/cgi-bin/luci b/modules/luci-base/htdocs/cgi-bin/luci
new file mode 100755
index 0000000000..529d1d0bc5
--- /dev/null
+++ b/modules/luci-base/htdocs/cgi-bin/luci
@@ -0,0 +1,5 @@
+#!/usr/bin/lua
+require "luci.cacheloader"
+require "luci.sgi.cgi"
+luci.dispatcher.indexcache = "/tmp/luci-indexcache"
+luci.sgi.cgi.run() \ No newline at end of file
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi.js b/modules/luci-base/htdocs/luci-static/resources/cbi.js
new file mode 100644
index 0000000000..02814a5428
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi.js
@@ -0,0 +1,1339 @@
+/*
+ LuCI - Lua Configuration Interface
+
+ Copyright 2008 Steven Barth <steven@midlink.org>
+ Copyright 2008-2012 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
+*/
+
+var cbi_d = [];
+var cbi_t = [];
+var cbi_c = [];
+
+var cbi_validators = {
+
+ 'integer': function()
+ {
+ return (this.match(/^-?[0-9]+$/) != null);
+ },
+
+ 'uinteger': function()
+ {
+ return (cbi_validators.integer.apply(this) && (this >= 0));
+ },
+
+ 'float': function()
+ {
+ return !isNaN(parseFloat(this));
+ },
+
+ 'ufloat': function()
+ {
+ return (cbi_validators['float'].apply(this) && (this >= 0));
+ },
+
+ 'ipaddr': function()
+ {
+ return cbi_validators.ip4addr.apply(this) ||
+ cbi_validators.ip6addr.apply(this);
+ },
+
+ 'ip4addr': function()
+ {
+ if (this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\S+))?$/))
+ {
+ return (RegExp.$1 >= 0) && (RegExp.$1 <= 255) &&
+ (RegExp.$2 >= 0) && (RegExp.$2 <= 255) &&
+ (RegExp.$3 >= 0) && (RegExp.$3 <= 255) &&
+ (RegExp.$4 >= 0) && (RegExp.$4 <= 255) &&
+ ((RegExp.$6.indexOf('.') < 0)
+ ? ((RegExp.$6 >= 0) && (RegExp.$6 <= 32))
+ : (cbi_validators.ip4addr.apply(RegExp.$6)))
+ ;
+ }
+
+ return false;
+ },
+
+ 'ip6addr': function()
+ {
+ if( this.match(/^([a-fA-F0-9:.]+)(\/(\d+))?$/) )
+ {
+ if( !RegExp.$2 || ((RegExp.$3 >= 0) && (RegExp.$3 <= 128)) )
+ {
+ var addr = RegExp.$1;
+
+ if( addr == '::' )
+ {
+ return true;
+ }
+
+ if( addr.indexOf('.') > 0 )
+ {
+ var off = addr.lastIndexOf(':');
+
+ if( !(off && cbi_validators.ip4addr.apply(addr.substr(off+1))) )
+ return false;
+
+ addr = addr.substr(0, off) + ':0:0';
+ }
+
+ if( addr.indexOf('::') >= 0 )
+ {
+ var colons = 0;
+ var fill = '0';
+
+ for( var i = 1; i < (addr.length-1); i++ )
+ if( addr.charAt(i) == ':' )
+ colons++;
+
+ if( colons > 7 )
+ return false;
+
+ for( var i = 0; i < (7 - colons); i++ )
+ fill += ':0';
+
+ if (addr.match(/^(.*?)::(.*?)$/))
+ addr = (RegExp.$1 ? RegExp.$1 + ':' : '') + fill +
+ (RegExp.$2 ? ':' + RegExp.$2 : '');
+ }
+
+ return (addr.match(/^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$/) != null);
+ }
+ }
+
+ return false;
+ },
+
+ 'port': function()
+ {
+ return cbi_validators.integer.apply(this) &&
+ (this >= 0) && (this <= 65535);
+ },
+
+ 'portrange': function()
+ {
+ if (this.match(/^(\d+)-(\d+)$/))
+ {
+ var p1 = RegExp.$1;
+ var p2 = RegExp.$2;
+
+ return cbi_validators.port.apply(p1) &&
+ cbi_validators.port.apply(p2) &&
+ (parseInt(p1) <= parseInt(p2))
+ ;
+ }
+ else
+ {
+ return cbi_validators.port.apply(this);
+ }
+ },
+
+ 'macaddr': function()
+ {
+ return (this.match(/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/) != null);
+ },
+
+ 'host': function()
+ {
+ return cbi_validators.hostname.apply(this) ||
+ cbi_validators.ipaddr.apply(this);
+ },
+
+ 'hostname': function()
+ {
+ if (this.length <= 253)
+ return (this.match(/^[a-zA-Z0-9]+$/) != null ||
+ (this.match(/^[a-zA-Z0-9_][a-zA-Z0-9_\-.]*[a-zA-Z0-9]$/) &&
+ this.match(/[^0-9.]/)));
+
+ return false;
+ },
+
+ 'network': function()
+ {
+ return cbi_validators.uciname.apply(this) ||
+ cbi_validators.host.apply(this);
+ },
+
+ 'wpakey': function()
+ {
+ var v = this;
+
+ if( v.length == 64 )
+ return (v.match(/^[a-fA-F0-9]{64}$/) != null);
+ else
+ return (v.length >= 8) && (v.length <= 63);
+ },
+
+ 'wepkey': function()
+ {
+ var v = this;
+
+ if ( v.substr(0,2) == 's:' )
+ v = v.substr(2);
+
+ if( (v.length == 10) || (v.length == 26) )
+ return (v.match(/^[a-fA-F0-9]{10,26}$/) != null);
+ else
+ return (v.length == 5) || (v.length == 13);
+ },
+
+ 'uciname': function()
+ {
+ return (this.match(/^[a-zA-Z0-9_]+$/) != null);
+ },
+
+ 'range': function(min, max)
+ {
+ var val = parseFloat(this);
+ if (!isNaN(min) && !isNaN(max) && !isNaN(val))
+ return ((val >= min) && (val <= max));
+
+ return false;
+ },
+
+ 'min': function(min)
+ {
+ var val = parseFloat(this);
+ if (!isNaN(min) && !isNaN(val))
+ return (val >= min);
+
+ return false;
+ },
+
+ 'max': function(max)
+ {
+ var val = parseFloat(this);
+ if (!isNaN(max) && !isNaN(val))
+ return (val <= max);
+
+ return false;
+ },
+
+ 'rangelength': function(min, max)
+ {
+ var val = '' + this;
+ if (!isNaN(min) && !isNaN(max))
+ return ((val.length >= min) && (val.length <= max));
+
+ return false;
+ },
+
+ 'minlength': function(min)
+ {
+ var val = '' + this;
+ if (!isNaN(min))
+ return (val.length >= min);
+
+ return false;
+ },
+
+ 'maxlength': function(max)
+ {
+ var val = '' + this;
+ if (!isNaN(max))
+ return (val.length <= max);
+
+ return false;
+ },
+
+ 'or': function()
+ {
+ for (var i = 0; i < arguments.length; i += 2)
+ {
+ if (typeof arguments[i] != 'function')
+ {
+ if (arguments[i] == this)
+ return true;
+ i--;
+ }
+ else if (arguments[i].apply(this, arguments[i+1]))
+ {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ 'and': function()
+ {
+ for (var i = 0; i < arguments.length; i += 2)
+ {
+ if (typeof arguments[i] != 'function')
+ {
+ if (arguments[i] != this)
+ return false;
+ i--;
+ }
+ else if (!arguments[i].apply(this, arguments[i+1]))
+ {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ 'neg': function()
+ {
+ return cbi_validators.or.apply(
+ this.replace(/^[ \t]*![ \t]*/, ''), arguments);
+ },
+
+ 'list': function(subvalidator, subargs)
+ {
+ if (typeof subvalidator != 'function')
+ return false;
+
+ var tokens = this.match(/[^ \t]+/g);
+ for (var i = 0; i < tokens.length; i++)
+ if (!subvalidator.apply(tokens[i], subargs))
+ return false;
+
+ return true;
+ },
+ 'phonedigit': function()
+ {
+ return (this.match(/^[0-9\*#!\.]+$/) != null);
+ }
+};
+
+
+function cbi_d_add(field, dep, next) {
+ var obj = document.getElementById(field);
+ if (obj) {
+ var entry
+ for (var i=0; i<cbi_d.length; i++) {
+ if (cbi_d[i].id == field) {
+ entry = cbi_d[i];
+ break;
+ }
+ }
+ if (!entry) {
+ entry = {
+ "node": obj,
+ "id": field,
+ "parent": obj.parentNode.id,
+ "next": next,
+ "deps": []
+ };
+ cbi_d.unshift(entry);
+ }
+ entry.deps.push(dep)
+ }
+}
+
+function cbi_d_checkvalue(target, ref) {
+ var t = document.getElementById(target);
+ var value;
+
+ if (!t) {
+ var tl = document.getElementsByName(target);
+
+ if( tl.length > 0 && (tl[0].type == 'radio' || tl[0].type == 'checkbox'))
+ for( var i = 0; i < tl.length; i++ )
+ if( tl[i].checked ) {
+ value = tl[i].value;
+ break;
+ }
+
+ value = value ? value : "";
+ } else if (!t.value) {
+ value = "";
+ } else {
+ value = t.value;
+
+ if (t.type == "checkbox") {
+ value = t.checked ? value : "";
+ }
+ }
+
+ return (value == ref)
+}
+
+function cbi_d_check(deps) {
+ var reverse;
+ var def = false;
+ for (var i=0; i<deps.length; i++) {
+ var istat = true;
+ reverse = false;
+ for (var j in deps[i]) {
+ if (j == "!reverse") {
+ reverse = true;
+ } else if (j == "!default") {
+ def = true;
+ istat = false;
+ } else {
+ istat = (istat && cbi_d_checkvalue(j, deps[i][j]))
+ }
+ }
+ if (istat) {
+ return !reverse;
+ }
+ }
+ return def;
+}
+
+function cbi_d_update() {
+ var state = false;
+ for (var i=0; i<cbi_d.length; i++) {
+ var entry = cbi_d[i];
+ var next = document.getElementById(entry.next)
+ var node = document.getElementById(entry.id)
+ var parent = document.getElementById(entry.parent)
+
+ if (node && node.parentNode && !cbi_d_check(entry.deps)) {
+ node.parentNode.removeChild(node);
+ state = true;
+ if( entry.parent )
+ cbi_c[entry.parent]--;
+ } else if ((!node || !node.parentNode) && cbi_d_check(entry.deps)) {
+ if (!next) {
+ parent.appendChild(entry.node);
+ } else {
+ next.parentNode.insertBefore(entry.node, next);
+ }
+ state = true;
+ if( entry.parent )
+ cbi_c[entry.parent]++;
+ }
+ }
+
+ if (entry && entry.parent) {
+ if (!cbi_t_update())
+ cbi_tag_last(parent);
+ }
+
+ if (state) {
+ cbi_d_update();
+ }
+}
+
+function cbi_bind(obj, type, callback, mode) {
+ if (!obj.addEventListener) {
+ obj.attachEvent('on' + type,
+ function(){
+ var e = window.event;
+
+ if (!e.target && e.srcElement)
+ e.target = e.srcElement;
+
+ return !!callback(e);
+ }
+ );
+ } else {
+ obj.addEventListener(type, callback, !!mode);
+ }
+ return obj;
+}
+
+function cbi_combobox(id, values, def, man) {
+ var selid = "cbi.combobox." + id;
+ if (document.getElementById(selid)) {
+ return
+ }
+
+ var obj = document.getElementById(id)
+ var sel = document.createElement("select");
+ sel.id = selid;
+ sel.className = obj.className.replace(/cbi-input-text/, 'cbi-input-select');
+
+ if (obj.nextSibling) {
+ obj.parentNode.insertBefore(sel, obj.nextSibling);
+ } else {
+ obj.parentNode.appendChild(sel);
+ }
+
+ var dt = obj.getAttribute('cbi_datatype');
+ var op = obj.getAttribute('cbi_optional');
+
+ if (dt)
+ cbi_validate_field(sel, op == 'true', dt);
+
+ if (!values[obj.value]) {
+ if (obj.value == "") {
+ var optdef = document.createElement("option");
+ optdef.value = "";
+ optdef.appendChild(document.createTextNode(def));
+ sel.appendChild(optdef);
+ } else {
+ var opt = document.createElement("option");
+ opt.value = obj.value;
+ opt.selected = "selected";
+ opt.appendChild(document.createTextNode(obj.value));
+ sel.appendChild(opt);
+ }
+ }
+
+ for (var i in values) {
+ var opt = document.createElement("option");
+ opt.value = i;
+
+ if (obj.value == i) {
+ opt.selected = "selected";
+ }
+
+ opt.appendChild(document.createTextNode(values[i]));
+ sel.appendChild(opt);
+ }
+
+ var optman = document.createElement("option");
+ optman.value = "";
+ optman.appendChild(document.createTextNode(man));
+ sel.appendChild(optman);
+
+ obj.style.display = "none";
+
+ cbi_bind(sel, "change", function() {
+ if (sel.selectedIndex == sel.options.length - 1) {
+ obj.style.display = "inline";
+ sel.parentNode.removeChild(sel);
+ obj.focus();
+ } else {
+ obj.value = sel.options[sel.selectedIndex].value;
+ }
+
+ try {
+ cbi_d_update();
+ } catch (e) {
+ //Do nothing
+ }
+ })
+
+ // Retrigger validation in select
+ sel.focus();
+ sel.blur();
+}
+
+function cbi_combobox_init(id, values, def, man) {
+ var obj = document.getElementById(id);
+ cbi_bind(obj, "blur", function() {
+ cbi_combobox(id, values, def, man)
+ });
+ cbi_combobox(id, values, def, man);
+}
+
+function cbi_filebrowser(id, url, defpath) {
+ var field = document.getElementById(id);
+ var browser = window.open(
+ url + ( field.value || defpath || '' ) + '?field=' + id,
+ "luci_filebrowser", "width=300,height=400,left=100,top=200,scrollbars=yes"
+ );
+
+ browser.focus();
+}
+
+function cbi_browser_init(id, respath, url, defpath)
+{
+ function cbi_browser_btnclick(e) {
+ cbi_filebrowser(id, url, defpath);
+ return false;
+ }
+
+ var field = document.getElementById(id);
+
+ var btn = document.createElement('img');
+ btn.className = 'cbi-image-button';
+ btn.src = respath + '/cbi/folder.gif';
+ field.parentNode.insertBefore(btn, field.nextSibling);
+
+ cbi_bind(btn, 'click', cbi_browser_btnclick);
+}
+
+function cbi_dynlist_init(name, respath, datatype, optional, choices)
+{
+ var input0 = document.getElementsByName(name)[0];
+ var prefix = input0.name;
+ var parent = input0.parentNode;
+ var holder = input0.placeholder;
+
+ var values;
+
+ function cbi_dynlist_redraw(focus, add, del)
+ {
+ values = [ ];
+
+ while (parent.firstChild)
+ {
+ var n = parent.firstChild;
+ var i = parseInt(n.index);
+
+ if (i != del)
+ {
+ if (n.nodeName.toLowerCase() == 'input')
+ values.push(n.value || '');
+ else if (n.nodeName.toLowerCase() == 'select')
+ values[values.length-1] = n.options[n.selectedIndex].value;
+ }
+
+ parent.removeChild(n);
+ }
+
+ if (add >= 0)
+ {
+ focus = add+1;
+ values.splice(focus, 0, '');
+ }
+ else if (values.length == 0)
+ {
+ focus = 0;
+ values.push('');
+ }
+
+ for (var i = 0; i < values.length; i++)
+ {
+ var t = document.createElement('input');
+ t.id = prefix + '.' + (i+1);
+ t.name = prefix;
+ t.value = values[i];
+ t.type = 'text';
+ t.index = i;
+ t.className = 'cbi-input-text';
+
+ if (i == 0 && holder)
+ {
+ t.placeholder = holder;
+ }
+
+ var b = document.createElement('img');
+ b.src = respath + ((i+1) < values.length ? '/cbi/remove.gif' : '/cbi/add.gif');
+ b.className = 'cbi-image-button';
+
+ parent.appendChild(t);
+ parent.appendChild(b);
+ parent.appendChild(document.createElement('br'));
+
+ if (datatype)
+ {
+ cbi_validate_field(t.id, ((i+1) == values.length) || optional, datatype);
+ }
+
+ if (choices)
+ {
+ cbi_combobox_init(t.id, choices[0], '', choices[1]);
+ t.nextSibling.index = i;
+
+ cbi_bind(t.nextSibling, 'keydown', cbi_dynlist_keydown);
+ cbi_bind(t.nextSibling, 'keypress', cbi_dynlist_keypress);
+
+ if (i == focus || -i == focus)
+ t.nextSibling.focus();
+ }
+ else
+ {
+ cbi_bind(t, 'keydown', cbi_dynlist_keydown);
+ cbi_bind(t, 'keypress', cbi_dynlist_keypress);
+
+ if (i == focus)
+ {
+ t.focus();
+ }
+ else if (-i == focus)
+ {
+ t.focus();
+
+ /* force cursor to end */
+ var v = t.value;
+ t.value = ' '
+ t.value = v;
+ }
+ }
+
+ cbi_bind(b, 'click', cbi_dynlist_btnclick);
+ }
+ }
+
+ function cbi_dynlist_keypress(ev)
+ {
+ ev = ev ? ev : window.event;
+
+ var se = ev.target ? ev.target : ev.srcElement;
+
+ if (se.nodeType == 3)
+ se = se.parentNode;
+
+ switch (ev.keyCode)
+ {
+ /* backspace, delete */
+ case 8:
+ case 46:
+ if (se.value.length == 0)
+ {
+ if (ev.preventDefault)
+ ev.preventDefault();
+
+ return false;
+ }
+
+ return true;
+
+ /* enter, arrow up, arrow down */
+ case 13:
+ case 38:
+ case 40:
+ if (ev.preventDefault)
+ ev.preventDefault();
+
+ return false;
+ }
+
+ return true;
+ }
+
+ function cbi_dynlist_keydown(ev)
+ {
+ ev = ev ? ev : window.event;
+
+ var se = ev.target ? ev.target : ev.srcElement;
+
+ if (se.nodeType == 3)
+ se = se.parentNode;
+
+ var prev = se.previousSibling;
+ while (prev && prev.name != name)
+ prev = prev.previousSibling;
+
+ var next = se.nextSibling;
+ while (next && next.name != name)
+ next = next.nextSibling;
+
+ /* advance one further in combobox case */
+ if (next && next.nextSibling.name == name)
+ next = next.nextSibling;
+
+ switch (ev.keyCode)
+ {
+ /* backspace, delete */
+ case 8:
+ case 46:
+ var del = (se.nodeName.toLowerCase() == 'select')
+ ? true : (se.value.length == 0);
+
+ if (del)
+ {
+ if (ev.preventDefault)
+ ev.preventDefault();
+
+ var focus = se.index;
+ if (ev.keyCode == 8)
+ focus = -focus+1;
+
+ cbi_dynlist_redraw(focus, -1, se.index);
+
+ return false;
+ }
+
+ break;
+
+ /* enter */
+ case 13:
+ cbi_dynlist_redraw(-1, se.index, -1);
+ break;
+
+ /* arrow up */
+ case 38:
+ if (prev)
+ prev.focus();
+
+ break;
+
+ /* arrow down */
+ case 40:
+ if (next)
+ next.focus();
+
+ break;
+ }
+
+ return true;
+ }
+
+ function cbi_dynlist_btnclick(ev)
+ {
+ ev = ev ? ev : window.event;
+
+ var se = ev.target ? ev.target : ev.srcElement;
+
+ if (se.src.indexOf('remove') > -1)
+ {
+ se.previousSibling.value = '';
+
+ cbi_dynlist_keydown({
+ target: se.previousSibling,
+ keyCode: 8
+ });
+ }
+ else
+ {
+ cbi_dynlist_keydown({
+ target: se.previousSibling,
+ keyCode: 13
+ });
+ }
+
+ return false;
+ }
+
+ cbi_dynlist_redraw(NaN, -1, -1);
+}
+
+//Hijacks the CBI form to send via XHR (requires Prototype)
+function cbi_hijack_forms(layer, win, fail, load) {
+ var forms = layer.getElementsByTagName('form');
+ for (var i=0; i<forms.length; i++) {
+ $(forms[i]).observe('submit', function(event) {
+ // Prevent the form from also submitting the regular way
+ event.stop();
+
+ // Submit via XHR
+ event.element().request({
+ onSuccess: win,
+ onFailure: fail
+ });
+
+ if (load) {
+ load();
+ }
+ });
+ }
+}
+
+
+function cbi_t_add(section, tab) {
+ var t = document.getElementById('tab.' + section + '.' + tab);
+ var c = document.getElementById('container.' + section + '.' + tab);
+
+ if( t && c ) {
+ cbi_t[section] = (cbi_t[section] || [ ]);
+ cbi_t[section][tab] = { 'tab': t, 'container': c, 'cid': c.id };
+ }
+}
+
+function cbi_t_switch(section, tab) {
+ if( cbi_t[section] && cbi_t[section][tab] ) {
+ var o = cbi_t[section][tab];
+ var h = document.getElementById('tab.' + section);
+ for( var tid in cbi_t[section] ) {
+ var o2 = cbi_t[section][tid];
+ if( o.tab.id != o2.tab.id ) {
+ o2.tab.className = o2.tab.className.replace(/(^| )cbi-tab( |$)/, " cbi-tab-disabled ");
+ o2.container.style.display = 'none';
+ }
+ else {
+ if(h) h.value = tab;
+ o2.tab.className = o2.tab.className.replace(/(^| )cbi-tab-disabled( |$)/, " cbi-tab ");
+ o2.container.style.display = 'block';
+ }
+ }
+ }
+ return false
+}
+
+function cbi_t_update() {
+ var hl_tabs = [ ];
+ var updated = false;
+
+ for( var sid in cbi_t )
+ for( var tid in cbi_t[sid] )
+ {
+ if( cbi_c[cbi_t[sid][tid].cid] == 0 ) {
+ cbi_t[sid][tid].tab.style.display = 'none';
+ }
+ else if( cbi_t[sid][tid].tab && cbi_t[sid][tid].tab.style.display == 'none' ) {
+ cbi_t[sid][tid].tab.style.display = '';
+
+ var t = cbi_t[sid][tid].tab;
+ t.className += ' cbi-tab-highlighted';
+ hl_tabs.push(t);
+ }
+
+ cbi_tag_last(cbi_t[sid][tid].container);
+ updated = true;
+ }
+
+ if( hl_tabs.length > 0 )
+ window.setTimeout(function() {
+ for( var i = 0; i < hl_tabs.length; i++ )
+ hl_tabs[i].className = hl_tabs[i].className.replace(/ cbi-tab-highlighted/g, '');
+ }, 750);
+
+ return updated;
+}
+
+
+function cbi_validate_form(form, errmsg)
+{
+ /* if triggered by a section removal or addition, don't validate */
+ if( form.cbi_state == 'add-section' || form.cbi_state == 'del-section' )
+ return true;
+
+ if( form.cbi_validators )
+ {
+ for( var i = 0; i < form.cbi_validators.length; i++ )
+ {
+ var validator = form.cbi_validators[i];
+ if( !validator() && errmsg )
+ {
+ alert(errmsg);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+function cbi_validate_reset(form)
+{
+ window.setTimeout(
+ function() { cbi_validate_form(form, null) }, 100
+ );
+
+ return true;
+}
+
+function cbi_validate_compile(code)
+{
+ var pos = 0;
+ var esc = false;
+ var depth = 0;
+ var stack = [ ];
+
+ code += ',';
+
+ for (var i = 0; i < code.length; i++)
+ {
+ if (esc)
+ {
+ esc = false;
+ continue;
+ }
+
+ switch (code.charCodeAt(i))
+ {
+ case 92:
+ esc = true;
+ break;
+
+ case 40:
+ case 44:
+ if (depth <= 0)
+ {
+ if (pos < i)
+ {
+ var label = code.substring(pos, i);
+ label = label.replace(/\\(.)/g, '$1');
+ label = label.replace(/^[ \t]+/g, '');
+ label = label.replace(/[ \t]+$/g, '');
+
+ if (label && !isNaN(label))
+ {
+ stack.push(parseFloat(label));
+ }
+ else if (label.match(/^(['"]).*\1$/))
+ {
+ stack.push(label.replace(/^(['"])(.*)\1$/, '$2'));
+ }
+ else if (typeof cbi_validators[label] == 'function')
+ {
+ stack.push(cbi_validators[label]);
+ stack.push(null);
+ }
+ else
+ {
+ throw "Syntax error, unhandled token '"+label+"'";
+ }
+ }
+ pos = i+1;
+ }
+ depth += (code.charCodeAt(i) == 40);
+ break;
+
+ case 41:
+ if (--depth <= 0)
+ {
+ if (typeof stack[stack.length-2] != 'function')
+ throw "Syntax error, argument list follows non-function";
+
+ stack[stack.length-1] =
+ arguments.callee(code.substring(pos, i));
+
+ pos = i+1;
+ }
+ break;
+ }
+ }
+
+ return stack;
+}
+
+function cbi_validate_field(cbid, optional, type)
+{
+ var field = (typeof cbid == "string") ? document.getElementById(cbid) : cbid;
+ var vstack; try { vstack = cbi_validate_compile(type); } catch(e) { };
+
+ if (field && vstack && typeof vstack[0] == "function")
+ {
+ var validator = function()
+ {
+ // is not detached
+ if( field.form )
+ {
+ field.className = field.className.replace(/ cbi-input-invalid/g, '');
+
+ // validate value
+ var value = (field.options && field.options.selectedIndex > -1)
+ ? field.options[field.options.selectedIndex].value : field.value;
+
+ if (!(((value.length == 0) && optional) || vstack[0].apply(value, vstack[1])))
+ {
+ // invalid
+ field.className += ' cbi-input-invalid';
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ if( ! field.form.cbi_validators )
+ field.form.cbi_validators = [ ];
+
+ field.form.cbi_validators.push(validator);
+
+ cbi_bind(field, "blur", validator);
+ cbi_bind(field, "keyup", validator);
+
+ if (field.nodeName == 'SELECT')
+ {
+ cbi_bind(field, "change", validator);
+ cbi_bind(field, "click", validator);
+ }
+
+ field.setAttribute("cbi_validate", validator);
+ field.setAttribute("cbi_datatype", type);
+ field.setAttribute("cbi_optional", (!!optional).toString());
+
+ validator();
+
+ var fcbox = document.getElementById('cbi.combobox.' + field.id);
+ if (fcbox)
+ cbi_validate_field(fcbox, optional, type);
+ }
+}
+
+function cbi_row_swap(elem, up, store)
+{
+ var tr = elem.parentNode;
+ while (tr && tr.nodeName.toLowerCase() != 'tr')
+ tr = tr.parentNode;
+
+ if (!tr)
+ return false;
+
+ var table = tr.parentNode;
+ while (table && table.nodeName.toLowerCase() != 'table')
+ table = table.parentNode;
+
+ if (!table)
+ return false;
+
+ var s = up ? 3 : 2;
+ var e = up ? table.rows.length : table.rows.length - 1;
+
+ for (var idx = s; idx < e; idx++)
+ {
+ if (table.rows[idx] == tr)
+ {
+ if (up)
+ tr.parentNode.insertBefore(table.rows[idx], table.rows[idx-1]);
+ else
+ tr.parentNode.insertBefore(table.rows[idx+1], table.rows[idx]);
+
+ break;
+ }
+ }
+
+ var ids = [ ];
+ for (idx = 2; idx < table.rows.length; idx++)
+ {
+ table.rows[idx].className = table.rows[idx].className.replace(
+ /cbi-rowstyle-[12]/, 'cbi-rowstyle-' + (1 + (idx % 2))
+ );
+
+ if (table.rows[idx].id && table.rows[idx].id.match(/-([^\-]+)$/) )
+ ids.push(RegExp.$1);
+ }
+
+ var input = document.getElementById(store);
+ if (input)
+ input.value = ids.join(' ');
+
+ return false;
+}
+
+function cbi_tag_last(container)
+{
+ var last;
+
+ for (var i = 0; i < container.childNodes.length; i++)
+ {
+ var c = container.childNodes[i];
+ if (c.nodeType == 1 && c.nodeName.toLowerCase() == 'div')
+ {
+ c.className = c.className.replace(/ cbi-value-last$/, '');
+ last = c;
+ }
+ }
+
+ if (last)
+ {
+ last.className += ' cbi-value-last';
+ }
+}
+
+String.prototype.serialize = function()
+{
+ var o = this;
+ switch(typeof(o))
+ {
+ case 'object':
+ // null
+ if( o == null )
+ {
+ return 'null';
+ }
+
+ // array
+ else if( o.length )
+ {
+ var i, s = '';
+
+ for( var i = 0; i < o.length; i++ )
+ s += (s ? ', ' : '') + String.serialize(o[i]);
+
+ return '[ ' + s + ' ]';
+ }
+
+ // object
+ else
+ {
+ var k, s = '';
+
+ for( k in o )
+ s += (s ? ', ' : '') + k + ': ' + String.serialize(o[k]);
+
+ return '{ ' + s + ' }';
+ }
+
+ break;
+
+ case 'string':
+ // complex string
+ if( o.match(/[^a-zA-Z0-9_,.: -]/) )
+ return 'decodeURIComponent("' + encodeURIComponent(o) + '")';
+
+ // simple string
+ else
+ return '"' + o + '"';
+
+ break;
+
+ default:
+ return o.toString();
+ }
+}
+
+String.prototype.format = function()
+{
+ if (!RegExp)
+ return;
+
+ var html_esc = [/&/g, '&#38;', /"/g, '&#34;', /'/g, '&#39;', /</g, '&#60;', />/g, '&#62;'];
+ var quot_esc = [/"/g, '&#34;', /'/g, '&#39;'];
+
+ function esc(s, r) {
+ for( var i = 0; i < r.length; i += 2 )
+ s = s.replace(r[i], r[i+1]);
+ return s;
+ }
+
+ var str = this;
+ var out = '';
+ var re = /^(([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X|q|h|j|t|m))/;
+ var a = b = [], numSubstitutions = 0, numMatches = 0;
+
+ while( a = re.exec(str) )
+ {
+ var m = a[1];
+ var leftpart = a[2], pPad = a[3], pJustify = a[4], pMinLength = a[5];
+ var pPrecision = a[6], pType = a[7];
+
+ numMatches++;
+
+ if (pType == '%')
+ {
+ subst = '%';
+ }
+ else
+ {
+ if (numSubstitutions < arguments.length)
+ {
+ var param = arguments[numSubstitutions++];
+
+ var pad = '';
+ if (pPad && pPad.substr(0,1) == "'")
+ pad = leftpart.substr(1,1);
+ else if (pPad)
+ pad = pPad;
+
+ var justifyRight = true;
+ if (pJustify && pJustify === "-")
+ justifyRight = false;
+
+ var minLength = -1;
+ if (pMinLength)
+ minLength = parseInt(pMinLength);
+
+ var precision = -1;
+ if (pPrecision && pType == 'f')
+ precision = parseInt(pPrecision.substring(1));
+
+ var subst = param;
+
+ switch(pType)
+ {
+ case 'b':
+ subst = (parseInt(param) || 0).toString(2);
+ break;
+
+ case 'c':
+ subst = String.fromCharCode(parseInt(param) || 0);
+ break;
+
+ case 'd':
+ subst = (parseInt(param) || 0);
+ break;
+
+ case 'u':
+ subst = Math.abs(parseInt(param) || 0);
+ break;
+
+ case 'f':
+ subst = (precision > -1)
+ ? ((parseFloat(param) || 0.0)).toFixed(precision)
+ : (parseFloat(param) || 0.0);
+ break;
+
+ case 'o':
+ subst = (parseInt(param) || 0).toString(8);
+ break;
+
+ case 's':
+ subst = param;
+ break;
+
+ case 'x':
+ subst = ('' + (parseInt(param) || 0).toString(16)).toLowerCase();
+ break;
+
+ case 'X':
+ subst = ('' + (parseInt(param) || 0).toString(16)).toUpperCase();
+ break;
+
+ case 'h':
+ subst = esc(param, html_esc);
+ break;
+
+ case 'q':
+ subst = esc(param, quot_esc);
+ break;
+
+ case 'j':
+ subst = String.serialize(param);
+ break;
+
+ case 't':
+ var td = 0;
+ var th = 0;
+ var tm = 0;
+ var ts = (param || 0);
+
+ if (ts > 60) {
+ tm = Math.floor(ts / 60);
+ ts = (ts % 60);
+ }
+
+ if (tm > 60) {
+ th = Math.floor(tm / 60);
+ tm = (tm % 60);
+ }
+
+ if (th > 24) {
+ td = Math.floor(th / 24);
+ th = (th % 24);
+ }
+
+ subst = (td > 0)
+ ? String.format('%dd %dh %dm %ds', td, th, tm, ts)
+ : String.format('%dh %dm %ds', th, tm, ts);
+
+ break;
+
+ case 'm':
+ var mf = pMinLength ? parseInt(pMinLength) : 1000;
+ var pr = pPrecision ? Math.floor(10*parseFloat('0'+pPrecision)) : 2;
+
+ var i = 0;
+ var val = parseFloat(param || 0);
+ var units = [ '', 'K', 'M', 'G', 'T', 'P', 'E' ];
+
+ for (i = 0; (i < units.length) && (val > mf); i++)
+ val /= mf;
+
+ subst = val.toFixed(pr) + ' ' + units[i];
+ break;
+ }
+ }
+ }
+
+ out += leftpart + subst;
+ str = str.substr(m.length);
+ }
+
+ return out + str;
+}
+
+String.prototype.nobr = function()
+{
+ return this.replace(/[\s\n]+/g, '&#160;');
+}
+
+String.serialize = function()
+{
+ var a = [ ];
+ for (var i = 1; i < arguments.length; i++)
+ a.push(arguments[i]);
+ return ''.serialize.apply(arguments[0], a);
+}
+
+String.format = function()
+{
+ var a = [ ];
+ for (var i = 1; i < arguments.length; i++)
+ a.push(arguments[i]);
+ return ''.format.apply(arguments[0], a);
+}
+
+String.nobr = function()
+{
+ var a = [ ];
+ for (var i = 1; i < arguments.length; i++)
+ a.push(arguments[i]);
+ return ''.nobr.apply(arguments[0], a);
+}
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/add.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/add.gif
new file mode 100644
index 0000000000..0888abf85e
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/add.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/apply.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/apply.gif
new file mode 100644
index 0000000000..82ae7ed821
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/apply.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/arrow.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/arrow.gif
new file mode 100644
index 0000000000..10d797e9b0
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/arrow.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/down.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/down.gif
new file mode 100644
index 0000000000..f0bb6a4ea6
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/down.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/download.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/download.gif
new file mode 100644
index 0000000000..f99a5383b2
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/download.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/edit.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/edit.gif
new file mode 100644
index 0000000000..7b02b6e72a
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/edit.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/fieldadd.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/fieldadd.gif
new file mode 100644
index 0000000000..431ff64d1f
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/fieldadd.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/file.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/file.gif
new file mode 100644
index 0000000000..3b1217dd65
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/file.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/find.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/find.gif
new file mode 100644
index 0000000000..9ae5e3489b
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/find.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/folder.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/folder.gif
new file mode 100644
index 0000000000..22b583bb59
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/folder.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/help.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/help.gif
new file mode 100644
index 0000000000..9dfa0e196a
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/help.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/key.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/key.gif
new file mode 100644
index 0000000000..e3853e5afd
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/key.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/link.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/link.gif
new file mode 100644
index 0000000000..f0bb78da6b
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/link.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/reload.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/reload.gif
new file mode 100644
index 0000000000..8268958a19
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/reload.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/remove.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/remove.gif
new file mode 100644
index 0000000000..bf43a0a0bc
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/remove.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/reset.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/reset.gif
new file mode 100644
index 0000000000..c941c19028
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/reset.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/save.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/save.gif
new file mode 100644
index 0000000000..35e949963e
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/save.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/up.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/up.gif
new file mode 100644
index 0000000000..e8234178ef
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/up.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi/user.gif b/modules/luci-base/htdocs/luci-static/resources/cbi/user.gif
new file mode 100644
index 0000000000..dcb5c2a899
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi/user.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/bridge.png b/modules/luci-base/htdocs/luci-static/resources/icons/bridge.png
new file mode 100644
index 0000000000..4c163bf692
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/bridge.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/bridge_disabled.png b/modules/luci-base/htdocs/luci-static/resources/icons/bridge_disabled.png
new file mode 100644
index 0000000000..0f367c5369
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/bridge_disabled.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/encryption.png b/modules/luci-base/htdocs/luci-static/resources/icons/encryption.png
new file mode 100644
index 0000000000..41d2ba9ace
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/encryption.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/encryption_disabled.png b/modules/luci-base/htdocs/luci-static/resources/icons/encryption_disabled.png
new file mode 100644
index 0000000000..f2e05a4251
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/encryption_disabled.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/ethernet.png b/modules/luci-base/htdocs/luci-static/resources/icons/ethernet.png
new file mode 100644
index 0000000000..a02538124c
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/ethernet.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/ethernet_disabled.png b/modules/luci-base/htdocs/luci-static/resources/icons/ethernet_disabled.png
new file mode 100644
index 0000000000..2bb02e455a
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/ethernet_disabled.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/loading.gif b/modules/luci-base/htdocs/luci-static/resources/icons/loading.gif
new file mode 100644
index 0000000000..5bb90fd6a4
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/loading.gif
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/port_down.png b/modules/luci-base/htdocs/luci-static/resources/icons/port_down.png
new file mode 100644
index 0000000000..25ea172324
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/port_down.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/port_up.png b/modules/luci-base/htdocs/luci-static/resources/icons/port_up.png
new file mode 100644
index 0000000000..e063037910
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/port_up.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/signal-0-25.png b/modules/luci-base/htdocs/luci-static/resources/icons/signal-0-25.png
new file mode 100644
index 0000000000..2e5dff4663
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/signal-0-25.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/signal-0.png b/modules/luci-base/htdocs/luci-static/resources/icons/signal-0.png
new file mode 100644
index 0000000000..114583a676
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/signal-0.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/signal-25-50.png b/modules/luci-base/htdocs/luci-static/resources/icons/signal-25-50.png
new file mode 100644
index 0000000000..ee8cc4f1ce
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/signal-25-50.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/signal-50-75.png b/modules/luci-base/htdocs/luci-static/resources/icons/signal-50-75.png
new file mode 100644
index 0000000000..26bcbf715d
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/signal-50-75.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/signal-75-100.png b/modules/luci-base/htdocs/luci-static/resources/icons/signal-75-100.png
new file mode 100644
index 0000000000..5cffaa1b84
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/signal-75-100.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/signal-none.png b/modules/luci-base/htdocs/luci-static/resources/icons/signal-none.png
new file mode 100644
index 0000000000..b77585c0f0
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/signal-none.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/switch.png b/modules/luci-base/htdocs/luci-static/resources/icons/switch.png
new file mode 100644
index 0000000000..5c99ba5684
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/switch.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/switch_disabled.png b/modules/luci-base/htdocs/luci-static/resources/icons/switch_disabled.png
new file mode 100644
index 0000000000..b8c84c8dc4
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/switch_disabled.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/tunnel.png b/modules/luci-base/htdocs/luci-static/resources/icons/tunnel.png
new file mode 100644
index 0000000000..c5a09dd685
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/tunnel.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/tunnel_disabled.png b/modules/luci-base/htdocs/luci-static/resources/icons/tunnel_disabled.png
new file mode 100644
index 0000000000..ad9856cfec
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/tunnel_disabled.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/vlan.png b/modules/luci-base/htdocs/luci-static/resources/icons/vlan.png
new file mode 100644
index 0000000000..5c99ba5684
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/vlan.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/vlan_disabled.png b/modules/luci-base/htdocs/luci-static/resources/icons/vlan_disabled.png
new file mode 100644
index 0000000000..b8c84c8dc4
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/vlan_disabled.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/wifi.png b/modules/luci-base/htdocs/luci-static/resources/icons/wifi.png
new file mode 100644
index 0000000000..528ce2b4e9
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/wifi.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/wifi_big.png b/modules/luci-base/htdocs/luci-static/resources/icons/wifi_big.png
new file mode 100644
index 0000000000..d73a5e7401
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/wifi_big.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/wifi_big_disabled.png b/modules/luci-base/htdocs/luci-static/resources/icons/wifi_big_disabled.png
new file mode 100644
index 0000000000..af93b37b7a
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/wifi_big_disabled.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/icons/wifi_disabled.png b/modules/luci-base/htdocs/luci-static/resources/icons/wifi_disabled.png
new file mode 100644
index 0000000000..338a34f78c
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/icons/wifi_disabled.png
Binary files differ
diff --git a/modules/luci-base/htdocs/luci-static/resources/xhr.js b/modules/luci-base/htdocs/luci-static/resources/xhr.js
new file mode 100644
index 0000000000..701c12ac19
--- /dev/null
+++ b/modules/luci-base/htdocs/luci-static/resources/xhr.js
@@ -0,0 +1,241 @@
+/*
+ * xhr.js - XMLHttpRequest helper class
+ * (c) 2008-2010 Jo-Philipp Wich
+ */
+
+XHR = function()
+{
+ this.reinit = function()
+ {
+ if (window.XMLHttpRequest) {
+ this._xmlHttp = new XMLHttpRequest();
+ }
+ else if (window.ActiveXObject) {
+ this._xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
+ }
+ else {
+ alert("xhr.js: XMLHttpRequest is not supported by this browser!");
+ }
+ }
+
+ this.busy = function() {
+ if (!this._xmlHttp)
+ return false;
+
+ switch (this._xmlHttp.readyState)
+ {
+ case 1:
+ case 2:
+ case 3:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ this.abort = function() {
+ if (this.busy())
+ this._xmlHttp.abort();
+ }
+
+ this.get = function(url,data,callback)
+ {
+ this.reinit();
+
+ var xhr = this._xmlHttp;
+ var code = this._encode(data);
+
+ url = location.protocol + '//' + location.host + url;
+
+ if (code)
+ if (url.substr(url.length-1,1) == '&')
+ url += code;
+ else
+ url += '?' + code;
+
+ xhr.open('GET', url, true);
+
+ xhr.onreadystatechange = function()
+ {
+ if (xhr.readyState == 4) {
+ var json = null;
+ if (xhr.getResponseHeader("Content-Type") == "application/json") {
+ try {
+ json = eval('(' + xhr.responseText + ')');
+ }
+ catch(e) {
+ json = null;
+ }
+ }
+
+ callback(xhr, json);
+ }
+ }
+
+ xhr.send(null);
+ }
+
+ this.post = function(url,data,callback)
+ {
+ this.reinit();
+
+ var xhr = this._xmlHttp;
+ var code = this._encode(data);
+
+ xhr.onreadystatechange = function()
+ {
+ if (xhr.readyState == 4)
+ callback(xhr);
+ }
+
+ xhr.open('POST', url, true);
+ xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+ xhr.setRequestHeader('Content-length', code.length);
+ xhr.setRequestHeader('Connection', 'close');
+ xhr.send(code);
+ }
+
+ this.cancel = function()
+ {
+ this._xmlHttp.onreadystatechange = function(){};
+ this._xmlHttp.abort();
+ }
+
+ this.send_form = function(form,callback,extra_values)
+ {
+ var code = '';
+
+ for (var i = 0; i < form.elements.length; i++)
+ {
+ var e = form.elements[i];
+
+ if (e.options)
+ {
+ code += (code ? '&' : '') +
+ form.elements[i].name + '=' + encodeURIComponent(
+ e.options[e.selectedIndex].value
+ );
+ }
+ else if (e.length)
+ {
+ for (var j = 0; j < e.length; j++)
+ if (e[j].name) {
+ code += (code ? '&' : '') +
+ e[j].name + '=' + encodeURIComponent(e[j].value);
+ }
+ }
+ else
+ {
+ code += (code ? '&' : '') +
+ e.name + '=' + encodeURIComponent(e.value);
+ }
+ }
+
+ if (typeof extra_values == 'object')
+ for (var key in extra_values)
+ code += (code ? '&' : '') +
+ key + '=' + encodeURIComponent(extra_values[key]);
+
+ return(
+ (form.method == 'get')
+ ? this.get(form.getAttribute('action'), code, callback)
+ : this.post(form.getAttribute('action'), code, callback)
+ );
+ }
+
+ this._encode = function(obj)
+ {
+ obj = obj ? obj : { };
+ obj['_'] = Math.random();
+
+ if (typeof obj == 'object')
+ {
+ var code = '';
+ var self = this;
+
+ for (var k in obj)
+ code += (code ? '&' : '') +
+ k + '=' + encodeURIComponent(obj[k]);
+
+ return code;
+ }
+
+ return obj;
+ }
+}
+
+XHR.get = function(url, data, callback)
+{
+ (new XHR()).get(url, data, callback);
+}
+
+XHR.poll = function(interval, url, data, callback)
+{
+ if (isNaN(interval) || interval < 1)
+ interval = 5;
+
+ if (!XHR._q)
+ {
+ XHR._t = 0;
+ XHR._q = [ ];
+ XHR._r = function() {
+ for (var i = 0, e = XHR._q[0]; i < XHR._q.length; e = XHR._q[++i])
+ {
+ if (!(XHR._t % e.interval) && !e.xhr.busy())
+ e.xhr.get(e.url, e.data, e.callback);
+ }
+
+ XHR._t++;
+ };
+ }
+
+ XHR._q.push({
+ interval: interval,
+ callback: callback,
+ url: url,
+ data: data,
+ xhr: new XHR()
+ });
+
+ XHR.run();
+}
+
+XHR.halt = function()
+{
+ if (XHR._i)
+ {
+ /* show & set poll indicator */
+ try {
+ document.getElementById('xhr_poll_status').style.display = '';
+ document.getElementById('xhr_poll_status_on').style.display = 'none';
+ document.getElementById('xhr_poll_status_off').style.display = '';
+ } catch(e) { }
+
+ window.clearInterval(XHR._i);
+ XHR._i = null;
+ }
+}
+
+XHR.run = function()
+{
+ if (XHR._r && !XHR._i)
+ {
+ /* show & set poll indicator */
+ try {
+ document.getElementById('xhr_poll_status').style.display = '';
+ document.getElementById('xhr_poll_status_on').style.display = '';
+ document.getElementById('xhr_poll_status_off').style.display = 'none';
+ } catch(e) { }
+
+ /* kick first round manually to prevent one second lag when setting up
+ * the poll interval */
+ XHR._r();
+ XHR._i = window.setInterval(XHR._r, 1000);
+ }
+}
+
+XHR.running = function()
+{
+ return !!(XHR._r && XHR._i);
+}