GSoC ’23: Migrating luci-app-mjpg-streamer to JavaScript: A Comprehensive Guide

The latest OpenWRT versions introduced a new web interface system that eliminated the need for Lua. Instead, the client’s browser handles the rendering and computation, allowing routers to focus on their primary tasks. This change has the advantage of eliminating the lua runtime, saving storage space, and having faster routers. In the previous CBI-based system, pages were rendered on the router and sent as HTML to the browser, increasing the load on the routers. This inefficiency can result in performance problems. To aid in this transition, LuCI offers the LuCI-JavaScript API, which is now utilized for constructing web interfaces.

luci-app-mjpg-streamer

I have successfully migrated luci-app-mjpg-streamer to JavaScript, making it a valuable example for building or migrating LuCI apps. This tutorial covers the essential aspects of the process, providing a comprehensive guide.

Below is the tree view representation of the directory structure for the app:

.
├── Makefile
├── htdocs
│   └── luci-static
│       └── resources
│           └── view
│               └── mjpg-streamer
│                   └── mjpg-streamer.js
├── po
│   ├── ar
│   │   └── mjpg-streamer.po
│   ├── ...
│   
│    
│       
└── root
    └── usr
        └── share
            ├── luci
            │   └── menu.d
            │       └── luci-app-mjpg-streamer.json
            └── rpcd
                └── acl.d
                    └── luci-app-mjpg-streamer.json

How to migrate your app :

ACLs

In the file  root/usr/share/rpcd/acl.d/luci-app-mjpg-streamer.json, we provide all the necessary access permissions for our application to function properly.

{
	"luci-app-mjpg-streamer": {
		"description": "Grant UCI access for luci-app-mjpg-streamer",
		"read": {
			"uci": [
				"mjpg-streamer"
			]
		},
		"write": {
			"uci": [
				"mjpg-streamer"
			]
		}
	}
}

For example, in my previously migrated app: luci-app-olsr, when there is a need to grant public access to specific pages, we ensure that all essential access permissions are appropriately configured in root/usr/share/rpcd/acl.d/luci-app-olsr-unauthenticated.json.

These permissions are necessary for the operation of our application when a user is not yet authenticated-

{
	"unauthenticated": {
		"description": "Grant read access",
		"read": {
			"ubus": {
				"uci": ["get"],
				"luci-rpc": ["*"],
				"network.interface": ["dump"],
				"network": ["get_proto_handlers"],
				"olsrd": ["olsrd_jsoninfo"],
				"olsrd6": ["olsrd_jsoninfo"],
				"olsrinfo": ["getjsondata", "hasipip", "hosts"],
				"file": ["read"],
				"iwinfo": ["assoclist"]

			},
			"uci": ["luci_olsr", "olsrd", "olsrd6", "network", "network.interface"]
		}
	}
}
 

To learn more about how ACL (Access Control List) works, you can refer to this resource: OpenWRT’s docs  It is important to consider applying the principle of least privilege when configuring ACLs.

MENU

In the file root/usr/share/luci/menu.d/luci-app-mjpg-streamer.json, we define the location where our view will be displayed in the admin menu. This is utilized for admin specific views

{
	"admin/services/mjpg-streamer": {
		"title": "MJPG-streamer",
		"action": {
			"type": "view",
			"path": "mjpg-streamer/mjpg-streamer"
		},
		"depends": {
			"acl": [
				"luci-app-mjpg-streamer"
			],
			"uci": {
				"mjpg-streamer": true
			}
		}
	}
}

The path indicates the location where the JavaScript view to be rendered is present with respect to the htdocs/luci-static/resources/view directory.

FORMS

To explore the JavaScript APIs offered by LuCI, you can visit the following link: LuCI client side API documentation. A recommended starting point is the core luci.js class.

LuCI forms allow you to create UCI or JSON-backed configuration forms. To create a typical form, you start by creating an instance of either LuCI.form.Map or LuCI.form.JSONMap using new. Then, you can add sections and options to the form instance. Finally, invoking the render() method on the instance generates the HTML markup and inserts it into the Document Object Model(DOM). For a better understanding of how LuCI forms work, you can refer to the following : LuCI.form.

This is an example demonstrating the usage of LuCI.form within one of the admin’s views, using a small portion of the mjpg-streamer.js code. The full code for the file can be found here.

'use strict';
'require view';
'require form';
'require uci';
'require ui';
'require poll';

/* Copyright 2014 Roger D < rogerdammit@gmail.com>
Licensed to the public under the Apache License 2.0. */

