luci2: move most RPC proxy function declarations into the views using them to reduce...
authorJo-Philipp Wich <jow@openwrt.org>
Sat, 1 Feb 2014 18:29:03 +0000 (18:29 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Sat, 1 Feb 2014 18:29:03 +0000 (18:29 +0000)
18 files changed:
luci2/htdocs/luci2/luci2.js
luci2/htdocs/luci2/view/network.diagnostics.js
luci2/htdocs/luci2/view/network.hosts.js
luci2/htdocs/luci2/view/network.routes.js
luci2/htdocs/luci2/view/network.switch.js
luci2/htdocs/luci2/view/status.dmesg.js
luci2/htdocs/luci2/view/status.overview.js
luci2/htdocs/luci2/view/status.processes.js
luci2/htdocs/luci2/view/status.routes.js
luci2/htdocs/luci2/view/status.syslog.js
luci2/htdocs/luci2/view/system.admin.js
luci2/htdocs/luci2/view/system.cron.js
luci2/htdocs/luci2/view/system.leds.js
luci2/htdocs/luci2/view/system.software.js
luci2/htdocs/luci2/view/system.startup.js
luci2/htdocs/luci2/view/system.system.js
luci2/htdocs/luci2/view/system.upgrade.js
luci2/htdocs/luci2/view/system.users.js

index 30e3063..34cef8a 100644 (file)
@@ -2805,41 +2805,6 @@ function LuCI2()
                        });
                },
 
                        });
                },
 
-               getProcessList: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'process_list',
-                       expect: { processes: [ ] },
-                       filter: function(data) {
-                               data.sort(function(a, b) { return a.pid - b.pid });
-                               return data;
-                       }
-               }),
-
-               getSystemLog: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'syslog',
-                       expect: { log: '' }
-               }),
-
-               getKernelLog: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'dmesg',
-                       expect: { log: '' }
-               }),
-
-               getZoneInfo: function(cb)
-               {
-                       return $.getJSON(_luci2.globals.resource + '/zoneinfo.json', cb);
-               },
-
-               sendSignal: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'process_signal',
-                       params: [ 'pid', 'signal' ],
-                       filter: function(data) {
-                               return (data == 0);
-                       }
-               }),
 
                initList: _luci2.rpc.declare({
                        object: 'luci2.system',
 
                initList: _luci2.rpc.declare({
                        object: 'luci2.system',
@@ -2879,239 +2844,12 @@ function LuCI2()
                initDisable: function(init, cb) { return _luci2.system.initRun(init, 'disable', cb) },
 
 
                initDisable: function(init, cb) { return _luci2.system.initRun(init, 'disable', cb) },
 
 
-               getRcLocal: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'rclocal_get',
-                       expect: { data: '' }
-               }),
-
-               setRcLocal: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'rclocal_set',
-                       params: [ 'data' ]
-               }),
-
-
-               getCrontab: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'crontab_get',
-                       expect: { data: '' }
-               }),
-
-               setCrontab: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'crontab_set',
-                       params: [ 'data' ]
-               }),
-
-
-               getSSHKeys: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'sshkeys_get',
-                       expect: { keys: [ ] }
-               }),
-
-               setSSHKeys: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'sshkeys_set',
-                       params: [ 'keys' ]
-               }),
-
-
-               setPassword: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'password_set',
-                       params: [ 'user', 'password' ]
-               }),
-
-
-               listLEDs: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'led_list',
-                       expect: { leds: [ ] }
-               }),
-
-               listUSBDevices: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'usb_list',
-                       expect: { devices: [ ] }
-               }),
-
-
-               testUpgrade: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'upgrade_test',
-                       expect: { '': { } }
-               }),
-
-               startUpgrade: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'upgrade_start',
-                       params: [ 'keep' ]
-               }),
-
-               cleanUpgrade: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'upgrade_clean'
-               }),
-
-
-               restoreBackup: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'backup_restore'
-               }),
-
-               cleanBackup: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'backup_clean'
-               }),
-
-
-               getBackupConfig: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'backup_config_get',
-                       expect: { config: '' }
-               }),
-
-               setBackupConfig: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'backup_config_set',
-                       params: [ 'data' ]
-               }),
-
-
-               listBackup: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'backup_list',
-                       expect: { files: [ ] }
-               }),
-
-
-               testReset: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'reset_test',
-                       expect: { supported: false }
-               }),
-
-               startReset: _luci2.rpc.declare({
-                       object: 'luci2.system',
-                       method: 'reset_start'
-               }),
-
-
                performReboot: _luci2.rpc.declare({
                        object: 'luci2.system',
                        method: 'reboot'
                })
        };
 
                performReboot: _luci2.rpc.declare({
                        object: 'luci2.system',
                        method: 'reboot'
                })
        };
 