return view.extend({
	load: function () {
		var self = this;
		poll.add(function () {
			self.render();
		}, 5);

		document
			.querySelector('head')
			.appendChild(
				E('style', { type: 'text/css' }, [
					'.img-preview {display: inline-block !important;height: auto;width: 640px;padding: 4px;line-height: 1.428571429;background-color: #fff;border: 1px solid #ddd;border-radius: 4px;-webkit-transition: all .2s ease-in-out;transition: all .2s ease-in-out;margin-bottom: 5px;display: none;}',
				]),
			);

		return Promise.all([uci.load('mjpg-streamer')]);
	},
	render: function () {
		var m, s, o;

		m = new form.Map('mjpg-streamer', 'MJPG-streamer', _('mjpg streamer is a streaming application for Linux-UVC compatible webcams'));

		//General settings

		var section_gen = m.section(form.TypedSection, 'mjpg-streamer', _('General'));
		section_gen.addremove = false;
		section_gen.anonymous = true;

		var enabled = section_gen.option(form.Flag, 'enabled', _('Enabled'), _('Enable MJPG-streamer'));

		var input = section_gen.option(form.ListValue, 'input', _('Input plugin'));
		input.depends('enabled', '1');
		input.value('uvc', 'UVC');
		// input: value("file", "File")
		input.optional = false;

		var output = section_gen.option(form.ListValue, 'output', _('Output plugin'));
		output.depends('enabled', '1');
		output.value('http', 'HTTP');
		output.value('file', 'File');
		output.optional = false;

		//Plugin settings

		s = m.section(form.TypedSection, 'mjpg-streamer', _('Plugin settings'));
		s.addremove = false;
		s.anonymous = true;

		s.tab('output_http', _('HTTP output'));
		s.tab('output_file', _('File output'));
		s.tab('input_uvc', _('UVC input'));
		// s: tab("input_file", _("File input"))

		// Input UVC settings

		var this_tab = 'input_uvc';

		var device = s.taboption(this_tab, form.Value, 'device', _('Device'));
		device.default = '/dev/video0';
		//device.datatype = "device"
		device.value('/dev/video0', '/dev/video0');
		device.value('/dev/video1', '/dev/video1');
		device.value('/dev/video2', '/dev/video2');
		device.optional = false;

                  //... This snippet represents only a small portion of the complete code.

		var ringbuffer = s.taboption(this_tab, form.Value, 'ringbuffer', _('Ring buffer size'), _('Max. number of pictures to hold'));
		ringbuffer.placeholder = '10';
		ringbuffer.datatype = 'uinteger';

		var exceed = s.taboption(this_tab, form.Value, 'exceed', _('Exceed'), _('Allow ringbuffer to exceed limit by this amount'));
		exceed.datatype = 'uinteger';

		var command = s.taboption(
			this_tab,
			form.Value,
			'command',
			_('Command to run'),
			_('Execute command after saving picture. Mjpg-streamer parses the filename as first parameter to your script.'),
		);

		var link = s.taboption(this_tab, form.Value, 'link', _('Link newest picture to fixed file name'), _('Link the last picture in ringbuffer to fixed named file provided.'));

		return m.render();
	},
});

Flexible Views

For enhanced flexibility in our pages, we have the option to manually define the HTML, which I have used in the status views. This approach allows us to have more control over the page structure and content, providing greater customization possibilities

This is an example demonstrating the usage of flexible views within one of the status’s views, using a small portion of the topology.js code. The full code for the file can be found here.

'use strict';
'require uci';
'require view';
'require poll';
'require rpc';
'require ui';


return view.extend({
	callGetJsonStatus: rpc.declare({
		object: 'olsrinfo',
		method: 'getjsondata',
		params: ['otable', 'v4_port', 'v6_port'],
	}),

	fetch_jsoninfo: function (otable) {
		var jsonreq4 = '';
		var jsonreq6 = '';
		var v4_port = parseInt(uci.get('olsrd', 'olsrd_jsoninfo', 'port') || '') || 9090;
		var v6_port = parseInt(uci.get('olsrd6', 'olsrd_jsoninfo', 'port') || '') || 9090;
		var json;
		var self = this;
		return new Promise(function (resolve, reject) {
			L.resolveDefault(self.callGetJsonStatus(otable, v4_port, v6_port), {})
				.then(function (res) {
					json = res;

					jsonreq4 = JSON.parse(json.jsonreq4);
					jsonreq6 = json.jsonreq6 !== '' ? JSON.parse(json.jsonreq6) : [];
					var jsondata4 = {};
					var jsondata6 = {};
					var data4 = [];
					var data6 = [];
					var has_v4 = false;
					var has_v6 = false;

					if (jsonreq4 === '' && jsonreq6 === '') {
						window.location.href = 'error_olsr';
						reject([null, 0, 0, true]);
						return;
					}

					if (jsonreq4 !== '') {
						has_v4 = true;
						jsondata4 = jsonreq4 || {};
						if (otable === 'status') {
							data4 = jsondata4;
						} else {
							data4 = jsondata4[otable] || [];
						}

						for (var i = 0; i < data4.length; i++) {
							data4[i]['proto'] = '4';
						}
					}

					if (jsonreq6 !== '') {
						has_v6 = true;
						jsondata6 = jsonreq6 || {};
						if (otable === 'status') {
							data6 = jsondata6;
						} else {
							data6 = jsondata6[otable] || [];
						}

						for (var j = 0; j < data6.length; j++) {
							data6[j]['proto'] = '6';
						}
					}

					for (var k = 0; k < data6.length; k++) {
						data4.push(data6[k]);
					}

					resolve([data4, has_v4, has_v6, false]);
				})
				.catch(function (err) {
					console.error(err);
					reject([null, 0, 0, true]);
				});
		});
	},
	action_topology: function () {
		var self = this;
		return new Promise(function (resolve, reject) {
			self
				.fetch_jsoninfo('topology')
				.then(function ([data, has_v4, has_v6, error]) {
					if (error) {
						reject(error);
					}

					function compare(a, b) {
						if (a.proto === b.proto) {
							return a.tcEdgeCost < b.tcEdgeCost;
						} else {
							return a.proto < b.proto;
						}
					}

					data.sort(compare);

					var result = { routes: data, has_v4: has_v4, has_v6: has_v6 };
					resolve(result);
				})
				.catch(function (err) {
					reject(err);
				});
		});
	},
	load: function () {
		return Promise.all([uci.load('olsrd'), uci.load('luci_olsr')]);
	},
	render: function () {
		var routes_res;
		var has_v4;
		var has_v6;

		return this.action_topology()
			.then(function (result) {
				routes_res = result.routes;
				has_v4 = result.has_v4;
				has_v6 = result.has_v6;
				var table = E('div', { 'class': 'table cbi-section-table' }, [
					E('div', { 'class': 'tr cbi-section-table-titles' }, [
						E('div', { 'class': 'th cbi-section-table-cell' }, _('OLSR node')),
						E('div', { 'class': 'th cbi-section-table-cell' }, _('Last hop')),
						E('div', { 'class': 'th cbi-section-table-cell' }, _('LQ')),
						E('div', { 'class': 'th cbi-section-table-cell' }, _('NLQ')),
						E('div', { 'class': 'th cbi-section-table-cell' }, _('ETX')),
					]),
				]);
				var i = 1;

				for (var k = 0; k < routes_res.length; k++) {
					var route = routes_res[k];
					var cost = (parseInt(route.tcEdgeCost) || 0).toFixed(3);
					var color = etx_color(parseInt(cost));
					var lq = (parseInt(route.linkQuality) || 0).toFixed(3);
					var nlq = (parseInt(route.neighborLinkQuality) || 0).toFixed(3);

					var tr = E('div', { 'class': 'tr cbi-section-table-row cbi-rowstyle-' + i + ' proto-' + route.proto }, [
						route.proto === '6'
							? E('div', { 'class': 'td cbi-section-table-cell left' }, [E('a', { 'href': 'http://[' + route.destinationIP + ']/cgi-bin-status.html' }, route.destinationIP)])
							: E('div', { 'class': 'td cbi-section-table-cell left' }, [E('a', { 'href': 'http://' + route.destinationIP + '/cgi-bin-status.html' }, route.destinationIP)]),
						route.proto === '6'
							? E('div', { 'class': 'td cbi-section-table-cell left' }, [E('a', { 'href': 'http://[' + route.lastHopIP + ']/cgi-bin-status.html' }, route.lastHopIP)])
							: E('div', { 'class': 'td cbi-section-table-cell left' }, [E('a', { 'href': 'http://' + route.lastHopIP + '/cgi-bin-status.html' }, route.lastHopIP)]),
						E('div', { 'class': 'td cbi-section-table-cell left' }, lq),
						E('div', { 'class': 'td cbi-section-table-cell left' }, nlq),
						E('div', { 'class': 'td cbi-section-table-cell left', 'style': 'background-color:' + color }, cost),
					]);

					table.appendChild(tr);
					i = (i % 2) + 1;
				}

				var fieldset = E('fieldset', { 'class': 'cbi-section' }, [E('legend', {}, _('Overview of currently known OLSR nodes')), table]);

                //... This snippet represents only a small portion of the complete code.

				var result = E([], {}, [h2, divToggleButtons, fieldset, statusOlsrLegend, statusOlsrCommonJs]);

				return result;
			})
			.catch(function (error) {
				console.error(error);
			});
	},
	handleSaveApply: null,
	handleSave: null,
});

Feel free to reach out to me via email if you have any doubts or questions. I’m here to help! Stay tuned for more valuable content as I continue to share useful information and resources. Thank you for your support!

GSoC’23 Final Report : LuCI Migrate to JavaScript-Based Framework

Hello!

I’ve had a wonderful and enriching experience over the last 5 months while working on my Google Summer of Code Project, “LuCI Migration to JavaScript-Based Framework.” As Google Summer of Code 2023@Freifunk draws to a close, I am excited to announce the successful completion of my project, which involved migrating several LuCI apps to JavaScript. I wish to extend my sincere gratitude to my mentor, Andreas Bräu. His constant support and guidance have been extremely helpful.

Project Goals

LuCI is an open-source framework that is widely used to build web interfaces for embedded devices such as WiFi routers. In the CBI-based old system, pages were rendered on the router and delivered as HTML to the browser, which caused a higher load on the embedded devices. This makes the system less efficient and can lead to performance issues.

The latest OpenWRT versions introduced a new web interface system that eliminated the need for Lua. Instead, the client’s browser handles the rendering and computation, allowing routers to focus on their primary tasks. This change has the advantage of eliminating the Lua runtime, saving storage space, and having faster routers. In the previous CBI-based system, pages were rendered on the router and sent as HTML to the browser, increasing the load on the routers. This inefficiency can result in performance problems. To aid in this transition, LuCI offers the LuCI-JavaScript API, which is now utilized for constructing web interfaces.

Project Results

As part of the project, I accomplished the successful migration of the following apps to JavaScript:

  • luci-app-olsr (OLSR configuration and status module)
  • luci-app-uhttpd (OLSR configuration and status module)
  • luci-app-olsr-viz (OLSR Visualization)
  • luci-app-babeld (LuCI support for babeld)
  • luci-app-mjpg-streamer (MJPG-Streamer service configuration module)

The migration of luci-app-olsr stood out as an incredibly exciting and valuable learning experience. Notably, this application now boasts a performance improvement of four to five times compared to its previous version. I also created a comprehensive tutorial based on the migration process of luci-app-olsr, which can serve as a valuable reference for writing or migrating other LuCI apps.

The tutorial covers the essential aspects of the process, providing a comprehensive guide. This app is an extensive application that includes both status views and an admin backend.
Below is the tree view representation of the directory structure for the app:

Explore My Contributions

My work can be located within the following commits, and all reviewed applications have been merged. These will soon be accessible to users in the upcoming OpenWRT releases.

What Next

While the work has been carried out within the scope of GSoC, I am committed to continuing the migration of additional apps to JavaScript even after the program’s conclusion. I will maintain an active presence in the community and actively seek out intriguing projects to contribute to.

Wrapping Up

Following the migration of these commonly used apps in LuCI, a significant enhancement in performance has been achieved. Project objectives have been successfully met, leading to reduced router workload and an improved user experience, particularly for those with lower-specification routers. The new system also offers increased developer flexibility. Leveraging a client-side JavaScript framework provides developers with versatile options for future customization and extension of the LuCI web interface.

This shift establishes a standardized approach for developers to interact with router services, configure data retrieval, and support the development and maintenance of LuCI-based applications. Such advancements are particularly valuable for community networks reliant on lower-spec devices. The heightened performance and decreased device load simplify network management, bolstering the efficacy of LuCI-based tools. In summary, the migration of LuCI to JavaScript yielded substantial benefits for the OpenWrt community and users. These include improved performance, elevated developer adaptability, and potentially streamlined management of LuCI-based applications within community networks.