-       this.opkg = {
-               updateLists: _luci2.rpc.declare({
-                       object: 'luci2.opkg',
-                       method: 'update',
-                       expect: { '': { } }
-               }),
-
-               _allPackages: _luci2.rpc.declare({
-                       object: 'luci2.opkg',
-                       method: 'list',
-                       params: [ 'offset', 'limit', 'pattern' ],
-                       expect: { '': { } }
-               }),
-
-               _installedPackages: _luci2.rpc.declare({
-                       object: 'luci2.opkg',
-                       method: 'list_installed',
-                       params: [ 'offset', 'limit', 'pattern' ],
-                       expect: { '': { } }
-               }),
-
-               _findPackages: _luci2.rpc.declare({
-                       object: 'luci2.opkg',
-                       method: 'find',
-                       params: [ 'offset', 'limit', 'pattern' ],
-                       expect: { '': { } }
-               }),
-
-               _fetchPackages: function(action, offset, limit, pattern)
-               {
-                       var packages = [ ];
-
-                       return action(offset, limit, pattern).then(function(list) {
-                               if (!list.total || !list.packages)
-                                       return { length: 0, total: 0 };
-
-                               packages.push.apply(packages, list.packages);
-                               packages.total = list.total;
-
-                               if (limit <= 0)
-                                       limit = list.total;
-
-                               if (packages.length >= limit)
-                                       return packages;
-
-                               _luci2.rpc.batch();
-
-                               for (var i = offset + packages.length; i < limit; i += 100)
-                                       action(i, (Math.min(i + 100, limit) % 100) || 100, pattern);
-
-                               return _luci2.rpc.flush();
-                       }).then(function(lists) {
-                               for (var i = 0; i < lists.length; i++)
-                               {
-                                       if (!lists[i].total || !lists[i].packages)
-                                               continue;
-
-                                       packages.push.apply(packages, lists[i].packages);
-                                       packages.total = lists[i].total;
-                               }
-
-                               return packages;
-                       });
-               },
-
-               listPackages: function(offset, limit, pattern)
-               {
-                       return _luci2.opkg._fetchPackages(_luci2.opkg._allPackages, offset, limit, pattern);
-               },
-
-               installedPackages: function(offset, limit, pattern)
-               {
-                       return _luci2.opkg._fetchPackages(_luci2.opkg._installedPackages, offset, limit, pattern);
-               },
-
-               findPackages: function(offset, limit, pattern)
-               {
-                       return _luci2.opkg._fetchPackages(_luci2.opkg._findPackages, offset, limit, pattern);
-               },
-
-               installPackage: _luci2.rpc.declare({
-                       object: 'luci2.opkg',
-                       method: 'install',
-                       params: [ 'package' ],
-                       expect: { '': { } }
-               }),
-
-               removePackage: _luci2.rpc.declare({
-                       object: 'luci2.opkg',
-                       method: 'remove',
-                       params: [ 'package' ],
-                       expect: { '': { } }
-               }),
-
-               getConfig: _luci2.rpc.declare({
-                       object: 'luci2.opkg',
-                       method: 'config_get',
-                       expect: { config: '' }
-               }),
-
-               setConfig: _luci2.rpc.declare({
-                       object: 'luci2.opkg',
-                       method: 'config_set',
-                       params: [ 'data' ]
-               })
-       };
-
        this.session = {
 
                login: _luci2.rpc.declare({
        this.session = {
 
                login: _luci2.rpc.declare({
index 99f185a..1649264 100644 (file)
@@ -1,24 +1,59 @@
 L.ui.view.extend({
        title: L.tr('Diagnostics'),
 
 L.ui.view.extend({
        title: L.tr('Diagnostics'),
 
+       runPing: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'ping',
+               params: [ 'data' ],
+               expect: { '': { code: -1 } }
+       }),
+
+       runPing6: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'ping6',
+               params: [ 'data' ],
+               expect: { '': { code: -1 } }
+       }),
+
+       runTraceroute: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'traceroute',
+               params: [ 'data' ],
+               expect: { '': { code: -1 } }
+       }),
+
+       runTraceroute6: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'traceroute6',
+               params: [ 'data' ],
+               expect: { '': { code: -1 } }
+       }),
+
+       runNslookup: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'nslookup',
+               params: [ 'data' ],
+               expect: { '': { code: -1 } }
+       }),
+
        execute: function() {
                var self = this;
                var tools = [ ];
 
                $.when(
        execute: function() {
                var self = this;
                var tools = [ ];
 
                $.when(
-                       L.network.runPing('?').then(function(rv) {
+                       self.runPing('?').then(function(rv) {
                                if (rv.code != -1) tools.push(['runPing', L.tr('IPv4 Ping')]);
                        }),
                                if (rv.code != -1) tools.push(['runPing', L.tr('IPv4 Ping')]);
                        }),
-                       L.network.runPing6('?').then(function(rv) {
+                       self.runPing6('?').then(function(rv) {
                                if (rv.code != -1) tools.push(['runPing6', L.tr('IPv6 Ping')]);
                        }),
                                if (rv.code != -1) tools.push(['runPing6', L.tr('IPv6 Ping')]);
                        }),
-                       L.network.runTraceroute('?').then(function(rv) {
+                       self.runTraceroute('?').then(function(rv) {
                                if (rv.code != -1) tools.push(['runTraceroute', L.tr('IPv4 Traceroute')]);
                        }),
                                if (rv.code != -1) tools.push(['runTraceroute', L.tr('IPv4 Traceroute')]);
                        }),
-                       L.network.runTraceroute6('?').then(function(rv) {
+                       self.runTraceroute6('?').then(function(rv) {
                                if (rv.code != -1) tools.push(['runTraceroute6', L.tr('IPv6 Tracroute')]);
                        }),
                                if (rv.code != -1) tools.push(['runTraceroute6', L.tr('IPv6 Tracroute')]);
                        }),
-                       L.network.runNslookup('?').then(function(rv) {
+                       self.runNslookup('?').then(function(rv) {
                                if (rv.code != -1) tools.push(['runNslookup', L.tr('DNS Lookup')]);
                        })
                ).then(function() {
                                if (rv.code != -1) tools.push(['runNslookup', L.tr('DNS Lookup')]);
                        })
                ).then(function() {
@@ -38,7 +73,7 @@ L.ui.view.extend({
 
                        $('#run').click(function() {
                                L.ui.loading(true);
 
                        $('#run').click(function() {
                                L.ui.loading(true);
-                               L.network[$('#tool').val()]($('#host').val()).then(function(rv) {
+                               self[$('#tool').val()]($('#host').val()).then(function(rv) {
                                        $('#output').empty().show();
 
                                        if (rv.stdout)
                                        $('#output').empty().show();
 
                                        if (rv.stdout)
index 587b181..99fbe47 100644 (file)
@@ -1,29 +1,29 @@
 L.ui.view.extend({
 L.ui.view.extend({
-    title: L.tr('Hostnames'),
-    description: L.tr('Manage static host records to let the local DNS server resolve certain names to specific IP addresses.'),
+       title: L.tr('Hostnames'),
+       description: L.tr('Manage static host records to let the local DNS server resolve certain names to specific IP addresses.'),
 
 
-    execute: function() {
-        var m = new L.cbi.Map('dhcp', {
-            readonly:    !this.options.acls.hostnames
-        });
+       execute: function() {
+               var m = new L.cbi.Map('dhcp', {
+                       readonly:    !this.options.acls.hostnames
+               });
 
 
-        var s = m.section(L.cbi.TableSection, 'domain', {
-            anonymous:   true,
-            addremove:   true,
-            add_caption: L.tr('Add new hostname'),
-            remove_caption: L.tr('Remove hostname')
-        });
+               var s = m.section(L.cbi.TableSection, 'domain', {
+                       anonymous:   true,
+                       addremove:   true,
+                       add_caption: L.tr('Add new hostname'),
+                       remove_caption: L.tr('Remove hostname')
+               });
 
 
-        s.option(L.cbi.InputValue, 'name', {
-            caption:     L.tr('Hostname'),
-            datatype:    'hostname'
-        });
+               s.option(L.cbi.InputValue, 'name', {
+                       caption:     L.tr('Hostname'),
+                       datatype:    'hostname'
+               });
 
 
-        s.option(L.cbi.InputValue, 'ip', {
-            caption:     L.tr('IP address'),
-            datatype:    'ipaddr'
-        });
+               s.option(L.cbi.InputValue, 'ip', {
+                       caption:     L.tr('IP address'),
+                       datatype:    'ipaddr'
+               });
 
 
-        return m.insertInto('#map');
-    }
+               return m.insertInto('#map');
+       }
 });
 });
index a1ed560..4464706 100644 (file)
 L.ui.view.extend({
 L.ui.view.extend({
-    title: L.tr('Routes'),
-    description: L.tr('Routes specify over which interface and gateway a certain host or network can be reached.'),
-
-    execute: function() {
-        var self = this;
-        return L.network.listNetworkNames().then(function(list) {
-            var m = new L.cbi.Map('network', {
-                readonly:    !self.options.acls.network
-            });
-
-            var s4 = m.section(L.cbi.TableSection, 'route', {
-                caption:     L.tr('Static IPv4 Routes'),
-                anonymous:   true,
-                addremove:   true,
-                sortable:    true,
-                add_caption: L.tr('Add new route'),
-                remove_caption: L.tr('Remove route')
-            });
-
-            var ifc = s4.option(L.cbi.ListValue, 'interface', {
-                caption:     L.tr('Interface')
-            });
-
-            for (var i = 0; i < list.length; i++)
-                ifc.value(list[i]);
-
-            s4.option(L.cbi.InputValue, 'target', {
-                caption:     L.tr('Target'),
-                datatype:    'ip4addr'
-            });
-
-            s4.option(L.cbi.InputValue, 'netmask', {
-                caption:     L.tr('IPv4-Netmask'),
-                datatype:    'ip4addr',
-                placeholder: '255.255.255.255',
-                optional:    true
-            });
-
-            s4.option(L.cbi.InputValue, 'gateway', {
-                caption:     L.tr('IPv4-Gateway'),
-                datatype:    'ip4addr',
-                optional:    true
-            });
-
-            s4.option(L.cbi.InputValue, 'metric', {
-                caption:     L.tr('Metric'),
-                datatype:    'range(0,255)',
-                placeholder: 0,
-                optional:    true
-            });
-
-            s4.option(L.cbi.InputValue, 'mtu', {
-                caption:     L.tr('MTU'),
-                datatype:    'range(64,9000)',
-                placeholder: 1500,
-                optional:    true
-            });
-
-
-            var s6 = m.section(L.cbi.TableSection, 'route6', {
-                caption:     L.tr('Static IPv6 Routes'),
-                anonymous:   true,
-                addremove:   true,
-                sortable:    true,
-                add_caption: L.tr('Add new route'),
-                remove_caption: L.tr('Remove route')
-            });
-
-            var ifc = s6.option(L.cbi.ListValue, 'interface', {
-                caption:     L.tr('Interface')
-            });
-
-            for (var i = 0; i < list.length; i++)
-                ifc.value(list[i]);
-
-            s6.option(L.cbi.InputValue, 'target', {
-                caption:     L.tr('Target'),
-                datatype:    'ip6addr'
-            });
-
-            s6.option(L.cbi.InputValue, 'gateway', {
-                caption:     L.tr('IPv6-Gateway'),
-                datatype:    'ip6addr',
-                optional:    true
-            });
-
-            s6.option(L.cbi.InputValue, 'metric', {
-                caption:     L.tr('Metric'),
-                datatype:    'range(0,255)',
-                placeholder: 0,
-                optional:    true
-            });
-
-            s6.option(L.cbi.InputValue, 'mtu', {
-                caption:     L.tr('MTU'),
-                datatype:    'range(64,9000)',
-                placeholder: 1500,
-                optional:    true
-            });
-
-            m.insertInto('#map');
-        });
-    }
+       title: L.tr('Routes'),
+       description: L.tr('Routes specify over which interface and gateway a certain host or network can be reached.'),
+
+       execute: function() {
+               var self = this;
+               var ifaces = L.NetworkModel.getInterfaces();
+
+               var m = new L.cbi.Map('network', {
+                       readonly:    !self.options.acls.network
+               });
+
+               var s4 = m.section(L.cbi.TableSection, 'route', {
+                       caption:     L.tr('Static IPv4 Routes'),
+                       anonymous:   true,
+                       addremove:   true,
+                       sortable:    true,
+                       add_caption: L.tr('Add new route'),
+                       remove_caption: L.tr('Remove route')
+               });
+
+               var ifc = s4.option(L.cbi.ListValue, 'interface', {
+                       caption:     L.tr('Interface')
+               });
+
+               for (var i = 0; i < ifaces.length; i++)
+                       ifc.value(ifaces[i].name());
+
+               s4.option(L.cbi.InputValue, 'target', {
+                       caption:     L.tr('Target'),
+                       datatype:    'ip4addr'
+               });
+
+               s4.option(L.cbi.InputValue, 'netmask', {
+                       caption:     L.tr('IPv4-Netmask'),
+                       datatype:    'ip4addr',
+                       placeholder: '255.255.255.255',
+                       optional:    true
+               });
+
+               s4.option(L.cbi.InputValue, 'gateway', {
+                       caption:     L.tr('IPv4-Gateway'),
+                       datatype:    'ip4addr',
+                       optional:    true
+               });
+
+               s4.option(L.cbi.InputValue, 'metric', {
+                       caption:     L.tr('Metric'),
+                       datatype:    'range(0,255)',
+                       placeholder: 0,
+                       optional:    true
+               });
+
+               s4.option(L.cbi.InputValue, 'mtu', {
+                       caption:     L.tr('MTU'),
+                       datatype:    'range(64,9000)',
+                       placeholder: 1500,
+                       optional:    true
+               });
+
+
+               var s6 = m.section(L.cbi.TableSection, 'route6', {
+                       caption:     L.tr('Static IPv6 Routes'),
+                       anonymous:   true,
+                       addremove:   true,
+                       sortable:    true,
+                       add_caption: L.tr('Add new route'),
+                       remove_caption: L.tr('Remove route')
+               });
+
+               var ifc = s6.option(L.cbi.ListValue, 'interface', {
+                       caption:     L.tr('Interface')
+               });
+
+               for (var i = 0; i < ifaces.length; i++)
+                       ifc.value(ifaces[i].name());
+
+               s6.option(L.cbi.InputValue, 'target', {
+                       caption:     L.tr('Target'),
+                       datatype:    'ip6addr'
+               });
+
+               s6.option(L.cbi.InputValue, 'gateway', {
+                       caption:     L.tr('IPv6-Gateway'),
+                       datatype:    'ip6addr',
+                       optional:    true
+               });
+
+               s6.option(L.cbi.InputValue, 'metric', {
+                       caption:     L.tr('Metric'),
+                       datatype:    'range(0,255)',
+                       placeholder: 0,
+                       optional:    true
+               });
+
+               s6.option(L.cbi.InputValue, 'mtu', {
+                       caption:     L.tr('MTU'),
+                       datatype:    'range(64,9000)',
+                       placeholder: 1500,
+                       optional:    true
+               });
+
+               m.insertInto('#map');
+       }
 });
 });
index bfe27c6..39a5b0b 100644 (file)
@@ -2,6 +2,37 @@ L.ui.view.extend({
        title: L.tr('Switch'),
        description: L.tr('The network ports on this device can be combined to several VLANs in which computers can communicate directly with each other. VLANs are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network.'),
 
        title: L.tr('Switch'),
        description: L.tr('The network ports on this device can be combined to several VLANs in which computers can communicate directly with each other. VLANs are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network.'),
 
+       listSwitchNames: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'switch_list',
+               expect: { switches: [ ] }
+       }),
+
+       getSwitchInfo: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'switch_info',
+               params: [ 'switch' ],
+               expect: { info: { } },
+               filter: function(data, params) {
+                       data['attrs']      = data['switch'];
+                       data['vlan_attrs'] = data['vlan'];
+                       data['port_attrs'] = data['port'];
+                       data['switch']     = params['switch'];
+
+                       delete data.vlan;
+                       delete data.port;
+
+                       return data;
+               }
+       }),
+
+       getSwitchStatus: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'switch_status',
+               params: [ 'switch' ],
+               expect: { ports: [ ] }
+       }),
+
        switchPortState: L.cbi.ListValue.extend({
                choices: [
                        [ 'n', L.trc('Switch port state', 'off')      ],
        switchPortState: L.cbi.ListValue.extend({
                choices: [
                        [ 'n', L.trc('Switch port state', 'off')      ],
@@ -60,11 +91,11 @@ L.ui.view.extend({
 
        execute: function() {
                var self = this;
 
        execute: function() {
                var self = this;
-               return L.network.listSwitchNames().then(function(switches) {
+               return self.listSwitchNames().then(function(switches) {
                        L.rpc.batch();
 
                        for (var i = 0; i < switches.length; i++)
                        L.rpc.batch();
 
                        for (var i = 0; i < switches.length; i++)
-                               L.network.getSwitchInfo(switches[i]);
+                               self.getSwitchInfo(switches[i]);
 
                        return L.rpc.flush();
                }).then(function(switches) {
 
                        return L.rpc.flush();
                }).then(function(switches) {
@@ -283,25 +314,25 @@ L.ui.view.extend({
                        }
 
                        return m.insertInto('#map').then(function() {
                        }
 
                        return m.insertInto('#map').then(function() {
-                self.repeat(function() {
-                    return L.network.getSwitchStatus(swname).then(function(ports) {
-                        for (var j = 0; j < ports.length; j++)
-                        {
-                            var s = L.tr('No link');
-                            var d = '&#160;';
-
-                            if (ports[j].link)
-                            {
-                                s = '%dbaseT'.format(ports[j].speed);
-                                d = ports[j].full_duplex ? L.tr('Full-duplex') : L.tr('Half-duplex');
-                            }
-
-                            $('#portstatus-%s-%d'.format(swname, j))
-                                .empty().append(s + '<br />' + d);
-                        }
-                    });
-                }, 5000);
-            });
+                               self.repeat(function() {
+                                       return self.getSwitchStatus(swname).then(function(ports) {
+                                               for (var j = 0; j < ports.length; j++)
+                                               {
+                                                       var s = L.tr('No link');
+                                                       var d = '&#160;';
+
+                                                       if (ports[j].link)
+                                                       {
+                                                               s = '%dbaseT'.format(ports[j].speed);
+                                                               d = ports[j].full_duplex ? L.tr('Full-duplex') : L.tr('Half-duplex');
+                                                       }
+
+                                                       $('#portstatus-%s-%d'.format(swname, j))
+                                                               .empty().append(s + '<br />' + d);
+                                               }
+                                       });
+                               }, 5000);
+                       });
                });
        }
 });
                });
        }
 });
index bcb35d0..de03973 100644 (file)
@@ -1,13 +1,20 @@
 L.ui.view.extend({
 L.ui.view.extend({
-    title: L.tr('Kernel Log'),
-    refresh: 5000,
-    execute: function() {
-        return L.system.getKernelLog().then(function(log) {
-            var ta = document.getElementById('syslog');
-            var lines = log.replace(/\n+$/, '').split(/\n/);
+       title: L.tr('Kernel Log'),
+       refresh: 5000,
 
 
-            ta.rows = lines.length;
-            ta.value = lines.reverse().join("\n");
-        });
-    }
+       getKernelLog: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'dmesg',
+               expect: { log: '' }
+       }),
+
+       execute: function() {
+               return this.getKernelLog().then(function(log) {
+                       var ta = document.getElementById('syslog');
+                       var lines = log.replace(/\n+$/, '').split(/\n/);
+
+                       ta.rows = lines.length;
+                       ta.value = lines.reverse().join("\n");
+               });
+       }
 });
 });
index 7440f8c..909c4c1 100644 (file)
@@ -1,10 +1,30 @@
 L.ui.view.extend({
        title: L.tr('Status'),
 L.ui.view.extend({
        title: L.tr('Status'),
-       execute: function() {
+
+       getConntrackCount: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'conntrack_count',
+               expect: { '': { count: 0, limit: 0 } }
+       }),
+
+       getDHCPLeases: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'dhcp_leases',
+               expect: { leases: [ ] }
+       }),
+
+       getDHCPv6Leases: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'dhcp6_leases',
+               expect: { leases: [ ] }
+       }),
+
+       renderContents: function() {
+               var self = this;
                return $.when(
                return $.when(
-                       L.network.findWanInterfaces().then(function(wans) {
-                               var wan  = wans[0];
-                               var wan6 = wans[1];
+                       L.NetworkModel.refreshStatus().then(function() {
+                               var wan  = L.NetworkModel.findWAN();
+                               var wan6 = L.NetworkModel.findWAN6();
 
                                if (!wan && !wan6)
                                {
 
                                if (!wan && !wan6)
                                {
@@ -21,57 +41,36 @@ L.ui.view.extend({
                                                width:  '146px',
                                                align:  'right',
                                                format: function(v) {
                                                width:  '146px',
                                                align:  'right',
                                                format: function(v) {
-                                                       return new L.ui.devicebadge(v).render();
+                                                       var dev = L.NetworkModel.resolveAlias(v.getDevice());
+                                                       if (dev)
+                                                               return $('<span />')
+                                                                       .addClass('badge')
+                                                                       .attr('title', dev.description())
+                                                                       .append($('<img />').attr('src', dev.icon()))
+                                                                       .append(' %s'.format(dev.name()));
+
+                                                       return '';
                                                }
                                        }, {
                                                format: function(v, n) {
                                                }
                                        }, {
                                                format: function(v, n) {
-                                                       var format_addr = function()
-                                                       {
-                                                               var rv = [ ];
-                                                               if (n > 0)
-                                                               {
-                                                                       for (var i = 0; i < v['ipv6-address'].length; i++)
-                                                                               rv.push('%s/%d'.format(v['ipv6-address'][i].address, v['ipv6-address'][i].mask));
-
-                                                                       for (var i = 0; i < v['ipv6-prefix-assignment'].length; i++)
-                                                                               rv.push('%s1/%d'.format(v['ipv6-prefix-assignment'][i].address, v['ipv6-prefix-assignment'][i].mask));
-                                                               }
-                                                               else
-                                                               {
-                                                                       for (var i = 0; i < v['ipv4-address'].length; i++)
-                                                                               rv.push('%s/%d'.format(v['ipv4-address'][i].address, v['ipv4-address'][i].mask));
-                                                               }
-                                                               return rv.join(', ');
-                                                       };
-
-                                                       var format_dns = function()
-                                                       {
-                                                               var rv = [ ];
-                                                               for (var i = 0; i < v['dns-server'].length; i++)
-                                                               {
-                                                                       if ((n > 0 && v['dns-server'][i].indexOf(':') > -1) ||
-                                                                           (n == 0 && v['dns-server'][i].indexOf(':') == -1))
-                                                                               rv.push(v['dns-server'][i]);
-                                                               }
-                                                               return rv.join(', ');
-                                                       };
-
                                                        var s = '<strong>' + L.tr('Type') + ':</strong> %s | ' +
                                                        var s = '<strong>' + L.tr('Type') + ':</strong> %s | ' +
-                                                               '<strong>' + L.tr('Connected') + ':</strong> %t<br />' +
-                                                               '<strong>' + L.tr('Address') + ':</strong> %s<br />';
+                                                               '<strong>' + L.tr('Connected') + ':</strong> %t<br />';
 
 
-                                                       s = s.format(v.proto, v.uptime, format_addr());
+                                                       s = s.format(v.getProtocol().description, v.getUptime(),
+                                                                    n ? v.getIPv6Addrs(true).join(', ')
+                                                                      : v.getIPv4Addrs(true).join(', '));
 
 
-                                                       for (var i = 0; i < v.route.length; i++)
-                                                               if (v.route[i].mask == 0 && v.route[i].nexthop != '::')
-                                                               {
-                                                                       s += '<strong>' + L.tr('Gateway') + ':</strong> %s<br />'.format(v.route[i].nexthop);
-                                                                       break;
-                                                               }
+                                                       var addr = n ? v.getIPv6Addrs() : v.getIPv4Addrs();
+                                                       if (addr.length)
+                                                               s += '<strong>' + L.tr('Address') + ':</strong> %s<br />'.format(addr.join(', '));
+
+                                                       var gw = v.getIPv4Gateway();
+                                                       if (gw)
+                                                               s += '<strong>' + L.tr('Gateway') + ':</strong> %s<br />'.format(gw);
 
 
-                                                       var dns = format_dns();
-                                                       if (dns)
-                                                               s += '<strong>' + L.tr('DNS') + ':</strong> %s<br />'.format(dns);
+                                                       var dns = n ? v.getIPv6DNS() : v.getIPv4DNS();
+                                                       if (dns.length)
+                                                               s += '<strong>' + L.tr('DNS') + ':</strong> %s<br />'.format(dns.join(', '));
 
                                                        return s;
                                                }
 
                                                        return s;
                                                }
@@ -86,7 +85,7 @@ L.ui.view.extend({
 
                                networkTable.insertInto('#network_status_table');
                        }),
 
                                networkTable.insertInto('#network_status_table');
                        }),
-                       L.network.getConntrackCount().then(function(count) {
+                       self.getConntrackCount().then(function(count) {
                                var conntrackTable = new L.ui.table({
                                        caption: L.tr('Connection Tracking'),
                                        columns: [ {
                                var conntrackTable = new L.ui.table({
                                        caption: L.tr('Connection Tracking'),
                                        columns: [ {
@@ -352,7 +351,7 @@ L.ui.view.extend({
                                assocTable.rows(assoclist);
                                assocTable.insertInto('#wifi_assoc_table');
                        }),
                                assocTable.rows(assoclist);
                                assocTable.insertInto('#wifi_assoc_table');
                        }),
-                       L.network.getDHCPLeases().then(function(leases) {
+                       self.getDHCPLeases().then(function(leases) {
                                var leaseTable = new L.ui.table({
                                        caption:     L.tr('DHCP Leases'),
                                        placeholder: L.tr('There are no active leases.'),
                                var leaseTable = new L.ui.table({
                                        caption:     L.tr('DHCP Leases'),
                                        placeholder: L.tr('There are no active leases.'),
@@ -378,7 +377,7 @@ L.ui.view.extend({
                                leaseTable.rows(leases);
                                leaseTable.insertInto('#lease_status_table');
                        }),
                                leaseTable.rows(leases);
                                leaseTable.insertInto('#lease_status_table');
                        }),
-                       L.network.getDHCPv6Leases().then(function(leases) {
+                       self.getDHCPv6Leases().then(function(leases) {
                                if (!leases.length)
                                        return;
 
                                if (!leases.length)
                                        return;
 
@@ -407,5 +406,13 @@ L.ui.view.extend({
                                leaseTable.insertInto('#lease6_status_table');
                        })
                )
                                leaseTable.insertInto('#lease6_status_table');
                        })
                )
+       },
+
+       execute: function()
+       {
+               var self = this;
+        return L.NetworkModel.init().then(function() {
+                       self.repeat(self.renderContents, 5000);
+        });
        }
 });
        }
 });
index 9c80d89..b58df8a 100644 (file)
@@ -1,59 +1,80 @@
 L.ui.view.extend({
 L.ui.view.extend({
-    title: L.tr('Processes'),
-    description: L.tr('This list gives an overview over currently running system processes and their status.'),
-    execute: function() {
-        var allow_signals = this.options.acls.status;
-        return L.system.getProcessList().then(function(list) {
-            var procTable = new L.ui.table({
-                columns: [ {
-                    caption: L.tr('PID'),
-                    key:     'pid'
-                }, {
-                    caption: L.tr('Owner'),
-                    key:     'user'
-                }, {
-                    caption: L.tr('Command'),
-                    key:     'command'
-                }, {
-                    caption: L.tr('CPU usage (%)'),
-                    key:     'cpu_percent',
-                    format:  '%d%%'
-                }, {
-                    caption: L.tr('Memory usage (%)'),
-                    key:     'vsize_percent',
-                    format:  '%d%%'
-                }, {
-                    key:    'pid',
-                    format: function(v, n) {
-                        return $('<div />')
-                            .addClass('btn-group')
-                            .append($('<button />')
-                                .addClass('btn btn-primary btn-sm dropdown-toggle')
-                                .attr('data-toggle', 'dropdown')
-                                .text(L.tr('Signal…')))
-                            .append($('<ul />')
-                                .addClass('dropdown-menu pull-right')
-                                .append($('<li />')
-                                    .append($('<a />')
-                                        .attr('href', '#')
-                                        .html('%s (<code>%s</code>)'.format(L.trc('UNIX signal', 'Reload'), 'HUP'))
-                                        .click(function(ev) { L.system.sendSignal(v, 1).then(status); ev.preventDefault(); })))
-                                .append($('<li />')
-                                    .append($('<a />')
-                                        .attr('href', '#')
-                                        .html('%s (<code>%s</code>)'.format(L.trc('UNIX signal', 'Terminate'), 'TERM'))
-                                        .click(function(ev) { L.system.sendSignal(v, 15).then(status); ev.preventDefault(); })))
-                                .append($('<li />')
-                                    .append($('<a />')
-                                        .attr('href', '#')
-                                        .html('%s (<code>%s</code>)'.format(L.trc('UNIX signal', 'Kill immediately'), 'KILL'))
-                                        .click(function(ev) { L.system.sendSignal(v, 9).then(status); ev.preventDefault(); }))))
-                    }
-                } ]
-            });
+       title: L.tr('Processes'),
+       description: L.tr('This list gives an overview over currently running system processes and their status.'),
 
 
-            procTable.rows(list);
-            procTable.insertInto('#process_table');
-        });
-    }
+       getProcessList: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'process_list',
+               expect: { processes: [ ] },
+               filter: function(data) {
+                       data.sort(function(a, b) { return a.pid - b.pid });
+                       return data;
+               }
+       }),
+
+       sendSignal: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'process_signal',
+               params: [ 'pid', 'signal' ],
+               filter: function(data) {
+                       return (data == 0);
+               }
+       }),
+
+       execute: function() {
+               var self = this;
+               var allow_signals = this.options.acls.status;
+               return self.getProcessList().then(function(list) {
+                       var procTable = new L.ui.table({
+                               columns: [ {
+                                       caption: L.tr('PID'),
+                                       key:     'pid'
+                               }, {
+                                       caption: L.tr('Owner'),
+                                       key:     'user'
+                               }, {
+                                       caption: L.tr('Command'),
+                                       key:     'command'
+                               }, {
+                                       caption: L.tr('CPU usage (%)'),
+                                       key:     'cpu_percent',
+                                       format:  '%d%%'
+                               }, {
+                                       caption: L.tr('Memory usage (%)'),
+                                       key:     'vsize_percent',
+                                       format:  '%d%%'
+                               }, {
+                                       key:    'pid',
+                                       format: function(v, n) {
+                                               return $('<div />')
+                                                       .addClass('btn-group')
+                                                       .append($('<button />')
+                                                               .addClass('btn btn-primary btn-sm dropdown-toggle')
+                                                               .attr('data-toggle', 'dropdown')
+                                                               .text(L.tr('Signal…')))
+                                                       .append($('<ul />')
+                                                               .addClass('dropdown-menu pull-right')
+                                                               .append($('<li />')
+                                                                       .append($('<a />')
+                                                                               .attr('href', '#')
+                                                                               .html('%s (<code>%s</code>)'.format(L.trc('UNIX signal', 'Reload'), 'HUP'))
+                                                                               .click(function(ev) { self.sendSignal(v, 1).then(status); ev.preventDefault(); })))
+                                                               .append($('<li />')
+                                                                       .append($('<a />')
+                                                                               .attr('href', '#')
+                                                                               .html('%s (<code>%s</code>)'.format(L.trc('UNIX signal', 'Terminate'), 'TERM'))
+                                                                               .click(function(ev) { self.sendSignal(v, 15).then(status); ev.preventDefault(); })))
+                                                               .append($('<li />')
+                                                                       .append($('<a />')
+                                                                               .attr('href', '#')
+                                                                               .html('%s (<code>%s</code>)'.format(L.trc('UNIX signal', 'Kill immediately'), 'KILL'))
+                                                                               .click(function(ev) { self.sendSignal(v, 9).then(status); ev.preventDefault(); }))))
+                                       }
+                               } ]
+                       });
+
+                       procTable.rows(list);
+                       procTable.insertInto('#process_table');
+               });
+       }
 });
 });
index 249acdf..a73cf82 100644 (file)
@@ -1,9 +1,29 @@
 L.ui.view.extend({
        title: L.tr('Routes'),
        description: L.tr('The following rules are currently active on this system.'),
 L.ui.view.extend({
        title: L.tr('Routes'),
        description: L.tr('The following rules are currently active on this system.'),
+
+       getRoutes: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'routes',
+               expect: { routes: [ ] }
+       }),
+
+       getIPv6Routes: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'routes',
+               expect: { routes: [ ] }
+       }),
+
+       getARPTable: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'arp_table',
+               expect: { entries: [ ] }
+       }),
+
        execute: function() {
        execute: function() {
+               var self = this;
                return $.when(
                return $.when(
-                       L.network.getARPTable().then(function(arp) {
+                       self.getARPTable().then(function(arp) {
                                var arpTable = new L.ui.table({
                                        caption: L.tr('ARP'),
                                        columns: [{
                                var arpTable = new L.ui.table({
                                        caption: L.tr('ARP'),
                                        columns: [{
@@ -21,7 +41,7 @@ L.ui.view.extend({
                                arpTable.rows(arp);
                                arpTable.insertInto('#arp_table');
                        }),
                                arpTable.rows(arp);
                                arpTable.insertInto('#arp_table');
                        }),
-                       L.network.getRoutes().then(function(routes) {
+                       self.getRoutes().then(function(routes) {
                                var routeTable = new L.ui.table({
                                        caption: L.tr('Active IPv4-Routes'),
                                        columns: [{
                                var routeTable = new L.ui.table({
                                        caption: L.tr('Active IPv4-Routes'),
                                        columns: [{
@@ -42,7 +62,7 @@ L.ui.view.extend({
                                routeTable.rows(routes);
                                routeTable.insertInto('#route_table');
                        }),
                                routeTable.rows(routes);
                                routeTable.insertInto('#route_table');
                        }),
-                       L.network.getIPv6Routes().then(function(routes) {
+                       self.getIPv6Routes().then(function(routes) {
                                var route6Table = new L.ui.table({
                                        caption: L.tr('Active IPv6-Routes'),
                                        columns: [{
                                var route6Table = new L.ui.table({
                                        caption: L.tr('Active IPv6-Routes'),
                                        columns: [{
index e57db2c..87b3386 100644 (file)
@@ -1,13 +1,20 @@
 L.ui.view.extend({
 L.ui.view.extend({
-    title: L.tr('System Log'),
-    refresh: 5000,
-    execute: function() {
-        return L.system.getSystemLog().then(function(log) {
-            var ta = document.getElementById('syslog');
-            var lines = log.replace(/\n+$/, '').split(/\n/);
+       title: L.tr('System Log'),
+       refresh: 5000,
 
 
-            ta.rows = lines.length;
-            ta.value = lines.reverse().join("\n");
-        });
-    }
+       getSystemLog: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'syslog',
+               expect: { log: '' }
+       }),
+
+       execute: function() {
+               return this.getSystemLog().then(function(log) {
+                       var ta = document.getElementById('syslog');
+                       var lines = log.replace(/\n+$/, '').split(/\n/);
+
+                       ta.rows = lines.length;
+                       ta.value = lines.reverse().join("\n");
+               });
+       }
 });
 });
index c10cb3c..8ecc95f 100644 (file)
 L.ui.view.extend({
 L.ui.view.extend({
-    PubkeyListValue: L.cbi.AbstractValue.extend({
-        base64Table: {
-            'A':  0, 'B':  1, 'C':  2, 'D':  3, 'E':  4, 'F':  5, 'G':  6,
-            'H':  7, 'I':  8, 'J':  9, 'K': 10, 'L': 11, 'M': 12, 'N': 13,
-            'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20,
-            'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27,
-            'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34,
-            'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41,
-            'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48,
-            'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55,
-            '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62,
-            '/': 63, '=': 64
-        },
-
-        base64Decode: function(s)
-        {
-            var i = 0;
-            var d = '';
+       PubkeyListValue: L.cbi.AbstractValue.extend({
+               base64Table: {
+                       'A':  0, 'B':  1, 'C':  2, 'D':  3, 'E':  4, 'F':  5, 'G':  6,
+                       'H':  7, 'I':  8, 'J':  9, 'K': 10, 'L': 11, 'M': 12, 'N': 13,
+                       'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20,
+                       'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27,
+                       'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34,
+                       'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41,
+                       'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48,
+                       'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55,
+                       '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62,
+                       '/': 63, '=': 64
+               },
+
+               base64Decode: function(s)
+               {
+                       var i = 0;
+                       var d = '';
 
 
-            if (s.match(/[^A-Za-z0-9\+\/\=]/))
-                return undefined;
-
-            while (i < s.length)
-            {
-                var e1 = this.base64Table[s.charAt(i++)];
-                var e2 = this.base64Table[s.charAt(i++)];
-                var e3 = this.base64Table[s.charAt(i++)];
-                var e4 = this.base64Table[s.charAt(i++)];
-
-                var c1 = ( e1       << 2) | (e2 >> 4);
-                var c2 = ((e2 & 15) << 4) | (e3 >> 2);
-                var c3 = ((e3 &  3) << 6) |  e4;
-
-                d += String.fromCharCode(c1);
-
-                if (e3 < 64)
-                    d += String.fromCharCode(c2);
-
-                if (e4 < 64)
-                    d += String.fromCharCode(c3);
-            }
-
-            return d;
-        },
-
-        lengthDecode: function(s, off)
-        {
-            var l = (s.charCodeAt(off++) << 24) |
-                    (s.charCodeAt(off++) << 16) |
-                    (s.charCodeAt(off++) <<  8) |
-                     s.charCodeAt(off++);
-
-            if (l < 0 || (off + l) > s.length)
-                return -1;
-
-            return l;
-        },
-
-        pubkeyDecode: function(s)
-        {
-            var parts = s.split(/\s+/);
-            if (parts.length < 2)
-                return undefined;
-
-            var key = this.base64Decode(parts[1]);
-            if (!key)
-                return undefined;
-
-            var off, len;
-
-            off = 0;
-            len = this.lengthDecode(key, off);
-
-            if (len < 0)
-                return undefined;
-
-            var type = key.substr(off + 4, len);
-            if (type != parts[0])
-                return undefined;
-
-            off += 4 + len;
-
-            var len1 = this.lengthDecode(key, off);
-            if (len1 < 0)
-                return undefined;
-
-            off += 4 + len1;
-
-            var len2 = this.lengthDecode(key, off);
-            if (len2 < 0)
-                return undefined;
-
-            if (len1 & 1)
-                len1--;
-
-            if (len2 & 1)
-                len2--;
-
-            switch (type)
-            {
-            case 'ssh-rsa':
-                return { type: 'RSA', bits: len2 * 8, comment: parts[2] };
-
-            case 'ssh-dss':
-                return { type: 'DSA', bits: len1 * 8, comment: parts[2] };
-
-            default:
-                return undefined;
-            }
-        },
-
-        _remove: function(ev)
-        {
-            var self = ev.data.self;
-
-            self._keys.splice(ev.data.index, 1);
-            self._render(ev.data.div);
-        },
-
-        _add: function(ev)
-        {
-            var self = ev.data.self;
-
-            var form = $('<div />')
-                .append($('<p />')
-                    .text(L.tr('Paste the public key line into the field below and press "%s" to continue.').format(L.tr('Ok'))))
-                .append($('<p />')
-                    .text(L.tr('Unrecognized public key! Please add only RSA or DSA keys.'))
-                    .addClass('alert alert-danger')
-                    .hide())
-                .append($('<p />')
-                    .append($('<input />')
-                        .attr('type', 'text')
-                        .attr('placeholder', L.tr('Paste key here'))
-                        .addClass('form-control')));
-
-            L.ui.dialog(L.tr('Add new public key'), form, {
-                style: 'confirm',
-                confirm: function() {
-                    var val = form.find('input').val();
-                    if (!val)
-                    {
-                        return;
-                    }
-
-                    var key = self.pubkeyDecode(val);
-                    if (!key)
-                    {
-                        form.find('input').val('');
-                        form.find('.alert').show();
-                        return;
-                    }
-
-                    self._keys.push(val);
-                    self._render(ev.data.div);
-
-                    L.ui.dialog(false);
-                }
-            });
-        },
-
-        _show: function(ev)
-        {
-            var self = ev.data.self;
-
-            L.ui.dialog(
-                L.tr('Public key'),
-                $('<pre />').text(self._keys[ev.data.index]),
-                { style: 'close' }
-            );
-        },
-
-        _render: function(div)
-        {
-            div.empty();
-
-            for (var i = 0; i < this._keys.length; i++)
-            {
-                var k = this.pubkeyDecode(this._keys[i] || '');
-
-                if (!k)
-                    continue;
-
-                $('<div />')
-                    .addClass('input-group')
-                    .append($('<input />')
-                        .addClass('form-control')
-                        .attr('type', 'text')
-                        .prop('readonly', true)
-                        .click({ self: this, index: i }, this._show)
-                        .val('%dBit %s - %s'.format(k.bits, k.type, k.comment || '?')))
-                    .append($('<span />')
-                        .addClass('input-group-btn')
-                        .append($('<button />')
-                            .addClass('btn btn-danger')
-                            .attr('title', L.tr('Remove public key'))
-                            .text('–')
-                            .click({ self: this, div: div, index: i }, this._remove)))
-                    .appendTo(div);
-            }
-
-            if (this._keys.length > 0)
-                $('<br />').appendTo(div);
-
-            L.ui.button(L.tr('Add public key …'), 'success')
-                .click({ self: this, div: div }, this._add)
-                .appendTo(div);
-        },
-
-        widget: function(sid)
-        {
-            this._keys = [ ];
-
-            for (var i = 0; i < this.options.keys.length; i++)
-                this._keys.push(this.options.keys[i]);
-
-            var d = $('<div />')
-                .attr('id', this.id(sid));
-
-            this._render(d);
-
-            return d;
-        },
-
-        changed: function(sid)
-        {
-            if (this.options.keys.length != this._keys.length)
-                return true;
-
-            for (var i = 0; i < this.options.keys.length; i++)
-                if (this.options.keys[i] != this._keys[i])
-                    return true;
-
-            return false;
-        },
-
-        save: function(sid)
-        {
-            if (this.changed(sid))
-            {
-                this.options.keys = [ ];
-
-                for (var i = 0; i < this._keys.length; i++)
-                    this.options.keys.push(this._keys[i]);
-
-                return L.system.setSSHKeys(this._keys);
-            }
-
-            return undefined;
-        }
-    }),
-
-    execute: function() {
-        var self = this;
-        return L.system.getSSHKeys().then(function(keys) {
-            var m = new L.cbi.Map('dropbear', {
-                caption:     L.tr('SSH Access'),
-                description: L.tr('Dropbear offers SSH network shell access and an integrated SCP server'),
-                tabbed:      true
-            });
-
-            var s1 = m.section(L.cbi.DummySection, '__password', {
-                caption:     L.tr('Router Password'),
-                description: L.tr('Changes the administrator password for accessing the device'),
-                readonly:    !self.options.acls.admin
-            });
-
-            var p1 = s1.option(L.cbi.PasswordValue, 'pass1', {
-                caption:     L.tr('Password'),
-                optional:    true
-            });
-
-            var p2 = s1.option(L.cbi.PasswordValue, 'pass2', {
-                caption:     L.tr('Confirmation'),
-                optional:    true,
-                datatype:    function(v) {
-                    var v1 = p1.formvalue('__password');
-                    if (v1 && v1.length && v != v1)
-                        return L.tr('Passwords must match!');
-                    return true;
-                }
-            });
-
-            p1.save = function(sid) { };
-            p2.save = function(sid) {
-                var v1 = p1.formvalue(sid);
-                var v2 = p2.formvalue(sid);
-                if (v2 && v2.length > 0 && v1 == v2)
-                    return L.system.setPassword('root', v2);
-            };
-
-
-            var s2 = m.section(L.cbi.DummySection, '__pubkeys', {
-                caption:     L.tr('SSH-Keys'),
-                description: L.tr('Specifies public keys for passwordless SSH authentication'),
-                readonly:    !self.options.acls.admin
-            });
-
-            var k = s2.option(self.PubkeyListValue, 'keys', {
-                caption:     L.tr('Saved keys'),
-                keys:        keys
-            });
-
-
-            var s3 = m.section(L.cbi.TypedSection, 'dropbear', {
-                caption:     L.tr('SSH Server'),
-                description: L.tr('This sections define listening instances of the builtin Dropbear SSH server'),
-                addremove:   true,
-                add_caption: L.tr('Add instance ...'),
-                readonly:    !self.options.acls.admin,
-                collabsible: true
-            });
-
-            s3.option(L.cbi.NetworkList, 'Interface', {
-                caption:     L.tr('Interface'),
-                description: L.tr('Listen only on the given interface or, if unspecified, on all')
-            });
-
-            s3.option(L.cbi.InputValue, 'Port', {
-                caption:     L.tr('Port'),
-                description: L.tr('Specifies the listening port of this Dropbear instance'),
-                datatype:    'port',
-                placeholder: 22,
-                optional:    true
-            });
-
-            s3.option(L.cbi.CheckboxValue, 'PasswordAuth', {
-                caption:     L.tr('Password authentication'),
-                description: L.tr('Allow SSH password authentication'),
-                initial:     true,
-                enabled:     'on',
-                disabled:    'off'
-            });
-
-            s3.option(L.cbi.CheckboxValue, 'RootPasswordAuth', {
-                caption:     L.tr('Allow root logins with password'),
-                description: L.tr('Allow the root user to login with password'),
-                initial:     true,
-                enabled:     'on',
-                disabled:    'off'
-            });
-
-            s3.option(L.cbi.CheckboxValue, 'GatewayPorts', {
-                caption:     L.tr('Gateway ports'),
-                description: L.tr('Allow remote hosts to connect to local SSH forwarded ports'),
-                initial:     false,
-                enabled:     'on',
-                disabled:    'off'
-            });
-
-            return m.insertInto('#map');
-        });
-    }
+                       if (s.match(/[^A-Za-z0-9\+\/\=]/))
+                               return undefined;
+
+                       while (i < s.length)
+                       {
+                               var e1 = this.base64Table[s.charAt(i++)];
+                               var e2 = this.base64Table[s.charAt(i++)];
+                               var e3 = this.base64Table[s.charAt(i++)];
+                               var e4 = this.base64Table[s.charAt(i++)];
+
+                               var c1 = ( e1       << 2) | (e2 >> 4);
+                               var c2 = ((e2 & 15) << 4) | (e3 >> 2);
+                               var c3 = ((e3 &  3) << 6) |  e4;
+
+                               d += String.fromCharCode(c1);
+
+                               if (e3 < 64)
+                                       d += String.fromCharCode(c2);
+
+                               if (e4 < 64)
+                                       d += String.fromCharCode(c3);
+                       }
+
+                       return d;
+               },
+
+               lengthDecode: function(s, off)
+               {
+                       var l = (s.charCodeAt(off++) << 24) |
+                                       (s.charCodeAt(off++) << 16) |
+                                       (s.charCodeAt(off++) <<  8) |
+                                        s.charCodeAt(off++);
+
+                       if (l < 0 || (off + l) > s.length)
+                               return -1;
+
+                       return l;
+               },
+
+               pubkeyDecode: function(s)
+               {
+                       var parts = s.split(/\s+/);
+                       if (parts.length < 2)
+                               return undefined;
+
+                       var key = this.base64Decode(parts[1]);
+                       if (!key)
+                               return undefined;
+
+                       var off, len;
+
+                       off = 0;
+                       len = this.lengthDecode(key, off);
+
+                       if (len < 0)
+                               return undefined;
+
+                       var type = key.substr(off + 4, len);
+                       if (type != parts[0])
+                               return undefined;
+
+                       off += 4 + len;
+
+                       var len1 = this.lengthDecode(key, off);
+                       if (len1 < 0)
+                               return undefined;
+
+                       off += 4 + len1;
+
+                       var len2 = this.lengthDecode(key, off);
+                       if (len2 < 0)
+                               return undefined;
+
+                       if (len1 & 1)
+                               len1--;
+
+                       if (len2 & 1)
+                               len2--;
+
+                       switch (type)
+                       {
+                       case 'ssh-rsa':
+                               return { type: 'RSA', bits: len2 * 8, comment: parts[2] };
+
+                       case 'ssh-dss':
+                               return { type: 'DSA', bits: len1 * 8, comment: parts[2] };
+
+                       default:
+                               return undefined;
+                       }
+               },
+
+               _remove: function(ev)
+               {
+                       var self = ev.data.self;
+
+                       self._keys.splice(ev.data.index, 1);
+                       self._render(ev.data.div);
+               },
+
+               _add: function(ev)
+               {
+                       var self = ev.data.self;
+
+                       var form = $('<div />')
+                               .append($('<p />')
+                                       .text(L.tr('Paste the public key line into the field below and press "%s" to continue.').format(L.tr('Ok'))))
+                               .append($('<p />')
+                                       .text(L.tr('Unrecognized public key! Please add only RSA or DSA keys.'))
+                                       .addClass('alert alert-danger')
+                                       .hide())
+                               .append($('<p />')
+                                       .append($('<input />')
+                                               .attr('type', 'text')
+                                               .attr('placeholder', L.tr('Paste key here'))
+                                               .addClass('form-control')));
+
+                       L.ui.dialog(L.tr('Add new public key'), form, {
+                               style: 'confirm',
+                               confirm: function() {
+                                       var val = form.find('input').val();
+                                       if (!val)
+                                       {
+                                               return;
+                                       }
+
+                                       var key = self.pubkeyDecode(val);
+                                       if (!key)
+                                       {
+                                               form.find('input').val('');
+                                               form.find('.alert').show();
+                                               return;
+                                       }
+
+                                       self._keys.push(val);
+                                       self._render(ev.data.div);
+
+                                       L.ui.dialog(false);
+                               }
+                       });
+               },
+
+               _show: function(ev)
+               {
+                       var self = ev.data.self;
+
+                       L.ui.dialog(
+                               L.tr('Public key'),
+                               $('<pre />').text(self._keys[ev.data.index]),
+                               { style: 'close' }
+                       );
+               },
+
+               _render: function(div)
+               {
+                       div.empty();
+
+                       for (var i = 0; i < this._keys.length; i++)
+                       {
+                               var k = this.pubkeyDecode(this._keys[i] || '');
+
+                               if (!k)
+                                       continue;
+
+                               $('<div />')
+                                       .addClass('input-group')
+                                       .append($('<input />')
+                                               .addClass('form-control')
+                                               .attr('type', 'text')
+                                               .prop('readonly', true)
+                                               .click({ self: this, index: i }, this._show)
+                                               .val('%dBit %s - %s'.format(k.bits, k.type, k.comment || '?')))
+                                       .append($('<span />')
+                                               .addClass('input-group-btn')
+                                               .append($('<button />')
+                                                       .addClass('btn btn-danger')
+                                                       .attr('title', L.tr('Remove public key'))
+                                                       .text('–')
+                                                       .click({ self: this, div: div, index: i }, this._remove)))
+                                       .appendTo(div);
+                       }
+
+                       if (this._keys.length > 0)
+                               $('<br />').appendTo(div);
+
+                       L.ui.button(L.tr('Add public key …'), 'success')
+                               .click({ self: this, div: div }, this._add)
+                               .appendTo(div);
+               },
+
+               widget: function(sid)
+               {
+                       this._keys = [ ];
+
+                       for (var i = 0; i < this.options.keys.length; i++)
+                               this._keys.push(this.options.keys[i]);
+
+                       var d = $('<div />')
+                               .attr('id', this.id(sid));
+
+                       this._render(d);
+
+                       return d;
+               },
+
+               changed: function(sid)
+               {
+                       if (this.options.keys.length != this._keys.length)
+                               return true;
+
+                       for (var i = 0; i < this.options.keys.length; i++)
+                               if (this.options.keys[i] != this._keys[i])
+                                       return true;
+
+                       return false;
+               },
+
+               save: function(sid)
+               {
+                       if (this.changed(sid))
+                       {
+                               this.options.keys = [ ];
+
+                               for (var i = 0; i < this._keys.length; i++)
+                                       this.options.keys.push(this._keys[i]);
+
+                               return L.views.SystemAdmin.setSSHKeys(this._keys);
+                       }
+
+                       return undefined;
+               }
+       }),
+
+       getSSHKeys: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'sshkeys_get',
+               expect: { keys: [ ] }
+       }),
+
+       setSSHKeys: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'sshkeys_set',
+               params: [ 'keys' ]
+       }),
+
+       setPassword: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'password_set',
+               params: [ 'user', 'password' ]
+       }),
+
+       execute: function() {
+               var self = this;
+               return self.getSSHKeys().then(function(keys) {
+                       var m = new L.cbi.Map('dropbear', {
+                               caption:     L.tr('SSH Access'),
+                               description: L.tr('Dropbear offers SSH network shell access and an integrated SCP server'),
+                               tabbed:      true
+                       });
+
+                       var s1 = m.section(L.cbi.DummySection, '__password', {
+                               caption:     L.tr('Router Password'),
+                               description: L.tr('Changes the administrator password for accessing the device'),
+                               readonly:    !self.options.acls.admin
+                       });
+
+                       var p1 = s1.option(L.cbi.PasswordValue, 'pass1', {
+                               caption:     L.tr('Password'),
+                               optional:    true
+                       });
+
+                       var p2 = s1.option(L.cbi.PasswordValue, 'pass2', {
+                               caption:     L.tr('Confirmation'),
+                               optional:    true,
+                               datatype:    function(v) {
+                                       var v1 = p1.formvalue('__password');
+                                       if (v1 && v1.length && v != v1)
+                                               return L.tr('Passwords must match!');
+                                       return true;
+                               }
+                       });
+
+                       p1.save = function(sid) { };
+                       p2.save = function(sid) {
+                               var v1 = p1.formvalue(sid);
+                               var v2 = p2.formvalue(sid);
+                               if (v2 && v2.length > 0 && v1 == v2)
+                                       return L.system.setPassword('root', v2);
+                       };
+
+
+                       var s2 = m.section(L.cbi.DummySection, '__pubkeys', {
+                               caption:     L.tr('SSH-Keys'),
+                               description: L.tr('Specifies public keys for passwordless SSH authentication'),
+                               readonly:    !self.options.acls.admin
+                       });
+
+                       var k = s2.option(self.PubkeyListValue, 'keys', {
+                               caption:     L.tr('Saved keys'),
+                               keys:        keys
+                       });
+
+
+                       var s3 = m.section(L.cbi.TypedSection, 'dropbear', {
+                               caption:     L.tr('SSH Server'),
+                               description: L.tr('This sections define listening instances of the builtin Dropbear SSH server'),
+                               addremove:   true,
+                               add_caption: L.tr('Add instance ...'),
+                               readonly:    !self.options.acls.admin,
+                               collabsible: true
+                       });
+
+                       s3.option(L.cbi.NetworkList, 'Interface', {
+                               caption:     L.tr('Interface'),
+                               description: L.tr('Listen only on the given interface or, if unspecified, on all')
+                       });
+
+                       s3.option(L.cbi.InputValue, 'Port', {
+                               caption:     L.tr('Port'),
+                               description: L.tr('Specifies the listening port of this Dropbear instance'),
+                               datatype:    'port',
+                               placeholder: 22,
+                               optional:    true
+                       });
+
+                       s3.option(L.cbi.CheckboxValue, 'PasswordAuth', {
+                               caption:     L.tr('Password authentication'),
+                               description: L.tr('Allow SSH password authentication'),
+                               initial:     true,
+                               enabled:     'on',
+                               disabled:    'off'
+                       });
+
+                       s3.option(L.cbi.CheckboxValue, 'RootPasswordAuth', {
+                               caption:     L.tr('Allow root logins with password'),
+                               description: L.tr('Allow the root user to login with password'),
+                               initial:     true,
+                               enabled:     'on',
+                               disabled:    'off'
+                       });
+
+                       s3.option(L.cbi.CheckboxValue, 'GatewayPorts', {
+                               caption:     L.tr('Gateway ports'),
+                               description: L.tr('Allow remote hosts to connect to local SSH forwarded ports'),
+                               initial:     false,
+                               enabled:     'on',
+                               disabled:    'off'
+                       });
+
+                       return m.insertInto('#map');
+               });
+       }
 });
 });
index aae4ce4..c663e01 100644 (file)
@@ -1,19 +1,33 @@
 L.ui.view.extend({
        title: L.tr('Scheduled Tasks'),
        description: L.tr('This is the system crontab in which scheduled tasks can be defined.'),
 L.ui.view.extend({
        title: L.tr('Scheduled Tasks'),
        description: L.tr('This is the system crontab in which scheduled tasks can be defined.'),
-    execute: function() {
-        var allow_write = this.options.acls.cron;
 
 
-        return L.system.getCrontab().then(function(data) {
+       getCrontab: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'crontab_get',
+               expect: { data: '' }
+       }),
+
+       setCrontab: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'crontab_set',
+               params: [ 'data' ]
+       }),
+
+       execute: function() {
+               var self = this;
+               var allow_write = this.options.acls.cron;
+
+               return self.getCrontab().then(function(data) {
                        $('textarea').val(data).attr('disabled', !allow_write);
                        $('input.cbi-button-save').attr('disabled', !allow_write).click(function() {
                                var data = ($('textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
                                L.ui.loading(true);
                        $('textarea').val(data).attr('disabled', !allow_write);
                        $('input.cbi-button-save').attr('disabled', !allow_write).click(function() {
                                var data = ($('textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
                                L.ui.loading(true);
-                               L.system.setCrontab(data).then(function() {
+                               self.setCrontab(data).then(function() {
                                        $('textarea').val(data);
                                        L.ui.loading(false);
                                });
                        });
                });
                                        $('textarea').val(data);
                                        L.ui.loading(false);
                                });
                        });
                });
-    }
+       }
 });
 });
index c19462d..8165daf 100644 (file)
 L.ui.view.extend({
 L.ui.view.extend({
-    execute: function() {
-        var m = new L.cbi.Map('system', {
-            caption:     L.tr('LED Configuration'),
-            description: L.tr('Customizes the behaviour of the device LEDs if possible.'),
-            prepare: function() {
-                return $.when(
-                    L.system.listLEDs().then(function(leds) {
-                        delete m.sections[0].fields.sysfs.choices;
-                        delete m.sections[0].fields.trigger.choices;
-
-                        for (var i = 0; i < leds.length; i++)
-                            m.sections[0].fields.sysfs.value(leds[i].name);
-
-                        for (var i = 0; i < leds[0].triggers.length; i++)
-                            m.sections[0].fields.trigger.value(leds[0].triggers[i]);
-                    }),
-                    L.system.listUSBDevices().then(function(devs) {
-                        delete m.sections[0].fields._usb_dev.choices;
-
-                        for (var i = 0; i < devs.length; i++)
-                            m.sections[0].fields._usb_dev.value(devs[i].name,
-                                                                '%04x:%04x (%s - %s)'.format(devs[i].vendor_id, devs[i].product_id,
-                                                                                             devs[i].vendor_name || '?', devs[i].product_name || '?'));
-                    }),
-                    L.network.listDeviceNames().then(function(devices) {
-                        delete m.sections[0].fields._net_dev.choices;
-                        for (var i = 0; i < devices.length; i++)
-                            m.sections[0].fields._net_dev.value(devices[i]);
-                    })
-                );
-            }
-        });
-
-        var s = m.section(L.cbi.TypedSection, 'led', {
-            caption:     L.tr('LED Definitions'),
-            teasers:     [ 'name', 'sysfs', 'default', 'trigger', '_net_dev', 'mode', '_usb_dev', 'delayon', 'delayoff' ],
-            collabsible: true,
-            addremove:   true,
-            add_caption: L.tr('Add new LED defintion'),
-            remove_caption: L.tr('Remove LED definition'),
-            readonly:    !this.options.acls.leds
-        });
-
-        s.option(L.cbi.InputValue, 'name', {
-            caption:     L.tr('Name')
-        });
-
-        s.option(L.cbi.ListValue, 'sysfs', {
-            caption:     L.tr('LED Name')
-        });
-
-        s.option(L.cbi.ListValue, 'default', {
-            caption:     L.tr('Default state'),
-            initial:     '0'
-        }).value('0', L.trc('LED state', 'off')).value('1', L.trc('LED state', 'on'));
-
-        s.option(L.cbi.ListValue, 'trigger', {
-            caption:     L.tr('Trigger')
-        });
-
-
-        s.option(L.cbi.InputValue, 'delayon', {
-            caption:     L.trc('LED timer trigger', 'On-State Delay'),
-            description: L.trc('LED timer trigger', 'Time in milliseconds the LED stays on'),
-            datatype:    'uinteger'
-        }).depends('trigger', 'timer');
-
-        s.option(L.cbi.InputValue, 'delayoff', {
-            caption:     L.trc('LED timer trigger', 'Off-State Delay'),
-            description: L.trc('LED timer trigger', 'Time in milliseconds the LED stays off'),
-            datatype:    'uinteger'
-        }).depends('trigger', 'timer');
-
-
-        s.option(L.cbi.ListValue, '_net_dev', {
-            caption:     L.trc('LED netdev trigger', 'Device'),
-            uci_option:  'dev',
-            optional:    true
-        }).depends('trigger', 'netdev');
-
-        s.option(L.cbi.MultiValue, 'mode', {
-            caption:     L.trc('LED netdev trigger', 'Trigger Mode')
-        }).depends('trigger', 'netdev')
-            .value('link', L.trc('LED netdev trigger mode', 'Link On'))
-            .value('tx',   L.trc('LED netdev trigger mode', 'Transmit'))
-            .value('rx',   L.trc('LED netdev trigger mode', 'Receive'));
-
-
-        s.option(L.cbi.ListValue, '_usb_dev', {
-            caption:     L.trc('LED usbdev trigger', 'Device'),
-            uci_option:  'dev',
-            optional:    true
-        }).depends('trigger', 'usbdev');
-
-        return m.insertInto('#map');
-    }
+       listLEDs: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'led_list',
+               expect: { leds: [ ] }
+       }),
+
+       listUSBDevices: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'usb_list',
+               expect: { devices: [ ] }
+       }),
+
+       execute: function() {
+               var self = this;
+               var m = new L.cbi.Map('system', {
+                       caption:     L.tr('LED Configuration'),
+                       description: L.tr('Customizes the behaviour of the device LEDs if possible.'),
+                       prepare: function() {
+                               delete m.sections[0].fields._net_dev.choices;
+
+                               var devs = L.NetworkModel.getDevices().sort(function(a, b) {
+                                       if (a.name() < b.name())
+                                               return -1;
+                                       else if (a.name() > b.name())
+                                               return 1;
+                                       else
+                                               return 0;
+                               });
+
+                               for (var i = 0; i < devs.length; i++)
+                                       if (!devs[i].isAlias())
+                                               m.sections[0].fields._net_dev.value(devs[i].name());
+
+                               return $.when(
+                                       self.listLEDs().then(function(leds) {
+                                               delete m.sections[0].fields.sysfs.choices;
+                                               delete m.sections[0].fields.trigger.choices;
+
+                                               for (var i = 0; i < leds.length; i++)
+                                                       m.sections[0].fields.sysfs.value(leds[i].name);
+
+                                               for (var i = 0; i < leds[0].triggers.length; i++)
+                                                       m.sections[0].fields.trigger.value(leds[0].triggers[i]);
+                                       }),
+                                       self.listUSBDevices().then(function(devs) {
+                                               delete m.sections[0].fields._usb_dev.choices;
+
+                                               for (var i = 0; i < devs.length; i++)
+                                                       m.sections[0].fields._usb_dev.value(devs[i].name,
+                                                                                                                               '%04x:%04x (%s - %s)'.format(devs[i].vendor_id, devs[i].product_id,
+                                                                                                                                                                                        devs[i].vendor_name || '?', devs[i].product_name || '?'));
+                                       })
+                               );
+                       }
+               });
+
+               var s = m.section(L.cbi.TypedSection, 'led', {
+                       caption:     L.tr('LED Definitions'),
+                       teasers:     [ 'name', 'sysfs', 'default', 'trigger', '_net_dev', 'mode', '_usb_dev', 'delayon', 'delayoff' ],
+                       collabsible: true,
+                       addremove:   true,
+                       add_caption: L.tr('Add new LED defintion'),
+                       remove_caption: L.tr('Remove LED definition'),
+                       readonly:    !this.options.acls.leds
+               });
+
+               s.option(L.cbi.InputValue, 'name', {
+                       caption:     L.tr('Name')
+               });
+
+               s.option(L.cbi.ListValue, 'sysfs', {
+                       caption:     L.tr('LED Name')
+               });
+
+               s.option(L.cbi.ListValue, 'default', {
+                       caption:     L.tr('Default state'),
+                       initial:     '0'
+               }).value('0', L.trc('LED state', 'off')).value('1', L.trc('LED state', 'on'));
+
+               s.option(L.cbi.ListValue, 'trigger', {
+                       caption:     L.tr('Trigger')
+               });
+
+
+               s.option(L.cbi.InputValue, 'delayon', {
+                       caption:     L.trc('LED timer trigger', 'On-State Delay'),
+                       description: L.trc('LED timer trigger', 'Time in milliseconds the LED stays on'),
+                       datatype:    'uinteger'
+               }).depends('trigger', 'timer');
+
+               s.option(L.cbi.InputValue, 'delayoff', {
+                       caption:     L.trc('LED timer trigger', 'Off-State Delay'),
+                       description: L.trc('LED timer trigger', 'Time in milliseconds the LED stays off'),
+                       datatype:    'uinteger'
+               }).depends('trigger', 'timer');
+
+
+               s.option(L.cbi.ListValue, '_net_dev', {
+                       caption:     L.trc('LED netdev trigger', 'Device'),
+                       uci_option:  'dev',
+                       optional:    true
+               }).depends('trigger', 'netdev');
+
+               s.option(L.cbi.MultiValue, 'mode', {
+                       caption:     L.trc('LED netdev trigger', 'Trigger Mode')
+               }).depends('trigger', 'netdev')
+                       .value('link', L.trc('LED netdev trigger mode', 'Link On'))
+                       .value('tx',   L.trc('LED netdev trigger mode', 'Transmit'))
+                       .value('rx',   L.trc('LED netdev trigger mode', 'Receive'));
+
+
+               s.option(L.cbi.ListValue, '_usb_dev', {
+                       caption:     L.trc('LED usbdev trigger', 'Device'),
+                       uci_option:  'dev',
+                       optional:    true
+               }).depends('trigger', 'usbdev');
+
+               return m.insertInto('#map');
+       }
 });
 });
index 65a86ea..7aed31c 100644 (file)
@@ -1,6 +1,120 @@
 L.ui.view.extend({
        title: L.tr('Package management'),
 
 L.ui.view.extend({
        title: L.tr('Package management'),
 
+       opkg: {
+               updateLists: L.rpc.declare({
+                       object: 'luci2.opkg',
+                       method: 'update',
+                       expect: { '': { } }
+               }),
+
+               _allPackages: L.rpc.declare({
+                       object: 'luci2.opkg',
+                       method: 'list',
+                       params: [ 'offset', 'limit', 'pattern' ],
+                       expect: { '': { } }
+               }),
+
+               _installedPackages: L.rpc.declare({
+                       object: 'luci2.opkg',
+                       method: 'list_installed',
+                       params: [ 'offset', 'limit', 'pattern' ],
+                       expect: { '': { } }
+               }),
+
+               _findPackages: L.rpc.declare({
+                       object: 'luci2.opkg',
+                       method: 'find',
+                       params: [ 'offset', 'limit', 'pattern' ],
+                       expect: { '': { } }
+               }),
+
+               _fetchPackages: function(action, offset, limit, pattern)
+               {
+                       var packages = [ ];
+
+                       return action(offset, limit, pattern).then(function(list) {
+                               if (!list.total || !list.packages)
+                                       return { length: 0, total: 0 };
+
+                               packages.push.apply(packages, list.packages);
+                               packages.total = list.total;
+
+                               if (limit <= 0)
+                                       limit = list.total;
+
+                               if (packages.length >= limit)
+                                       return packages;
+
+                               L.rpc.batch();
+
+                               for (var i = offset + packages.length; i < limit; i += 100)
+                                       action(i, (Math.min(i + 100, limit) % 100) || 100, pattern);
+
+                               return L.rpc.flush();
+                       }).then(function(lists) {
+                               for (var i = 0; i < lists.length; i++)
+                               {
+                                       if (!lists[i].total || !lists[i].packages)
+                                               continue;
+
+                                       packages.push.apply(packages, lists[i].packages);
+                                       packages.total = lists[i].total;
+                               }
+
+                               return packages;
+                       });
+               },
+
+               listPackages: function(offset, limit, pattern)
+               {
+                       return this._fetchPackages(this._allPackages, offset, limit, pattern);
+               },
+
+               installedPackages: function(offset, limit, pattern)
+               {
+                       return this._fetchPackages(this._installedPackages, offset, limit, pattern);
+               },
+
+               findPackages: function(offset, limit, pattern)
+               {
+                       return this._fetchPackages(this._findPackages, offset, limit, pattern);
+               },
+
+               installPackage: L.rpc.declare({
+                       object: 'luci2.opkg',
+                       method: 'install',
+                       params: [ 'package' ],
+                       expect: { '': { } }
+               }),
+
+               removePackage: L.rpc.declare({
+                       object: 'luci2.opkg',
+                       method: 'remove',
+                       params: [ 'package' ],
+                       expect: { '': { } }
+               }),
+
+               getConfig: L.rpc.declare({
+                       object: 'luci2.opkg',
+                       method: 'config_get',
+                       expect: { config: '' }
+               }),
+
+               setConfig: L.rpc.declare({
+                       object: 'luci2.opkg',
+                       method: 'config_set',
+                       params: [ 'data' ]
+               }),
+
+               isInstalled: function(pkg)
+               {
+                       return this._installedPackages(0, 1, pkg).then(function(list) {
+                               return (!isNaN(list.total) && list.total > 0);
+                       });
+               }
+       },
+
        updateDiskSpace: function()
        {
                return L.system.getDiskInfo().then(function(info) {
        updateDiskSpace: function()
        {
                return L.system.getDiskInfo().then(function(info) {
@@ -15,19 +129,19 @@ L.ui.view.extend({
 
        installRemovePackage: function(pkgname, installed)
        {
 
        installRemovePackage: function(pkgname, installed)
        {
+               var self = this;
+
                var dspname   = pkgname.replace(/^.+\//, '');
                var dspname   = pkgname.replace(/^.+\//, '');
-               var action    = installed ? L.opkg.removePackage : L.opkg.installPackage;
+               var action    = installed ? self.opkg.removePackage : self.opkg.installPackage;
                var title     = (installed ? L.tr('Removing package "%s" …') : L.tr('Installing package "%s" …')).format(dspname);
                var confirm   = (installed ? L.tr('Really remove package "%h" ?') : L.tr('Really install package "%h" ?')).format(dspname);
 
                var title     = (installed ? L.tr('Removing package "%s" …') : L.tr('Installing package "%s" …')).format(dspname);
                var confirm   = (installed ? L.tr('Really remove package "%h" ?') : L.tr('Really install package "%h" ?')).format(dspname);
 
-               var self = this;
-
                L.ui.dialog(title, confirm, {
                        style:   'confirm',
                        confirm: function() {
                                L.ui.dialog(title, L.tr('Waiting for package manager …'), { style: 'wait' });
 
                L.ui.dialog(title, confirm, {
                        style:   'confirm',
                        confirm: function() {
                                L.ui.dialog(title, L.tr('Waiting for package manager …'), { style: 'wait' });
 
-                               action(pkgname).then(function(res) {
+                               action.call(self.opkg, pkgname).then(function(res) {
                                        self.fetchInstalledList().then(function() { return self.fetchPackageList(); }).then(function() {
                                                var output = [ ];
 
                                        self.fetchInstalledList().then(function() { return self.fetchPackageList(); }).then(function() {
                                                var output = [ ];
 
@@ -52,7 +166,7 @@ L.ui.view.extend({
        fetchInstalledList: function()
        {
                var self = this;
        fetchInstalledList: function()
        {
                var self = this;
-               return L.opkg.installedPackages(0, 0, '*').then(function(list) {
+               return self.opkg.installedPackages(0, 0, '*').then(function(list) {
                        self.installedList = { };
                        for (var i = 0; i < list.length; i++)
                                self.installedList[list[i][0]] = true;
                        self.installedList = { };
                        for (var i = 0; i < list.length; i++)
                                self.installedList[list[i][0]] = true;
@@ -67,19 +181,21 @@ L.ui.view.extend({
                if (typeof(offset) == 'undefined')
                        offset = parseInt($('#package_filter').attr('offset')) || 0;
 
                if (typeof(offset) == 'undefined')
                        offset = parseInt($('#package_filter').attr('offset')) || 0;
 
+               var self = this;
+
                var pattern = $('#package_filter').val() || '';
                var action;
 
                if (pattern.length)
                {
                var pattern = $('#package_filter').val() || '';
                var action;
 
                if (pattern.length)
                {
-                       action = $('#package_which').prop('checked') ? L.opkg.installedPackages : L.opkg.findPackages;
+                       action = $('#package_which').prop('checked') ? self.opkg.installedPackages : self.opkg.findPackages;
                        pattern = '*' + pattern + '*';
 
                        $('#package_filter').next().attr('src', L.globals.resource + '/icons/cbi/remove.gif');
                }
                else
                {
                        pattern = '*' + pattern + '*';
 
                        $('#package_filter').next().attr('src', L.globals.resource + '/icons/cbi/remove.gif');
                }
                else
                {
-                       action = $('#package_which').prop('checked') ? L.opkg.installedPackages : L.opkg.listPackages;
+                       action = $('#package_which').prop('checked') ? self.opkg.installedPackages : self.opkg.listPackages;
                        pattern = '*';
 
                        $('#package_filter').next().attr('src', L.globals.resource + '/icons/cbi/find.gif');
                        pattern = '*';
 
                        $('#package_filter').next().attr('src', L.globals.resource + '/icons/cbi/find.gif');
@@ -88,9 +204,8 @@ L.ui.view.extend({
                $('#package_filter').attr('offset', offset);
 
                var install_disabled = $('#package_install').attr('disabled');
                $('#package_filter').attr('offset', offset);
 
                var install_disabled = $('#package_install').attr('disabled');
-               var self = this;
 
 
-               return action(offset, 100, pattern).then(function(list) {
+               return action.call(self.opkg, offset, 100, pattern).then(function(list) {
                        var packageTable = new L.ui.table({
                                placeholder: L.tr('No matching packages found.'),
                                columns: [ {
                        var packageTable = new L.ui.table({
                                placeholder: L.tr('No matching packages found.'),
                                columns: [ {
@@ -159,7 +274,7 @@ L.ui.view.extend({
                $('#package_update, #package_url, #package_install').attr('disabled', !this.options.acls.software);
 
                return $.when(
                $('#package_update, #package_url, #package_install').attr('disabled', !this.options.acls.software);
 
                return $.when(
-                       L.opkg.getConfig().then(function(config) {
+                       self.opkg.getConfig().then(function(config) {
                                $('#config textarea')
                                        .attr('rows', (config.match(/\n/g) || [ ]).length + 1)
                                        .val(config);
                                $('#config textarea')
                                        .attr('rows', (config.match(/\n/g) || [ ]).length + 1)
                                        .val(config);
@@ -168,7 +283,7 @@ L.ui.view.extend({
                                        .click(function() {
                                                var data = ($('#config textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
                                                L.ui.loading(true);
                                        .click(function() {
                                                var data = ($('#config textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
                                                L.ui.loading(true);
-                                               L.opkg.setConfig(data).then(function() {
+                                               self.opkg.setConfig(data).then(function() {
                                                        $('#config textarea')
                                                                .attr('rows', (data.match(/\n/g) || [ ]).length + 1)
                                                                .val(data);
                                                        $('#config textarea')
                                                                .attr('rows', (data.match(/\n/g) || [ ]).length + 1)
                                                                .val(data);
@@ -225,7 +340,7 @@ L.ui.view.extend({
 
                        $('#package_update').click(function(ev) {
                                L.ui.dialog(L.tr('Updating package lists'), L.tr('Waiting for package manager …'), { style: 'wait' });
 
                        $('#package_update').click(function(ev) {
                                L.ui.dialog(L.tr('Updating package lists'), L.tr('Waiting for package manager …'), { style: 'wait' });
-                               L.opkg.updateLists().then(function(res) {
+                               self.opkg.updateLists().then(function(res) {
                                        var output = [ ];
 
                                        if (res.stdout)
                                        var output = [ ];
 
                                        if (res.stdout)
index e00e74a..769d12d 100644 (file)
 L.ui.view.extend({
 L.ui.view.extend({
-    title: L.tr('Startup'),
-    execute: function() {
-        var self = this;
-        var redraw = function() { return self.execute(); };
-        var allow_write = self.options.acls.startup;
+       title: L.tr('Startup'),
 
 
-        return $.when(
-            L.system.initList().then(function(list) {
-                /* filter init scripts with no start prio */
-                for (var i = 0; i < list.length; i++)
-                {
-                    if (typeof(list[i].start) != 'undefined')
-                        continue;
+       getRcLocal: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'rclocal_get',
+               expect: { data: '' }
+       }),
 
 
-                    list.splice(i--, 1);
-                }
+       setRcLocal: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'rclocal_set',
+               params: [ 'data' ]
+       }),
 
 
-                var initTable = new L.ui.table({
-                    columns: [ {
-                        caption: L.tr('Start priority'),
-                        key:     'start'
-                    }, {
-                        caption: L.tr('Initscript'),
-                        key:     'name'
-                    }, {
-                        key:     'enabled',
-                        format:  function(v, n) {
-                            return [
-                                $('<div />')
-                                    .addClass('btn-group pull-right')
-                                    .append($('<button />')
-                                        .attr('disabled', !allow_write)
-                                        .attr('name', list[n].name)
-                                        .addClass('btn btn-sm')
-                                        .addClass(v ? 'btn-success' : 'btn-danger')
-                                        .text(v ? L.trc('Init script state', 'Enabled') : L.trc('Init script state', 'Disabled'))
-                                        .click(function() {
-                                            L.ui.loading(true);
-                                            if (v)
-                                                L.system.initDisable(this.getAttribute('name')).then(redraw);
-                                            else
-                                                L.system.initEnable(this.getAttribute('name')).then(redraw);
-                                        }))
-                                    .append($('<button />')
-                                        .addClass('btn btn-primary btn-sm dropdown-toggle')
-                                        .attr('data-toggle', 'dropdown')
-                                        .attr('disabled', !allow_write)
-                                        .text(L.tr('Action…')))
-                                    .append($('<ul />')
-                                        .addClass('dropdown-menu pull-right')
-                                        .append($('<li />')
-                                            .append($('<a />')
-                                                .attr('href', '#')
-                                                .text(L.tr('Reload'))
-                                                .click(function(ev) { L.system.initReload(v).then(redraw); ev.preventDefault(); })))
-                                        .append($('<li />')
-                                            .append($('<a />')
-                                                .attr('href', '#')
-                                                .text(L.tr('Restart'))
-                                                .click(function(ev) { L.system.initRestart(v).then(redraw); ev.preventDefault(); })))
-                                        .append($('<li />')
-                                            .append($('<a />')
-                                                .attr('href', '#')
-                                                .text(L.tr('Stop'))
-                                                .click(function(ev) { L.system.initStop(v).then(redraw); ev.preventDefault(); }))))
-                            ];
-                        }
-                    } ]
-                });
+       execute: function() {
+               var self = this;
+               var redraw = function() { return self.execute(); };
+               var allow_write = self.options.acls.startup;
 
 
-                initTable.rows(list);
-                initTable.insertInto('#init_table');
+               return $.when(
+                       L.system.initList().then(function(list) {
+                               /* filter init scripts with no start prio */
+                               for (var i = 0; i < list.length; i++)
+                               {
+                                       if (typeof(list[i].start) != 'undefined')
+                                               continue;
 
 
-                L.ui.loading(false);
-            }),
-            L.system.getRcLocal().then(function(data) {
-                $('textarea').val(data).attr('disabled', !allow_write);
-                $('input.cbi-button-save').attr('disabled', !allow_write).click(function() {
-                    var data = ($('textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
-                    L.ui.loading(true);
-                    L.system.setRcLocal(data).then(function() {
-                        $('textarea').val(data);
-                        L.ui.loading(false);
-                    });
-                });
-            })
-        );
-    }
+                                       list.splice(i--, 1);
+                               }
+
+                               var initTable = new L.ui.table({
+                                       columns: [ {
+                                               caption: L.tr('Start priority'),
+                                               key:     'start'
+                                       }, {
+                                               caption: L.tr('Initscript'),
+                                               key:     'name'
+                                       }, {
+                                               key:     'enabled',
+                                               format:  function(v, n) {
+                                                       return [
+                                                               $('<div />')
+                                                                       .addClass('btn-group pull-right')
+                                                                       .append($('<button />')
+                                                                               .attr('disabled', !allow_write)
+                                                                               .attr('name', list[n].name)
+                                                                               .addClass('btn btn-sm')
+                                                                               .addClass(v ? 'btn-success' : 'btn-danger')
+                                                                               .text(v ? L.trc('Init script state', 'Enabled') : L.trc('Init script state', 'Disabled'))
+                                                                               .click(function() {
+                                                                                       L.ui.loading(true);
+                                                                                       if (v)
+                                                                                               L.system.initDisable(this.getAttribute('name')).then(redraw);
+                                                                                       else
+                                                                                               L.system.initEnable(this.getAttribute('name')).then(redraw);
+                                                                               }))
+                                                                       .append($('<button />')
+                                                                               .addClass('btn btn-primary btn-sm dropdown-toggle')
+                                                                               .attr('data-toggle', 'dropdown')
+                                                                               .attr('disabled', !allow_write)
+                                                                               .text(L.tr('Action…')))
+                                                                       .append($('<ul />')
+                                                                               .addClass('dropdown-menu pull-right')
+                                                                               .append($('<li />')
+                                                                                       .append($('<a />')
+                                                                                               .attr('href', '#')
+                                                                                               .text(L.tr('Reload'))
+                                                                                               .click(function(ev) { L.system.initReload(v).then(redraw); ev.preventDefault(); })))
+                                                                               .append($('<li />')
+                                                                                       .append($('<a />')
+                                                                                               .attr('href', '#')
+                                                                                               .text(L.tr('Restart'))
+                                                                                               .click(function(ev) { L.system.initRestart(v).then(redraw); ev.preventDefault(); })))
+                                                                               .append($('<li />')
+                                                                                       .append($('<a />')
+                                                                                               .attr('href', '#')
+                                                                                               .text(L.tr('Stop'))
+                                                                                               .click(function(ev) { L.system.initStop(v).then(redraw); ev.preventDefault(); }))))
+                                                       ];
+                                               }
+                                       } ]
+                               });
+
+                               initTable.rows(list);
+                               initTable.insertInto('#init_table');
+
+                               L.ui.loading(false);
+                       }),
+                       self.getRcLocal().then(function(data) {
+                               $('textarea').val(data).attr('disabled', !allow_write);
+                               $('input.cbi-button-save').attr('disabled', !allow_write).click(function() {
+                                       var data = ($('textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
+                                       L.ui.loading(true);
+                                       self.setRcLocal(data).then(function() {
+                                               $('textarea').val(data);
+                                               L.ui.loading(false);
+                                       });
+                               });
+                       })
+               );
+       }
 });
 });
index 924c45d..90e0012 100644 (file)
 L.ui.view.extend({
 L.ui.view.extend({
-    execute: function() {
-        var m = new L.cbi.Map('system', {
-            caption:     L.tr('System'),
-            description: L.tr('Here you can configure the basic aspects of your device like its hostname or the timezone.'),
-            collabsible: true
-        });
-
-        var s = m.section(L.cbi.TypedSection, 'system', {
-            caption:     L.tr('System Properties'),
-            teasers:     [ 'hostname', 'zonename', 'languages', 'themes' ],
-            readonly:    !this.options.acls.system
-        });
-
-        s.tab({
-            id:          'general',
-            caption:     L.tr('General Settings')
-        });
-
-        var t = s.taboption('general', L.cbi.DummyValue, '__time', {
-            caption:     L.tr('Local Time')
-        });
-
-        t.load = function(sid)
-        {
-            var id = this.id(sid);
-
-            return L.system.getSystemInfo().then(function(info) {
-                var date = new Date();
-                var time = info.localtime;
-
-                window.setInterval(function() {
-                    date.setTime(++time * 1000);
-
-                    $('#' + id).text('%04d/%02d/%02d %02d:%02d:%02d'.format(
-                        date.getUTCFullYear(),
-                        date.getUTCMonth() + 1,
-                        date.getUTCDate(),
-                        date.getUTCHours(),
-                        date.getUTCMinutes(),
-                        date.getUTCSeconds()
-                    ));
-                }, 1000);
-            });
-        };
-
-
-        s.taboption('general', L.cbi.InputValue, 'hostname', {
-            caption:     L.tr('Hostname'),
-            datatype:    'hostname'
-        });
-
-
-        var z = s.taboption('general', L.cbi.ListValue, 'zonename', {
-            caption:     L.tr('Timezone')
-        });
-
-        z.load = function(sid) {
-            return L.system.getZoneInfo(function(zones) {
-                var znames = [ ];
-
-                for (var i = 0; i < zones.length; i++)
-                    for (var j = 5; j < zones[i].length; j++)
-                        znames.push(zones[i][j]);
-
-                znames.sort();
-
-                for (var i = 0; i < znames.length; i++)
-                    z.value(znames[i]);
-
-                z.zones = zones;
-            });
-        };
-
-        z.save = function(sid)
-        {
-            var uci = this.ucipath(sid);
-            var val = this.formvalue(sid);
-
-            if (!this.callSuper('save', sid))
-                return false;
-
-            for (var i = 0; i < z.zones.length; i++)
-                for (var j = 5; j < z.zones[i].length; j++)
-                    if (z.zones[i][j] == val)
-                    {
-                        m.set(uci.config, uci.section, 'timezone', z.zones[i][0]);
-                        return true;
-                    }
-
-            m.set(uci.config, uci.section, 'timezone', 'GMT0');
-            return true;
-        };
-
-
-        s.tab({
-            id:          'logging',
-            caption:     L.tr('Logging')
-        });
-
-        s.taboption('logging', L.cbi.InputValue, 'log_size', {
-            caption:     L.tr('System log buffer size'),
-            description: L.tr('kiB'),
-            placeholder: 16,
-            optional:    true,
-            datatype:    'range(0, 32)'
-        });
-
-        s.taboption('logging', L.cbi.InputValue, 'log_ip', {
-            caption:     L.tr('External system log server'),
-            placeholder: '0.0.0.0',
-            optional:    true,
-            datatype:    'ip4addr'
-        });
-
-        s.taboption('logging', L.cbi.InputValue, 'log_port', {
-            caption:     L.tr('External system log server port'),
-            placeholder: 514,
-            optional:    true,
-            datatype:    'port'
-        });
-
-        s.taboption('logging', L.cbi.ListValue, 'conloglevel', {
-            caption:     L.tr('Log output level')
-        }).value(8, L.tr('Debug'))
-          .value(7, L.tr('Info'))
-          .value(6, L.tr('Notice'))
-          .value(5, L.tr('Warning'))
-          .value(4, L.tr('Error'))
-          .value(3, L.tr('Critical'))
-          .value(2, L.tr('Alert'))
-          .value(1, L.tr('Emergency'));
-
-        s.taboption('logging', L.cbi.ListValue, 'cronloglevel', {
-            caption:     L.tr('Cron Log level')
-        }).value(5, L.tr('Debug'))
-          .value(8, L.tr('Normal'))
-          .value(9, L.tr('Warning'));
-
-        s.tab({
-            id:          'language',
-            caption:     L.tr('Language and Style')
-        });
-
-
-        var l = s.taboption('language', L.cbi.ListValue, 'languages', {
-            caption:     L.tr('Language'),
-            uci_package: 'luci',
-            uci_section: 'main',
-            uci_option:  'lang'
-        }).value('auto', L.tr('Automatic'));
-
-        l.load = function(sid)
-        {
-            var langs = m.get('luci', 'languages');
-            for (var key in langs)
-                if (key.charAt(0) != '.')
-                    l.value(key, langs[key]);
-        };
-
-
-        var t = s.taboption('language', L.cbi.ListValue, 'themes', {
-            caption:     L.tr('Design'),
-            uci_package: 'luci',
-            uci_section: 'main',
-            uci_option:  'mediaurlbase'
-        });
-
-        t.load = function(sid)
-        {
-            var themes = m.get('luci', 'themes');
-            for (var key in themes)
-                if (key.charAt(0) != '.')
-                    t.value(themes[key], key);
-        };
-
-
-        var s2 = m.section(L.cbi.NamedSection, 'ntp', {
-            caption:      L.tr('Time Synchronization'),
-            readonly:    !this.options.acls.system
-        });
-
-        var e = s2.option(L.cbi.CheckboxValue, '.enable', {
-            caption:      L.tr('Enable NTP client'),
-            optional:     true
-        });
-
-        e.load = function(sid) {
-            return L.system.initEnabled('sysntpd').then(function(enabled) {
-                e.options.initial = enabled;
-            });
-        };
-
-        e.save = function(sid) {
-            if (this.formvalue(sid))
-                return L.system.initEnable('sysntpd');
-            else
-                return L.system.initDisable('sysntpd');
-        };
-
-        s2.option(L.cbi.CheckboxValue, 'enable_server', {
-            caption:      L.tr('Enable NTP server')
-        }).depends('.enable');
-
-        s2.option(L.cbi.DynamicList, 'server', {
-            caption:      L.tr('NTP server candidates'),
-            datatype:     'host'
-        }).depends('.enable');
-
-        return m.insertInto('#map');
-    }
+       execute: function() {
+               var m = new L.cbi.Map('system', {
+                       caption:     L.tr('System'),
+                       description: L.tr('Here you can configure the basic aspects of your device like its hostname or the timezone.'),
+                       collabsible: true
+               });
+
+               var s = m.section(L.cbi.TypedSection, 'system', {
+                       caption:     L.tr('System Properties'),
+                       teasers:     [ 'hostname', 'zonename', 'languages', 'themes' ],
+                       readonly:    !this.options.acls.system
+               });
+
+               s.tab({
+                       id:          'general',
+                       caption:     L.tr('General Settings')
+               });
+
+               var t = s.taboption('general', L.cbi.DummyValue, '__time', {
+                       caption:     L.tr('Local Time')
+               });
+
+               t.load = function(sid)
+               {
+                       var id = this.id(sid);
+
+                       return L.system.getSystemInfo().then(function(info) {
+                               var date = new Date();
+                               var time = info.localtime;
+
+                               window.setInterval(function() {
+                                       date.setTime(++time * 1000);
+
+                                       $('#' + id).text('%04d/%02d/%02d %02d:%02d:%02d'.format(
+                                               date.getUTCFullYear(),
+                                               date.getUTCMonth() + 1,
+                                               date.getUTCDate(),
+                                               date.getUTCHours(),
+                                               date.getUTCMinutes(),
+                                               date.getUTCSeconds()
+                                       ));
+                               }, 1000);
+                       });
+               };
+
+
+               s.taboption('general', L.cbi.InputValue, 'hostname', {
+                       caption:     L.tr('Hostname'),
+                       datatype:    'hostname'
+               });
+
+
+               var z = s.taboption('general', L.cbi.ListValue, 'zonename', {
+                       caption:     L.tr('Timezone')
+               });
+
+               z.load = function(sid) {
+                       return $.getJSON(L.globals.resource + '/zoneinfo.json').then(function(zones) {
+                               var znames = [ ];
+
+                               for (var i = 0; i < zones.length; i++)
+                                       for (var j = 5; j < zones[i].length; j++)
+                                               znames.push(zones[i][j]);
+
+                               znames.sort();
+
+                               for (var i = 0; i < znames.length; i++)
+                                       z.value(znames[i]);
+
+                               z.zones = zones;
+                       });
+               };
+
+               z.save = function(sid)
+               {
+                       var uci = this.ucipath(sid);
+                       var val = this.formvalue(sid);
+
+                       if (!this.callSuper('save', sid))
+                               return false;
+
+                       for (var i = 0; i < z.zones.length; i++)
+                               for (var j = 5; j < z.zones[i].length; j++)
+                                       if (z.zones[i][j] == val)
+                                       {
+                                               m.set(uci.config, uci.section, 'timezone', z.zones[i][0]);
+                                               return true;
+                                       }
+
+                       m.set(uci.config, uci.section, 'timezone', 'GMT0');
+                       return true;
+               };
+
+
+               s.tab({
+                       id:          'logging',
+                       caption:     L.tr('Logging')
+               });
+
+               s.taboption('logging', L.cbi.InputValue, 'log_size', {
+                       caption:     L.tr('System log buffer size'),
+                       description: L.tr('kiB'),
+                       placeholder: 16,
+                       optional:    true,
+                       datatype:    'range(0, 32)'
+               });
+
+               s.taboption('logging', L.cbi.InputValue, 'log_ip', {
+                       caption:     L.tr('External system log server'),
+                       placeholder: '0.0.0.0',
+                       optional:    true,
+                       datatype:    'ip4addr'
+               });
+
+               s.taboption('logging', L.cbi.InputValue, 'log_port', {
+                       caption:     L.tr('External system log server port'),
+                       placeholder: 514,
+                       optional:    true,
+                       datatype:    'port'
+               });
+
+               s.taboption('logging', L.cbi.ListValue, 'conloglevel', {
+                       caption:     L.tr('Log output level')
+               }).value(8, L.tr('Debug'))
+                 .value(7, L.tr('Info'))
+                 .value(6, L.tr('Notice'))
+                 .value(5, L.tr('Warning'))
+                 .value(4, L.tr('Error'))
+                 .value(3, L.tr('Critical'))
+                 .value(2, L.tr('Alert'))
+                 .value(1, L.tr('Emergency'));
+
+               s.taboption('logging', L.cbi.ListValue, 'cronloglevel', {
+                       caption:     L.tr('Cron Log level')
+               }).value(5, L.tr('Debug'))
+                 .value(8, L.tr('Normal'))
+                 .value(9, L.tr('Warning'));
+
+               s.tab({
+                       id:          'language',
+                       caption:     L.tr('Language and Style')
+               });
+
+
+               var l = s.taboption('language', L.cbi.ListValue, 'languages', {
+                       caption:     L.tr('Language'),
+                       uci_package: 'luci',
+                       uci_section: 'main',
+                       uci_option:  'lang'
+               }).value('auto', L.tr('Automatic'));
+
+               l.load = function(sid)
+               {
+                       var langs = m.get('luci', 'languages');
+                       for (var key in langs)
+                               if (key.charAt(0) != '.')
+                                       l.value(key, langs[key]);
+               };
+
+
+               var t = s.taboption('language', L.cbi.ListValue, 'themes', {
+                       caption:     L.tr('Design'),
+                       uci_package: 'luci',
+                       uci_section: 'main',
+                       uci_option:  'mediaurlbase'
+               });
+
+               t.load = function(sid)
+               {
+                       var themes = m.get('luci', 'themes');
+                       for (var key in themes)
+                               if (key.charAt(0) != '.')
+                                       t.value(themes[key], key);
+               };
+
+
+               var s2 = m.section(L.cbi.NamedSection, 'ntp', {
+                       caption:      L.tr('Time Synchronization'),
+                       readonly:    !this.options.acls.system
+               });
+
+               var e = s2.option(L.cbi.CheckboxValue, '.enable', {
+                       caption:      L.tr('Enable NTP client'),
+                       optional:     true
+               });
+
+               e.load = function(sid) {
+                       return L.system.initEnabled('sysntpd').then(function(enabled) {
+                               e.options.initial = enabled;
+                       });
+               };
+
+               e.save = function(sid) {
+                       if (this.formvalue(sid))
+                               return L.system.initEnable('sysntpd');
+                       else
+                               return L.system.initDisable('sysntpd');
+               };
+
+               s2.option(L.cbi.CheckboxValue, 'enable_server', {
+                       caption:      L.tr('Enable NTP server')
+               }).depends('.enable');
+
+               s2.option(L.cbi.DynamicList, 'server', {
+                       caption:      L.tr('NTP server candidates'),
+                       datatype:     'host'
+               }).depends('.enable');
+
+               return m.insertInto('#map');
+       }
 });
 });
index 9caac28..4c0799c 100644 (file)
@@ -1,22 +1,78 @@
 L.ui.view.extend({
        title: L.tr('Flash operations'),
 
 L.ui.view.extend({
        title: L.tr('Flash operations'),
 
-       handle_flash_upload: function() {
+       testUpgrade: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'upgrade_test',
+               expect: { '': { } }
+       }),
+
+       startUpgrade: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'upgrade_start',
+               params: [ 'keep' ]
+       }),
+
+       cleanUpgrade: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'upgrade_clean'
+       }),
+
+       restoreBackup: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'backup_restore'
+       }),
+
+       cleanBackup: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'backup_clean'
+       }),
+
+       getBackupConfig: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'backup_config_get',
+               expect: { config: '' }
+       }),
+
+       setBackupConfig: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'backup_config_set',
+               params: [ 'data' ]
+       }),
+
+       listBackup: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'backup_list',
+               expect: { files: [ ] }
+       }),
+
+       testReset: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'reset_test',
+               expect: { supported: false }
+       }),
+
+       startReset: L.rpc.declare({
+               object: 'luci2.system',
+               method: 'reset_start'
+       }),
+
+       handleFlashUpload: function() {
                var self = this;
                L.ui.upload(
                        L.tr('Firmware upload'),
                        L.tr('Select the sysupgrade image to flash and click "%s" to proceed.').format(L.tr('Ok')), {
                                filename: '/tmp/firmware.bin',
                                success: function(info) {
                var self = this;
                L.ui.upload(
                        L.tr('Firmware upload'),
                        L.tr('Select the sysupgrade image to flash and click "%s" to proceed.').format(L.tr('Ok')), {
                                filename: '/tmp/firmware.bin',
                                success: function(info) {
-                                       self.handle_flash_verify(info);
+                                       self.handleFlashVerify(info);
                                }
                        }
                );
        },
 
                                }
                        }
                );
        },
 
-       handle_flash_verify: function(info) {
+       handleFlashVerify: function(info) {
                var self = this;
                var self = this;
-               L.system.testUpgrade().then(function(res) {
+               self.testUpgrade().then(function(res) {
                        if (res.code == 0)
                        {
                                L.ui.dialog(
                        if (res.code == 0)
                        {
                                L.ui.dialog(
@@ -38,7 +94,7 @@ L.ui.view.extend({
                                        ], {
                                                style: 'confirm',
                                                confirm: function() {
                                        ], {
                                                style: 'confirm',
                                                confirm: function() {
-                                                       //L.system.startUpgrade().then(function() {
+                                                       //self.startUpgrade().then(function() {
                                                        //      L.ui.reconnect();
                                                        //});
 
                                                        //      L.ui.reconnect();
                                                        //});
 
@@ -59,7 +115,7 @@ L.ui.view.extend({
                                        ], {
                                                style: 'close',
                                                close: function() {
                                        ], {
                                                style: 'close',
                                                close: function() {
-                                                       L.system.cleanUpgrade().then(function() {
+                                                       self.cleanUpgrade().then(function() {
                                                                L.ui.dialog(false);
                                                        });
                                                }
                                                                L.ui.dialog(false);
                                                        });
                                                }
@@ -69,20 +125,20 @@ L.ui.view.extend({
                });
        },
 
                });
        },
 
-       handle_backup_upload: function() {
+       handleBackupUpload: function() {
                var self = this;
                L.ui.upload(
                        L.tr('Backup restore'),
                        L.tr('Select the backup archive to restore and click "%s" to proceed.').format(L.tr('Ok')), {
                                filename: '/tmp/backup.tar.gz',
                                success: function(info) {
                var self = this;
                L.ui.upload(
                        L.tr('Backup restore'),
                        L.tr('Select the backup archive to restore and click "%s" to proceed.').format(L.tr('Ok')), {
                                filename: '/tmp/backup.tar.gz',
                                success: function(info) {
-                                       self.handle_backup_verify(info);
+                                       self.handleBackupVerify(info);
                                }
                        }
                );
        },
 
                                }
                        }
                );
        },
 
-       handle_backup_verify: function(info) {
+       handleBackupVerify: function(info) {
                var self = this;
                L.ui.dialog(
                        L.tr('Backup restore'), [
                var self = this;
                L.ui.dialog(
                        L.tr('Backup restore'), [
@@ -97,15 +153,15 @@ L.ui.view.extend({
                        ], {
                                style: 'confirm',
                                confirm: function() {
                        ], {
                                style: 'confirm',
                                confirm: function() {
-                                       self.handle_backup_restore();
+                                       self.handleBackupRestore();
                                }
                        }
                );
        },
 
                                }
                        }
                );
        },
 
-       handle_backup_restore: function() {
+       handleBackupRestore: function() {
                var self = this;
                var self = this;
-               L.system.restoreBackup().then(function(res) {
+               self.restoreBackup().then(function(res) {
                        if (res.code == 0)
                        {
                                L.ui.dialog(
                        if (res.code == 0)
                        {
                                L.ui.dialog(
@@ -119,7 +175,7 @@ L.ui.view.extend({
                                        ], {
                                                style: 'close',
                                                close: function() {
                                        ], {
                                                style: 'close',
                                                close: function() {
-                                                       L.system.cleanBackup().then(function() {
+                                                       self.cleanBackup().then(function() {
                                                                L.ui.dialog(false);
                                                        });
                                                }
                                                                L.ui.dialog(false);
                                                        });
                                                }
@@ -138,7 +194,7 @@ L.ui.view.extend({
                                        ], {
                                                style: 'close',
                                                close: function() {
                                        ], {
                                                style: 'close',
                                                close: function() {
-                                                       L.system.cleanBackup().then(function() {
+                                                       self.cleanBackup().then(function() {
                                                                L.ui.dialog(false);
                                                        });
                                                }
                                                                L.ui.dialog(false);
                                                        });
                                                }
@@ -148,18 +204,19 @@ L.ui.view.extend({
                });
        },
 
                });
        },
 
-       handle_backup_download: function() {
+       handleBackupDownload: function() {
                var form = $('#btn_backup').parent();
 
                form.find('[name=sessionid]').val(L.globals.sid);
                form.submit();
        },
 
                var form = $('#btn_backup').parent();
 
                form.find('[name=sessionid]').val(L.globals.sid);
                form.submit();
        },
 
-       handle_reset: function() {
+       handleReset: function() {
+               var self = this;
                L.ui.dialog(L.tr('Really reset all changes?'), L.tr('This will reset the system to its initial configuration, all changes made since the initial flash will be lost!'), {
                        style: 'confirm',
                        confirm: function() {
                L.ui.dialog(L.tr('Really reset all changes?'), L.tr('This will reset the system to its initial configuration, all changes made since the initial flash will be lost!'), {
                        style: 'confirm',
                        confirm: function() {
-                               //L.system.startReset().then(function() {
+                               //self.startReset().then(function() {
                                //      L.ui.reconnect();
                                //});
 
                                //      L.ui.reconnect();
                                //});
 
@@ -171,7 +228,7 @@ L.ui.view.extend({
        execute: function() {
                var self = this;
 
        execute: function() {
                var self = this;
 
-               L.system.testReset().then(function(reset_avail) {
+               self.testReset().then(function(reset_avail) {
                        if (!reset_avail) {
                                $('#btn_reset').prop('disabled', true);
                        }
                        if (!reset_avail) {
                                $('#btn_reset').prop('disabled', true);
                        }
@@ -180,19 +237,19 @@ L.ui.view.extend({
                                $('#btn_restore, #btn_save, textarea').prop('disabled', true);
                        }
                        else {
                                $('#btn_restore, #btn_save, textarea').prop('disabled', true);
                        }
                        else {
-                               $('#btn_backup').click(function() { self.handle_backup_download(); });
-                               $('#btn_restore').click(function() { self.handle_backup_upload(); });
+                               $('#btn_backup').click(function() { self.handleBackupDownload(); });
+                               $('#btn_restore').click(function() { self.handleBackupUpload(); });
                        }
 
                        if (!self.options.acls.upgrade) {
                                $('#btn_flash, #btn_reset').prop('disabled', true);
                        }
                        else {
                        }
 
                        if (!self.options.acls.upgrade) {
                                $('#btn_flash, #btn_reset').prop('disabled', true);
                        }
                        else {
-                               $('#btn_flash').click(function() { self.handle_flash_upload(); });
-                               $('#btn_reset').click(function() { self.handle_reset(); });
+                               $('#btn_flash').click(function() { self.handleFlashUpload(); });
+                               $('#btn_reset').click(function() { self.handleReset(); });
                        }
 
                        }
 
-                       return L.system.getBackupConfig();
+                       return self.getBackupConfig();
                }).then(function(config) {
                        $('textarea')
                                .attr('rows', (config.match(/\n/g) || [ ]).length + 1)
                }).then(function(config) {
                        $('textarea')
                                .attr('rows', (config.match(/\n/g) || [ ]).length + 1)
@@ -202,7 +259,7 @@ L.ui.view.extend({
                                .click(function() {
                                        var data = ($('textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
                                        L.ui.loading(true);
                                .click(function() {
                                        var data = ($('textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
                                        L.ui.loading(true);
-                                       L.system.setBackupConfig(data).then(function() {
+                                       self.setBackupConfig(data).then(function() {
                                                $('textarea')
                                                        .attr('rows', (data.match(/\n/g) || [ ]).length + 1)
                                                        .val(data);
                                                $('textarea')
                                                        .attr('rows', (data.match(/\n/g) || [ ]).length + 1)
                                                        .val(data);
@@ -214,7 +271,7 @@ L.ui.view.extend({
                        $('#btn_list')
                                .click(function() {
                                        L.ui.loading(true);
                        $('#btn_list')
                                .click(function() {
                                        L.ui.loading(true);
-                                       L.system.listBackup().then(function(list) {
+                                       self.listBackup().then(function(list) {
                                                L.ui.loading(false);
                                                L.ui.dialog(
                                                        L.tr('Backup file list'),
                                                L.ui.loading(false);
                                                L.ui.dialog(
                                                        L.tr('Backup file list'),
index 6b30d30..cc495e9 100644 (file)
 L.ui.view.extend({
 L.ui.view.extend({
-    aclTable: L.cbi.AbstractValue.extend({
-        strGlob: function(pattern, match) {
-            var re = new RegExp('^' + (pattern
-                .replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1')
-                .replace(/\\\*/g, '.*?')) + '$');
-
-            return re.test(match);
-        },
-
-        aclMatch: function(list, group) {
-            for (var i = 0; i < list.length; i++)
-            {
-                var x = list[i].replace(/^\s*!\s*/, '');
-                if (x == list[i])
-                    continue;
-
-                if (this.strGlob(x, group))
-                    return false;
-            }
-
-            for (var i = 0; i < list.length; i++)
-            {
-                var x = list[i].replace(/^\s*!\s*/, '');
-                if (x != list[i])
-                    continue;
-
-                if (this.strGlob(x, group))
-                    return true;
-            }
-
-            return false;
-        },
-
-        aclTest: function(list, group) {
-            for (var i = 0; i < list.length; i++)
-                if (list[i] == group)
-                    return true;
-
-            return false;
-        },
-
-        aclEqual: function(list1, list2) {
-            if (list1.length != list2.length)
-                return false;
-
-            for (var i = 0; i < list1.length; i++)
-                if (list1[i] != list2[i])
-                    return false;
-
-            return true;
-        },
-
-        aclFromUCI: function(value) {
-            var list;
-            if (typeof(value) == 'string')
-                list = value.split(/\s+/);
-            else if ($.isArray(value))
-                list = value;
-            else
-                list = [ ];
-
-            var rv = [ ];
-            if (this.choices)
-                for (var i = 0; i < this.choices.length; i++)
-                    if (this.aclMatch(list, this.choices[i][0]))
-                        rv.push(this.choices[i][0]);
-
-            return rv;
-        },
-
-        aclToUCI: function(list) {
-            if (list.length < (this.choices.length / 2))
-                return list;
-
-            var set = { };
-            for (var i = 0; i < list.length; i++)
-                set[list[i]] = true;
-
-            var rv = [ '*' ];
-            for (var i = 0; i < this.choices.length; i++)
-                if (!set[this.choices[i][0]])
-                    rv.push('!' + this.choices[i][0]);
-
-            return rv;
-        },
-
-        setAll: function(ev) {
-            $(this).parents('table')
-                .find('input[value=%d]'.format(ev.data.level))
-                .prop('checked', true);
-        },
+       aclTable: L.cbi.AbstractValue.extend({
+               strGlob: function(pattern, match) {
+                       var re = new RegExp('^' + (pattern
+                               .replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1')
+                               .replace(/\\\*/g, '.*?')) + '$');
+
+                       return re.test(match);
+               },
+
+               aclMatch: function(list, group) {
+                       for (var i = 0; i < list.length; i++)
+                       {
+                               var x = list[i].replace(/^\s*!\s*/, '');
+                               if (x == list[i])
+                                       continue;
+
+                               if (this.strGlob(x, group))
+                                       return false;
+                       }
+
+                       for (var i = 0; i < list.length; i++)
+                       {
+                               var x = list[i].replace(/^\s*!\s*/, '');
+                               if (x != list[i])
+                                       continue;
+
+                               if (this.strGlob(x, group))
+                                       return true;
+                       }
+
+                       return false;
+               },
+
+               aclTest: function(list, group) {
+                       for (var i = 0; i < list.length; i++)
+                               if (list[i] == group)
+                                       return true;
+
+                       return false;
+               },
+
+               aclEqual: function(list1, list2) {
+                       if (list1.length != list2.length)
+                               return false;
+
+                       for (var i = 0; i < list1.length; i++)
+                               if (list1[i] != list2[i])
+                                       return false;
+
+                       return true;
+               },
+
+               aclFromUCI: function(value) {
+                       var list;
+                       if (typeof(value) == 'string')
+                               list = value.split(/\s+/);
+                       else if ($.isArray(value))
+                               list = value;
+                       else
+                               list = [ ];
+
+                       var rv = [ ];
+                       if (this.choices)
+                               for (var i = 0; i < this.choices.length; i++)
+                                       if (this.aclMatch(list, this.choices[i][0]))
+                                               rv.push(this.choices[i][0]);
+
+                       return rv;
+               },
+
+               aclToUCI: function(list) {
+                       if (list.length < (this.choices.length / 2))
+                               return list;
+
+                       var set = { };
+                       for (var i = 0; i < list.length; i++)
+                               set[list[i]] = true;
+
+                       var rv = [ '*' ];
+                       for (var i = 0; i < this.choices.length; i++)
+                               if (!set[this.choices[i][0]])
+                                       rv.push('!' + this.choices[i][0]);
+
+                       return rv;
+               },
+
+               setAll: function(ev) {
+                       $(this).parents('table')
+                               .find('input[value=%d]'.format(ev.data.level))
+                               .prop('checked', true);
+               },
 
                widget: function(sid)
                {
 
                widget: function(sid)
                {
-            var t = $('<table />')
-                .addClass('table table-condensed table-hover')
-                .attr('id', this.id(sid))
-                .append($('<tr />')
-                    .append($('<th />')
-                        .text(L.tr('ACL Group')))
-                    .append($('<th />')
-                        .text(L.trc('No access', 'N'))
-                        .attr('title', L.tr('Set all to no access'))
-                        .css('cursor', 'pointer')
-                        .click({ level: 0 }, this.setAll))
-                    .append($('<th />')
-                        .text(L.trc('Read only access', 'R'))
-                        .attr('title', L.tr('Set all to read only access'))
-                        .css('cursor', 'pointer')
-                        .click({ level: 1 }, this.setAll))
-                    .append($('<th />')
-                        .text(L.trc('Full access', 'F'))
-                        .attr('title', L.tr('Set all to full access'))
-                        .css('cursor', 'pointer')
-                        .click({ level: 2 }, this.setAll)));
-
-            var acl_r = this.aclFromUCI(this.map.get('rpcd', sid, 'read'));
-            var acl_w = this.aclFromUCI(this.map.get('rpcd', sid, 'write'));
-
-            if (this.choices)
-                for (var i = 0; i < this.choices.length; i++)
-                {
-                    var r = t.get(0).insertRow(-1);
-                    var is_r = this.aclTest(acl_r, this.choices[i][0]);
-                    var is_w = this.aclTest(acl_w, this.choices[i][0]);
-
-                    $(r.insertCell(-1))
-                        .text(this.choices[i][1]);
-
-                    for (var j = 0; j < 3; j++)
-                    {
-                        $(r.insertCell(-1))
-                            .append($('<input />')
-                                .addClass('form-control')
-                                .attr('type', 'radio')
-                                .attr('name', '%s_%s'.format(this.id(sid), this.choices[i][0]))
-                                .attr('value', j)
-                                .prop('checked', (j == 0 && !is_r && !is_w) ||
-                                                 (j == 1 &&  is_r && !is_w) ||
-                                                 (j == 2 &&           is_w)));
-                    }
-                }
-
-            return t;
+                       var t = $('<table />')
+                               .addClass('table table-condensed table-hover')
+                               .attr('id', this.id(sid))
+                               .append($('<tr />')
+                                       .append($('<th />')
+                                               .text(L.tr('ACL Group')))
+                                       .append($('<th />')
+                                               .text(L.trc('No access', 'N'))
+                                               .attr('title', L.tr('Set all to no access'))
+                                               .css('cursor', 'pointer')
+                                               .click({ level: 0 }, this.setAll))
+                                       .append($('<th />')
+                                               .text(L.trc('Read only access', 'R'))
+                                               .attr('title', L.tr('Set all to read only access'))
+                                               .css('cursor', 'pointer')
+                                               .click({ level: 1 }, this.setAll))
+                                       .append($('<th />')
+                                               .text(L.trc('Full access', 'F'))
+                                               .attr('title', L.tr('Set all to full access'))
+                                               .css('cursor', 'pointer')
+                                               .click({ level: 2 }, this.setAll)));
+
+                       var acl_r = this.aclFromUCI(this.map.get('rpcd', sid, 'read'));
+                       var acl_w = this.aclFromUCI(this.map.get('rpcd', sid, 'write'));
+
+                       if (this.choices)
+                               for (var i = 0; i < this.choices.length; i++)
+                               {
+                                       var r = t.get(0).insertRow(-1);
+                                       var is_r = this.aclTest(acl_r, this.choices[i][0]);
+                                       var is_w = this.aclTest(acl_w, this.choices[i][0]);
+
+                                       $(r.insertCell(-1))
+                                               .text(this.choices[i][1]);
+
+                                       for (var j = 0; j < 3; j++)
+                                       {
+                                               $(r.insertCell(-1))
+                                                       .append($('<input />')
+                                                               .addClass('form-control')
+                                                               .attr('type', 'radio')
+                                                               .attr('name', '%s_%s'.format(this.id(sid), this.choices[i][0]))
+                                                               .attr('value', j)
+                                                               .prop('checked', (j == 0 && !is_r && !is_w) ||
+                                                                                                (j == 1 &&  is_r && !is_w) ||
+                                                                                                (j == 2 &&           is_w)));
+                                       }
+                               }
+
+                       return t;
                },
 
                },
 
-        textvalue: function(sid)
-        {
-            var acl_r = this.aclFromUCI(this.map.get('rpcd', sid, 'read'));
-            var acl_w = this.aclFromUCI(this.map.get('rpcd', sid, 'write'));
+               textvalue: function(sid)
+               {
+                       var acl_r = this.aclFromUCI(this.map.get('rpcd', sid, 'read'));
+                       var acl_w = this.aclFromUCI(this.map.get('rpcd', sid, 'write'));
 
 
-            var htmlid = this.id(sid);
-            var radios = $('#' + htmlid + ' input');
+                       var htmlid = this.id(sid);
+                       var radios = $('#' + htmlid + ' input');
 
 
-            var acls = [  ];
+                       var acls = [  ];
 
 
-            for (var i = 0; i < this.choices.length; i++)
-            {
-                switch (radios.filter('[name=%s_%s]:checked'.format(htmlid, this.choices[i][0])).val())
-                {
-                case '2':
-                    acls.push('%s: %s'.format(this.choices[i][0], L.trc('Full access', 'F')));
-                    break;
+                       for (var i = 0; i < this.choices.length; i++)
+                       {
+                               switch (radios.filter('[name=%s_%s]:checked'.format(htmlid, this.choices[i][0])).val())
+                               {
+                               case '2':
+                                       acls.push('%s: %s'.format(this.choices[i][0], L.trc('Full access', 'F')));
+                                       break;
 
 
-                case '1':
-                    acls.push('%s: %s'.format(this.choices[i][0], L.trc('Read only access', 'R')));
-                    break;
+                               case '1':
+                                       acls.push('%s: %s'.format(this.choices[i][0], L.trc('Read only access', 'R')));
+                                       break;
 
 
-                case '0':
-                    acls.push('%s: %s'.format(this.choices[i][0], L.trc('No access', 'N')));
-                    break;
-                }
-            }
+                               case '0':
+                                       acls.push('%s: %s'.format(this.choices[i][0], L.trc('No access', 'N')));
+                                       break;
+                               }
+                       }
 
 
-            return acls.join(', ');
-        },
+                       return acls.join(', ');
+               },
 
                value: function(k, v)
                {
 
                value: function(k, v)
                {
@@ -185,133 +185,133 @@ L.ui.view.extend({
                        return this;
                },
 
                        return this;
                },
 
-        save: function(sid)
-        {
-            var acl_r = this.aclFromUCI(this.map.get('rpcd', sid, 'read'));
-            var acl_w = this.aclFromUCI(this.map.get('rpcd', sid, 'write'));
-
-            var acl_r_new = [ ];
-            var acl_w_new = [ ];
-
-            var htmlid = this.id(sid);
-            var radios = $('#' + htmlid + ' input');
-
-            for (var i = 0; i < this.choices.length; i++)
-            {
-                switch (radios.filter('[name=%s_%s]:checked'.format(htmlid, this.choices[i][0])).val())
-                {
-                case '2':
-                    acl_r_new.push(this.choices[i][0]);
-                    acl_w_new.push(this.choices[i][0]);
-                    break;
-
-                case '1':
-                    acl_r_new.push(this.choices[i][0]);
-                    break;
-                }
-            }
-
-            if (!this.aclEqual(acl_r, acl_r_new))
-                this.map.set('rpcd', sid, 'read', this.aclToUCI(acl_r_new));
-
-            if (!this.aclEqual(acl_w, acl_w_new))
-                this.map.set('rpcd', sid, 'write', this.aclToUCI(acl_w_new));
-        }
+               save: function(sid)
+               {
+                       var acl_r = this.aclFromUCI(this.map.get('rpcd', sid, 'read'));
+                       var acl_w = this.aclFromUCI(this.map.get('rpcd', sid, 'write'));
+
+                       var acl_r_new = [ ];
+                       var acl_w_new = [ ];
+
+                       var htmlid = this.id(sid);
+                       var radios = $('#' + htmlid + ' input');
+
+                       for (var i = 0; i < this.choices.length; i++)
+                       {
+                               switch (radios.filter('[name=%s_%s]:checked'.format(htmlid, this.choices[i][0])).val())
+                               {
+                               case '2':
+                                       acl_r_new.push(this.choices[i][0]);
+                                       acl_w_new.push(this.choices[i][0]);
+                                       break;
+
+                               case '1':
+                                       acl_r_new.push(this.choices[i][0]);
+                                       break;
+                               }
+                       }
+
+                       if (!this.aclEqual(acl_r, acl_r_new))
+                               this.map.set('rpcd', sid, 'read', this.aclToUCI(acl_r_new));
+
+                       if (!this.aclEqual(acl_w, acl_w_new))
+                               this.map.set('rpcd', sid, 'write', this.aclToUCI(acl_w_new));
+               }
        }),
 
        }),
 
-    execute: function() {
-        var self = this;
-        return L.ui.listAvailableACLs().then(function(acls) {
-            var m = new L.cbi.Map('rpcd', {
-                caption:     L.tr('Guest Logins'),
-                description: L.tr('Manage user accounts and permissions for accessing the LuCI ui.'),
-                readonly:    !self.options.acls.users
-            });
-
-            var s = m.section(L.cbi.TypedSection, 'login', {
-                caption:      L.tr('Accounts'),
-                collabsible:  true,
-                addremove:    true,
-                add_caption:  L.tr('Add account …'),
-                teasers:      [ 'username', '__shadow', '__acls' ]
-            });
-
-            s.option(L.cbi.InputValue, 'username', {
-                caption:     L.tr('Username'),
-                description: L.tr('Specifies the login name for the guest account'),
-                optional:    false
-            });
-
-
-            var shadow = s.option(L.cbi.CheckboxValue, '__shadow', {
-                caption:     L.tr('Use system account'),
-                description: L.tr('Use password from the Linux user database')
-            });
-
-            shadow.ucivalue = function(sid) {
-                var pw = this.map.get('rpcd', sid, 'password');
-                return (pw && pw.indexOf('$p$') == 0);
-            };
-
-
-            var password = s.option(L.cbi.PasswordValue, 'password', {
-                caption:     L.tr('Password'),
-                description: L.tr('Specifies the password for the guest account. If you enter a plaintext password here, it will get replaced with a crypted password hash on save.'),
-                optional:    false
-            });
-
-            password.depends('__shadow', false);
-
-            password.toggle = function(sid) {
-                var id = '#' + this.id(sid);
-                var pw = this.map.get('rpcd', sid, 'password');
-                var sh = this.section.fields.__shadow.formvalue(sid);
-
-                if (!sh && pw && pw.indexOf('$p$') == 0)
-                    $(id).val('');
-
-                this.callSuper('toggle', sid);
-            };
-
-            shadow.save = password.save = function(sid) {
-                var sh = this.section.fields.__shadow.formvalue(sid);
-                var pw = this.section.fields.password.formvalue(sid);
-
-                if (!sh && !pw)
-                    return;
-
-                if (sh)
-                    pw = '$p$' + this.section.fields.username.formvalue(sid);
-
-                if (pw.match(/^\$[0-9p][a-z]?\$/))
-                {
-                    if (pw != this.map.get('rpcd', sid, 'password'))
-                        this.map.set('rpcd', sid, 'password', pw);
-                }
-                else
-                {
-                    var map = this.map;
-                    return L.ui.cryptPassword(pw).then(function(crypt) {
-                        map.set('rpcd', sid, 'password', crypt);
-                    });
-                }
-            };
-
-            var o = s.option(self.aclTable, '__acls', {
-                caption:     L.tr('User ACLs'),
-                description: L.tr('Specifies the access levels of this account. The "N" column means no access, "R" stands for read only access and "F" for full access.')
-            });
-
-            var groups = [ ];
-            for (var group_name in acls)
-                groups.push(group_name);
-
-            groups.sort();
-
-            for (var i = 0; i < groups.length; i++)
-                o.value(groups[i], acls[groups[i]].description);
-
-            return m.insertInto('#map');
-        });
-    }
+       execute: function() {
+               var self = this;
+               return L.ui.listAvailableACLs().then(function(acls) {
+                       var m = new L.cbi.Map('rpcd', {
+                               caption:     L.tr('Guest Logins'),
+                               description: L.tr('Manage user accounts and permissions for accessing the LuCI ui.'),
+                               readonly:    !self.options.acls.users
+                       });
+
+                       var s = m.section(L.cbi.TypedSection, 'login', {
+                               caption:      L.tr('Accounts'),
+                               collabsible:  true,
+                               addremove:    true,
+                               add_caption:  L.tr('Add account …'),
+                               teasers:      [ 'username', '__shadow', '__acls' ]
+                       });
+
+                       s.option(L.cbi.InputValue, 'username', {
+                               caption:     L.tr('Username'),
+                               description: L.tr('Specifies the login name for the guest account'),
+                               optional:    false
+                       });
+
+
+                       var shadow = s.option(L.cbi.CheckboxValue, '__shadow', {
+                               caption:     L.tr('Use system account'),
+                               description: L.tr('Use password from the Linux user database')
+                       });
+
+                       shadow.ucivalue = function(sid) {
+                               var pw = this.map.get('rpcd', sid, 'password');
+                               return (pw && pw.indexOf('$p$') == 0);
+                       };
+
+
+                       var password = s.option(L.cbi.PasswordValue, 'password', {
+                               caption:     L.tr('Password'),
+                               description: L.tr('Specifies the password for the guest account. If you enter a plaintext password here, it will get replaced with a crypted password hash on save.'),
+                               optional:    false
+                       });
+
+                       password.depends('__shadow', false);
+
+                       password.toggle = function(sid) {
+                               var id = '#' + this.id(sid);
+                               var pw = this.map.get('rpcd', sid, 'password');
+                               var sh = this.section.fields.__shadow.formvalue(sid);
+
+                               if (!sh && pw && pw.indexOf('$p$') == 0)
+                                       $(id).val('');
+
+                               this.callSuper('toggle', sid);
+                       };
+
+                       shadow.save = password.save = function(sid) {
+                               var sh = this.section.fields.__shadow.formvalue(sid);
+                               var pw = this.section.fields.password.formvalue(sid);
+
+                               if (!sh && !pw)
+                                       return;
+
+                               if (sh)
+                                       pw = '$p$' + this.section.fields.username.formvalue(sid);
+
+                               if (pw.match(/^\$[0-9p][a-z]?\$/))
+                               {
+                                       if (pw != this.map.get('rpcd', sid, 'password'))
+                                               this.map.set('rpcd', sid, 'password', pw);
+                               }
+                               else
+                               {
+                                       var map = this.map;
+                                       return L.ui.cryptPassword(pw).then(function(crypt) {
+                                               map.set('rpcd', sid, 'password', crypt);
+                                       });
+                               }
+                       };
+
+                       var o = s.option(self.aclTable, '__acls', {
+                               caption:     L.tr('User ACLs'),
+                               description: L.tr('Specifies the access levels of this account. The "N" column means no access, "R" stands for read only access and "F" for full access.')
+                       });
+
+                       var groups = [ ];
+                       for (var group_name in acls)
+                               groups.push(group_name);
+
+                       groups.sort();
+
+                       for (var i = 0; i < groups.length; i++)
+                               o.value(groups[i], acls[groups[i]].description);
+
+                       return m.insertInto('#map');
+               });
+       }
 });
 });