My engagement in this project was a source of enjoyment and knowledge, and though it demanded significant efforts, I found enjoyment in the process. and I extend special appreciation to my mentor for his unwavering support and motivation. GSoC 2023 with Freifunk has proven to be an enriching experience. My path ahead involves contributing to additional open-source projects, further app migrations to JavaScript.

GSoC ’23: Migrating LuCI Apps to JavaScript: A Comprehensive Guide

The latest OpenWRT versions introduced a new web interface system that eliminates the need for lua. Instead, the client’s browser handles the rendering and computation, allowing routers to focus on their primary tasks. This change brings the advantage of eliminating the lua runtime and saving storage space, having faster routers. In the previous CBI-based system, pages were rendered on the router and sent as HTML to the browser, increasing the load on the routers. This inefficiency can result in performance problems. To aid in this transition, LuCI offers the LuCI-JavaScript API, which is now utilized for constructing web interfaces.

luci-app-olsr

I have successfully migrated luci-app-olsr to JavaScript, making it a valuable example for building or migrating LuCI apps. First and foremost, I would like to express my heartfelt thanks to my mentor Andreas Bräu. Without his unwavering support, I would not have been able to successfully migrate this huge application.

This tutorial covers the essential aspects of the process, providing a comprehensive guide. This app is an extensive application that includes both status views and an admin backend.

Below is the tree view representation of the directory structure for the app:

How to migrate your app :

ACLs

In the file root/usr/share/rpcd/acl.d/luci-app-olsr.json, we provide all the necessary access permissions for our application to function properly.

{
	"luci-app-olsr": {
		"description": "Grant UCI access for luci-app-olsr",
		"read": {
			"ubus": {
				"luci-rpc": [
					"*"
				],
				"olsrinfo": [
					"getjsondata",
					"hasipip"
				]
			},
			"file": {
				"/etc/modules.d": [
					"list",
					"read"
				],
				"/usr/lib": [ "list" ]
			},
			"uci": [
				"luci_olsr",
				"olsrd",
				"olsrd6"
			]
		},
		"write": {
			"uci": [
				"luci_olsr",
				"olsrd",
				"olsrd6"
			]
		}
	}
}

Similarly, in root/usr/share/rpcd/acl.d/luci-app-olsr-unauthenticated.json, we grant the required access permissions for our application when the user is not authenticated. This is used for status views.

To learn more about how ACL (Access Control List) works, you can refer to this resource: OpenWRT’s docs  It is important to consider applying the principle of least privilege when configuring ACLs.

MENU

In the file root/usr/share/luci/menu.d/luci-app-olsr-backend.json, we define the location where our view will be displayed in the admin menu. This is utilized for admin specific views.

{
	"admin/services/olsrd": {
		"title": "OLSR IPv4",
		"order": 5,
		"depends": {
			"acl": ["luci-app-olsr"]
		},
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrd"
		}
	},
	"admin/services/olsrd/display": {
		"title": "Display",
		"order": 10,
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrddisplay"
		}
	},
	"admin/services/olsrd/iface": {
		"order": 10,
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrdiface"
		}
	},
	"admin/services/olsrd/hna": {
		"title": "HNA Announcements",
		"order": 15,
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrdhna"
		}
	},
	"admin/services/olsrd/plugins": {
		"title": "Plugins",
		"order": 20,
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrdplugins"
		}
	},
	"admin/services/olsrd6": {
		"title": "OLSR IPv6",
		"order": 5,
		"depends": {
			"acl": ["luci-app-olsr"]
		},
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrd6"
		}
	},
	"admin/services/olsrd6/display": {
		"title": "Display",
		"order": 10,
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrddisplay"
		}
	},
	"admin/services/olsrd6/iface": {
		"order": 10,
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrdiface6"
		}
	},
	"admin/services/olsrd6/hna": {
		"title": "HNA Announcements",
		"order": 15,
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrdhna6"
		}
	},
	"admin/services/olsrd6/plugins": {
		"title": "Plugins",
		"order": 20,
		"action": {
			"type": "view",
			"path": "olsr/frontend/olsrdplugins6"
		}
	}
}

On the other hand, in root/usr/share/luci/menu.d/luci-app-olsr-frontend.json, we specify the location where our view will be displayed in the menu. This is used for the status views of our application.

The path indicates the location where the JavaScript view to be rendered is present with respect to the htdocs/luci-static/resources/view directory.

Forms and Flexible Views

By utilizing root/etc/uci-defaults/40_luci-olsr, we ensure the creation of a straightforward configuration file for our application upon installation.

To explore the JavaScript APIs offered by LuCI, you can visit the following link: LuCI client side API documentation. A recommended starting point is the core luci.js class.

FORMS

LuCI forms allow you to create UCI or JSON-backed configuration forms. To create a typical form, you start by creating an instance of either LuCI.form.Map or LuCI.form.JSONMap using new. Then, you can add sections and options to the form instance. Finally, invoking the render() method on the instance generates the HTML markup and inserts it into the Document Object Model(DOM). For a better understanding of how LuCI forms work, you can refer to the following : LuCI.form.

This is an example demonstrating the usage of LuCI.form within one of the admin’s views, using a small portion of the olsrd.js code. The full code for the file can be found here.

'use strict';
'require view';
'require form';
'require fs';
'require uci';
'require ui';
'require rpc';

return view.extend({
	callHasIpIp: rpc.declare({
		object: 'olsrinfo',
		method: 'hasipip',
	}),
	load: function () {
		return Promise.all([uci.load('olsrd').then(() => {
			var hasDefaults = false;

			uci.sections('olsrd', 'InterfaceDefaults', function (s) {
				hasDefaults = true;
				return false;
			});

			if (!hasDefaults) {
				uci.add('olsrd', 'InterfaceDefaults');
			}
		})]);
	},
	render: function () {
		var m, s, o;

		var has_ipip;

		m = new form.Map(
			'olsrd',
			_('OLSR Daemon'),
			_(
				'The OLSR daemon is an implementation of the Optimized Link State Routing protocol. ' +
					'As such it allows mesh routing for any network equipment. ' +
					'It runs on any wifi card that supports ad-hoc mode and of course on any ethernet device. ' +
					'Visit <a href="http://www.olsr.org">olsrd.org</a> for help and documentation.'
			)
		);


		s = m.section(form.TypedSection, 'olsrd', _('General settings'));
		s.anonymous = true;

		s.tab('general', _('General Settings'));
		s.tab('lquality', _('Link Quality Settings'));
		this.callHasIpIp()
		.then(function (res) {
			var output = res.result;
			has_ipip = output.trim().length > 0;
		})
		.catch(function (err) {
			console.error(err);
		})
		.finally(function () {
               
            //... This snippet represents only a small portion of the complete code.
	
		});

		s.tab('advanced', _('Advanced Settings'));

		var ipv = s.taboption('general', form.ListValue, 'IpVersion', _('Internet protocol'), _('IP-version to use. If 6and4 is selected then one olsrd instance is started for each protocol.'));
		ipv.value('4', 'IPv4');
		ipv.value('6and4', '6and4');

		var poll = s.taboption('advanced', form.Value, 'Pollrate', _('Pollrate'), _('Polling rate for OLSR sockets in seconds. Default is 0.05.'));
		poll.optional = true;
		poll.datatype = 'ufloat';
		poll.placeholder = '0.05';

		var nicc = s.taboption('advanced', form.Value, 'NicChgsPollInt', _('Nic changes poll interval'), _('Interval to poll network interfaces for configuration changes (in seconds). Default is "2.5".'));
		nicc.optional = true;
		nicc.datatype = 'ufloat';
		nicc.placeholder = '2.5';

		var tos = s.taboption('advanced', form.Value, 'TosValue', _('TOS value'), _('Type of service value for the IP header of control traffic. Default is "16".'));
		tos.optional = true;
		tos.datatype = 'uinteger';
		tos.placeholder = '16';

        //... This snippet represents only a small portion of the complete code.

		return m.render();
	},
});

Flexible Views

For enhanced flexibility in our pages, we have the option to manually define the HTML, which I have used in the status views. This approach allows us to have more control over the page structure and content, providing greater customization possibilities

This is an example demonstrating the usage of flexible views within one of the status’s views, using a small portion of the topology.js code. The full code for the file can be found here.

'use strict';
'require uci';
'require view';
'require poll';
'require rpc';
'require ui';


return view.extend({
	callGetJsonStatus: rpc.declare({
		object: 'olsrinfo',
		method: 'getjsondata',
		params: ['otable', 'v4_port', 'v6_port'],
	}),

	fetch_jsoninfo: function (otable) {
		var jsonreq4 = '';
		var jsonreq6 = '';
		var v4_port = parseInt(uci.get('olsrd', 'olsrd_jsoninfo', 'port') || '') || 9090;
		var v6_port = parseInt(uci.get('olsrd6', 'olsrd_jsoninfo', 'port') || '') || 9090;
		var json;
		var self = this;
		return new Promise(function (resolve, reject) {
			L.resolveDefault(self.callGetJsonStatus(otable, v4_port, v6_port), {})
				.then(function (res) {
					json = res;

					jsonreq4 = JSON.parse(json.jsonreq4);
					jsonreq6 = json.jsonreq6 !== '' ? JSON.parse(json.jsonreq6) : [];
					var jsondata4 = {};
					var jsondata6 = {};
					var data4 = [];
					var data6 = [];
					var has_v4 = false;
					var has_v6 = false;

					if (jsonreq4 === '' && jsonreq6 === '') {
						window.location.href = 'error_olsr';
						reject([null, 0, 0, true]);
						return;
					}

					if (jsonreq4 !== '') {
						has_v4 = true;
						jsondata4 = jsonreq4 || {};
						if (otable === 'status') {
							data4 = jsondata4;
						} else {
							data4 = jsondata4[otable] || [];
						}

						for (var i = 0; i < data4.length; i++) {
							data4[i]['proto'] = '4';
						}
					}

					if (jsonreq6 !== '') {
						has_v6 = true;
						jsondata6 = jsonreq6 || {};
						if (otable === 'status') {
							data6 = jsondata6;
						} else {
							data6 = jsondata6[otable] || [];
						}

						for (var j = 0; j < data6.length; j++) {
							data6[j]['proto'] = '6';
						}
					}

					for (var k = 0; k < data6.length; k++) {
						data4.push(data6[k]);
					}

					resolve([data4, has_v4, has_v6, false]);
				})
				.catch(function (err) {
					console.error(err);
					reject([null, 0, 0, true]);
				});
		});
	},
	action_topology: function () {
		var self = this;
		return new Promise(function (resolve, reject) {
			self
				.fetch_jsoninfo('topology')
				.then(function ([data, has_v4, has_v6, error]) {
					if (error) {
						reject(error);
					}

					function compare(a, b) {
						if (a.proto === b.proto) {
							return a.tcEdgeCost < b.tcEdgeCost;
						} else {
							return a.proto < b.proto;
						}
					}

					data.sort(compare);

					var result = { routes: data, has_v4: has_v4, has_v6: has_v6 };
					resolve(result);
				})
				.catch(function (err) {
					reject(err);
				});
		});
	},
	load: function () {
		return Promise.all([uci.load('olsrd'), uci.load('luci_olsr')]);
	},
	render: function () {
		var routes_res;
		var has_v4;
		var has_v6;

		return this.action_topology()
			.then(function (result) {
				routes_res = result.routes;
				has_v4 = result.has_v4;
				has_v6 = result.has_v6;
				var table = E('div', { 'class': 'table cbi-section-table' }, [
					E('div', { 'class': 'tr cbi-section-table-titles' }, [
						E('div', { 'class': 'th cbi-section-table-cell' }, _('OLSR node')),
						E('div', { 'class': 'th cbi-section-table-cell' }, _('Last hop')),
						E('div', { 'class': 'th cbi-section-table-cell' }, _('LQ')),
						E('div', { 'class': 'th cbi-section-table-cell' }, _('NLQ')),
						E('div', { 'class': 'th cbi-section-table-cell' }, _('ETX')),
					]),
				]);
				var i = 1;

				for (var k = 0; k < routes_res.length; k++) {
					var route = routes_res[k];
					var cost = (parseInt(route.tcEdgeCost) || 0).toFixed(3);
					var color = etx_color(parseInt(cost));
					var lq = (parseInt(route.linkQuality) || 0).toFixed(3);
					var nlq = (parseInt(route.neighborLinkQuality) || 0).toFixed(3);

					var tr = E('div', { 'class': 'tr cbi-section-table-row cbi-rowstyle-' + i + ' proto-' + route.proto }, [
						route.proto === '6'
							? E('div', { 'class': 'td cbi-section-table-cell left' }, [E('a', { 'href': 'http://[' + route.destinationIP + ']/cgi-bin-status.html' }, route.destinationIP)])
							: E('div', { 'class': 'td cbi-section-table-cell left' }, [E('a', { 'href': 'http://' + route.destinationIP + '/cgi-bin-status.html' }, route.destinationIP)]),
						route.proto === '6'
							? E('div', { 'class': 'td cbi-section-table-cell left' }, [E('a', { 'href': 'http://[' + route.lastHopIP + ']/cgi-bin-status.html' }, route.lastHopIP)])
							: E('div', { 'class': 'td cbi-section-table-cell left' }, [E('a', { 'href': 'http://' + route.lastHopIP + '/cgi-bin-status.html' }, route.lastHopIP)]),
						E('div', { 'class': 'td cbi-section-table-cell left' }, lq),
						E('div', { 'class': 'td cbi-section-table-cell left' }, nlq),
						E('div', { 'class': 'td cbi-section-table-cell left', 'style': 'background-color:' + color }, cost),
					]);

					table.appendChild(tr);
					i = (i % 2) + 1;
				}

				var fieldset = E('fieldset', { 'class': 'cbi-section' }, [E('legend', {}, _('Overview of currently known OLSR nodes')), table]);

                //... This snippet represents only a small portion of the complete code.

				var result = E([], {}, [h2, divToggleButtons, fieldset, statusOlsrLegend, statusOlsrCommonJs]);

				return result;
			})
			.catch(function (error) {
				console.error(error);
			});
	},
	handleSaveApply: null,
	handleSave: null,
});

RPCD: OpenWrt ubus RPC daemon

rpcd is the OpenWrt ubus RPC daemon responsible for the backend server. To enable the exposure of shell script functionality via ubus, the rpcd plugin utilizes executable files located in the /usr/libexec/rpcd/ directory. When rpcd is triggered, it runs these executables, allowing the execution of various methods. For instance, consider the file root/usr/libexec/rpcd/olsrinfo.sh Here we are creating two new ubus methods getjsondata & hasipip, of the object olsrinfo

#!/bin/sh                                                                                                                                                                
. /usr/share/libubox/jshn.sh                                                                                                                                             
. /lib/functions.sh                                                                                                                                                      
                                                                                                                                                                         
case "$1" in                                                                                                                                                             
  list)                                                                                                                                                                  
    json_init
    json_add_object "getjsondata"
    json_add_string 'otable' 'String'
    json_add_int 'v4_port' 'Integer'
    json_add_int 'v6_port' 'Integer'
    json_close_object
	json_add_object "hasipip"
	json_close_object
    json_dump
    ;;                                                                                                                                                                   
  call)                                                                                                                                                                  
    case "$2" in                                                                                                                                                         
      getjsondata)                                                                                                                                                       
        json_init                                                                                                                                                        
        json_load "$(cat)"                                                                                                                                               
        json_get_var otable  otable                                                                                                                                      
        json_get_var v4_port v4_port                                                                                                                                     
        json_get_var v6_port v6_port                                                                                                                                     
                                                                                                                                                                         
        jsonreq4=$(echo "/${otable}" | nc 127.0.0.1 "${v4_port}" | sed -n '/^[}{ ]/p' 2>/dev/null)                                                                       
        jsonreq6=$(echo "/${otable}" | nc ::1 "${v6_port}" | sed -n '/^[}{ ]/p' 2>/dev/null)                                                                             
                                                                                                                                                                         
        json_init                                                                                                                                                        
        json_add_string "jsonreq4" "$jsonreq4"                                                                                                                           
        json_add_string "jsonreq6" "$jsonreq6"                                                                                                                           
        json_dump                                                                                                                                                        
        ;;
	 hasipip)
        result=$(ls /etc/modules.d/ | grep -E "[0-9]*-ipip")
        json_init
        json_add_string "result" "$result"
        json_dump
        ;;                                                                                                                                                               
    esac                                                                                                                                                                 
    ;;                                                                                                                                                                   
esac  

We use these methods by declaring an rpc as follows & then by calling them which I’ve shown in the topology.js code.

callGetJsonStatus: rpc.declare({
		object: 'olsrinfo',
		method: 'getjsondata',
		params: ['otable', 'v4_port', 'v6_port'],
	})

Feel free to reach out to me via email if you have any doubts or questions. I’m here to help! Stay tuned for more valuable content as I continue to share useful information and resources. Thank you for your support!

GSoC’23 : LuCI Migrate to JavaScript-Based Framework

Project Details

LuCI is an open-source framework that is widely used to build web interfaces for embedded devices such as WiFi routers. In the CBI based old system, pages were rendered on the router and delivered as HTML to the browser, which causes a higher load on the embedded devices. This makes the system less efficient and can lead to performance issues.

To facilitate this migration, LuCI provides LuCI-JavaScript API that will be used to build web interfaces that can be rendered in the browser. Additionally, data will be provided via RPCD and UBUS. The project will involve writing new RPCD services to provide data to the client side that was formerly used directly on the router.

Project Goals

The migration of LuCI to a JavaScript-based framework will bring numerous advantages to the OpenWrt community and other users of OpenWrt-based devices. One of the primary benefits is enhanced performance and reduced load on embedded devices, such as WiFi routers. By shifting the rendering of pages to the client-side using JavaScript, instead of on the router, the workload on the router will be decreased, resulting in a better user experience, particularly for users with lower-specification routers.

Another benefit of the new system is increased flexibility for developers. The utilization of a client-side JavaScript framework provides developers with more options for customization and extension of the LuCI web interface in the future. It also establishes a standardized approach for developers to interact with the router’s services, retrieve or set configuration data, and facilitate the development and maintenance of LuCI-based applications.

Community networks, which often rely on lower-specification devices, can greatly benefit from these improvements. The improved performance and reduced load on devices will make it easier for community networks to manage and maintain their networks using LuCI-based tools.

In summary, the migration of LuCI to a JavaScript-based framework will bring significant benefits to the community and users of OpenWrt-based devices. These benefits include improved performance, increased flexibility for developers, and potentially easier management of LuCI-based applications for community networks.

Project Progress

I successfully migrated luci-app-uhttpd to JavaScript, gaining valuable experience and insights from the process which will help to migrate more advanced applications. It improved performance, enhanced user experience, and provided with greater flexibility as a developer. I’m excited to continue contributing to the growth of LuCI and further advancing OpenWrt.

I am currently working on the migration of luci-app-olsr to a JavaScript-based framework. It has been an engaging and exciting experience so far. By leveraging JavaScript, I aim to enhance the performance, usability, and customization options of the web interface for olsrd. I am excited to contribute to the improvement of this essential tool for mesh routing and network management on OpenWrt-based devices.

Community Bonding Period

During the GSoC community bonding period, I have had an incredible learning experience. I have been fortunate to have constant communication and guidance from my mentor, Andreas Bräu, who has been exceptionally supportive throughout the process. Whenever I face challenges or got stuck, my mentor is there to provide valuable insights and assistance. Additionally, this journey has allowed me to become more familiar with the OpenWrt and Freifunk communities, providing me with a broader understanding of the ecosystem and related technologies. The community bonding period has been instrumental in preparing me for the successful migration of future applications and has fostered valuable connections within the community.

VRConfig Update 2

Hi,

I spent the last weeks mainly developing the LuCI Application for VRConfig. As soon as you want to do advanced things with LuCI, it gets cumbersome.
As the API is mostly undocumented, you have to dig through the LuCI’s source code trying out functions which could be useful according to their name.
It’s a bit of a trial and error game.
Currently the LuCI app does the following.
It displays an image of the router and parses the JSON file, which contains the locations of the components.
With this information it can mark the associated physical ports to the currently selected network interface and display those network ports, which are connected to a cable. You can also hover over the components and click on them, which leads you to their respective settings page.

I also improved the annotation app. It now lets you choose the router name from a list of all currently supported router models of OpenWrt. I got that list with a series of grep and sed commands from the OpenWrt git repository.
For your information, there are currently around 1100 different router models supported. 🙂

In the next weeks I will polish the LuCI Application and try to integrate VRConfig into the openwrt build system to be able to select the correct router image and JSON file at build time.

VRConfig Update

Hi,

I have some quick updates about VRConfig for you.
Short recap: VRConfig aims to introduce a graphical configuration mode for OpenWrt’s Webinterface LuCI.
For that need to collect pictures of the backside of all supported routers. The idea is to do this in a crowdsourcing manner. The community can submit pictures of their routers together with a metadata file which contains the locations of the components on the picture.

I spent the last weeks developing a web application to provide the annotation functionality of the router components.
A working prototype is now ready and can be tested at the following URL: https://vrconfig.gitlab.io/annotator/
Source code: https://gitlab.com/vrconfig/annotator

The annotator produces a JSON file which in turn can be parsed by the LuCI Application to provide the graphical configuration mode.

The LuCI application is being developed right now and will be provided shorty under the following URL: https://gitlab.com/vrconfig/luci-app-vrconfig

More info about that in the next blog post.

VRConfig – Visual Router Configuration for OpenWrt

Hi,

I am Tobias, a Computer Science student at the TU-Berlin. This is my second time participating in GSoC for Freifunk.
I am excited about this project as it helps to reduce the entry barrier for inexperienced users of OpenWrt and its web interface LuCI.

When you look at the current LuCI Webinterface you will notice that it looks fairly decent, especially with the Material Theme.
However for an inexperienced user without a technical background it surely looks scary. All the text full of technical terms with few pictures can look like a book with seven seals.

This project aims to introduce a graphical configuration mode.
To make the configuration interface more connected to the actual router the user owns, we want to display an image of the backside of the ports in the web interface.
The user shall be able to interact with this graphical representation of the router by hovering and clicking on the different parts like LAN ports, antenna etc.

What are the necessary steps to archive this goal?

First, we need pictures of the backside of all the different router models. Here the idea is to collect them via crowdsourcing by the community. Everyone can take a picture of their router and upload it to a Git repository. Also, the location of of the router components must be marked on every picture. For that I will develop a small application which allows the user to annotate a router picture and generates a metadata file.
Second, the annotated pictures need to be integrated into the OpenWrt buildsystem.
Third, a LuCI application needs to be developed to display the result as an interactive graphic in the web interface.

In the next blog post I will go into more detail on the individual steps as well as update you about the progress.

GSoC2014 – BGP/Bird integration with OpenWRT and QMP

This year, the Google Summer of Code and FreiFunk have given to Guifi.net and QMP communities the opportunity to develope a project in this event. This project is: BGP/Bird integration with OpenWRT and QMP.

 

A brief description of the project

Most of the community networks run dynamic routing protocols (OLSR, BMX6, BATMAN-ADV, etc.) with non-dynamic ones (BGP, OSPF, etc.). Guifi.net (BGP) and QMP (BMX6) are a scenario where this collision of metrics and routes happens. 

Furthermore, these communities are using Quagga for the BGP routing, which is a complex and oversized tool for the type of nodes that will work with it. For these kind of nodes, Bird is a really lightweight BGP daemon that is still not well supported to be used easily by the community (it does not have an easy and graphical configuration system yet).

 

This project will contribute with:

1st Block: Adapting Bird to fit into QMP firmware. (1 month)

  • Bird daemon improvements (3 weeks)
    • Give to Bird integration with OpenWRT: Add support for UCI configuration. Thus, will ease the configuring process and become closer to non-expert users.
    • Improve Bird UCI configuration adding LUCI support (web graphical interface). Adding a graphical user-friendly interface and the necessary tools to automate hardest processes of the configuration, non-expert community users could find this daemon as an easy to use routing tool.
  • Adapt Bird daemon to QMP (1 week)
    • Once working on OpenWRT and with an easy ‘to put on work’ configuration, add integration with the QMP firmware owing to replace Quagga’s routing functions in frontier nodes of the QMP network.

 

2n Block: Automate the translations of routes and metrics between a non-dynamic routing protocol and a dynamic one. (~2 months)

  • Creation of a routes and metrics exchange system (7 weeks)
    • Study and build an exchange system for metrics and routes between BGP (from Guifi.net) and BMX6 (from QMP).
    • Test and debug this solution in the real scenario (Guifi.net-QMP)

 

3rd Block: Project basic feedback and documentation (1 week)

  • Documentation, user support&feedback and results presentation.

 

About the author

I am Eloi Carbó, a Computer Science student specialized in Information Technology in the UPC of Barcelona. Currently I am working on my Final Degree dissertation: UPC CN-A testbed mesh network deployment, monitoring and validation. Using the Wibed Platform developed by the CONFINE Project [Link: https://wiki.confine-project.eu/wibed:start].

 

About the project collaborators

The project tutors are Roger Baig (fundació Guifi.net) and Axel Neumann (Freifunk and BMX6 support) and the special collaboration of Pau Escrich (fundació Guifi.net and QMP project).

IPv6 and TLS capable network-superserver in Lua and C with HTTP and RPC Slave

The summer of code project of Steven Barth aka Cyrus is about planning and implementing an IPv6 and TLS capable
superserver in Lua as well as an HTTP/1.1-Server working on top of it
using the LuCI HTTP-Stack. This application will serve LuCI – the
Freifunk Firmware web user interface – and replace the currently used
slower CGI-solution without IPv6 and encryption support. Additionally
an RPC-Server will be built to allow remote administration of one or
more OpenWrt devices in a standardized way using JSON-RPC over TCP.

LuCId HttpD

The results of the summer work of Cyrus is pretty overwhelming. There is for example nixio, the new POSIX Lua library which will help us getting rid of the Lua 3rd
party library mess. And based on that there is also LuCId – which was described in the GSoC project. It brings us a new efficient HTTP-server. Some people may have
discovered that Cyrus already checked in things into trunk ocassionally. Also SSL support is working. Another nice new feature is native
support for creating wizards which will be used in the near future. The results of LuCId are already being tested in productive environments. They are performing well. Kernel mode
IO and TLS encryption function well. Special thanks for the achievements also go to John Crispin aka BLogic who is the mentor of Steve during the summer.

Links

OpenWrt team announces OpenWrt Kamikaze 808 Release with Luci Interface

The OpenWrt team (Cph) has announced a new version of its Linux distribution for embedded wireless devices named "OpenWrt Kamikaze 808 Release". I talked to Felix Fietkau already at the WCW. Unfortunately we did not have the time to do an interview at the end. But Cyrus from freifunk Halle gave a short showcase of his interface (in German). The OpenWrt team was also impressed by it and they now announce the enclosure of the Luci interface officially. Congratulations Cyrus!

It has been quite a while since OpenWrt had a new Kamikaze release. The developer team has decided that it is time to get things straight and focus on a new release. This release have the official name: OpenWrt Kamikaze 808 Release.

The schedule will look like this:
*Last day in July – final release candidate: 808 RC-1 808 RC-1 will be a feature freeze, and all changes after this point will be bug fixes.
*Last day in August – final release: OpenWrt Kamikaze 808 Release.

OpenWrt Kamikaze 808 Release will focus on bringing the following:
– Firewall rewrite
– Broadcom 47xx running reliably with the new Kernel, not including wifi
– IMQ and Traffic shaping tested with newer kernels, especially 2.6.25
– Sysupgrade for more platforms (x86 is tested again)
– The new web interface (LuCI, Lua Configuration Interface)
– Attention towards the integration of security updates
– Package maintaining and updates between releases
– Testing, testing and lots of testing…

The 808 Release will also include support for several new platforms/targets. (http://forum.openwrt.org/viewtopic.php?pid=69873 )