How to Build a WI-FI Dashboard Using Node.js and Ractive.js
Key Takeaways
- Utilize Node.js to build a server that extends existing functionalities to include Wi-Fi network information, making the dashboard more comprehensive.
- Implement Ractive.js for the client-side to manage real-time updates and interactions more efficiently, enhancing user experience with dynamic content updates.
- Process Wi-Fi command outputs on the server to extract and display available network details, ensuring data is structured and ready for client-side use.
- Design and use HTML templates with Ractive.js to display Wi-Fi network data, enabling interactive and responsive web elements that improve navigation and usability.
- Expand the dashboard’s capabilities by adding new endpoints and handling AJAX calls effectively, ensuring the dashboard remains scalable and maintainable.

Server
On the server side, we’re going to reuse and extend what we’ve created for the battery viz. In this tutorial we’ll focus on Ubuntu but the server code is structured in such a way that you’ll need to write just a couple of adapters to support Mac or Windows’ machines.Bash Command
To start, we extend the original configuration method by adding commands and callbacks for the new endpoint. Some renaming has been necessary to prevent clashes with commands for the battery widget.<span>function switchConfigForCurrentOS () { </span> <span>switch(process.platform) { </span> <span>case 'linux': </span> <span>return { </span> <span>batteryCommand: 'upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep -E "state|time to empty|to full|percentage"', </span> <span>batteryProcessFunction: processBatteryStdoutForLinux, </span> <span>wifiCommand: 'iwlist wlan0 scanning | egrep "Cell |Address|Channel|Frequency|Encryption|Quality|Signal level|Last beacon|Mode|Group Cipher|Pairwise Ciphers|Authentication Suites|ESSID"', </span> <span>wifiProcessFunction: processWifiStdoutForLinux </span> <span>}; </span> <span>case 'darwin': //MAc OsX </span> <span>... </span> <span>} </span> <span>}</span>
Processing Command Output
The way we process the command output is really similar to what we’ve already done for battery. We go through the output line by line and process it to extract meaningful parameters from our readings. But in this case we’re getting readings about a list of items, not a single one! So we need to identify when a new item actually starts in the output, and create a new object for each item. Then we’ll filter valid lines, adding the properties we read to our current item.<span>function processWifiStdoutForLinux(stdout) { </span> <span>var networks = {}; </span> <span>var net_cell = ""; </span> <span>var cell = {}; </span> stdout<span>.split('\n').map(trimParam).forEach(function (line) { </span> <span>if (line.length > 0) { </span> <span>//check if the line starts a new cell </span> <span>if (stringStartsWith(line, NET_CELL_PREFIX)) { </span> <span>if (net_cell.length > 0) { </span> networks<span>[net_cell] = mapWifiKeysForLinux(cell); </span> <span>} </span> cell <span>= {}; </span> line <span>= line.split("-"); </span> net_cell <span>= line[0].trim(); </span> line <span>= line[1]; </span> <span>} </span> <span>//Either way, now we are sure we have a non empty line with (at least one) key-value pair </span> <span>// and that cell has been properly initialized </span> <span>processWifiLineForLinux(cell, line); </span> <span>} </span> <span>}); </span> <span>if (net_cell.length > 0) { </span> networks<span>[net_cell] = mapWifiKeysForLinux(cell); </span> <span>} </span> <span>return networks; </span> <span>}</span>
- Since we add a cell to our hash only when the description of the next one starts, we would otherwise miss the final if statement (to capture the last network in the output).
- The code above assumes that two cells can’t share the same name. This is a reasonable assumption because networks are not indexed by their name (that info is captured by the ESSID field). They are listed and assigned a progressive identifier “Cell 0X”.
- The last thing we do before storing properties is a call to mapWifiKeysForLinux and in this case they just return the keys unaltered.
<span>function switchConfigForCurrentOS () { </span> <span>switch(process.platform) { </span> <span>case 'linux': </span> <span>return { </span> <span>batteryCommand: 'upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep -E "state|time to empty|to full|percentage"', </span> <span>batteryProcessFunction: processBatteryStdoutForLinux, </span> <span>wifiCommand: 'iwlist wlan0 scanning | egrep "Cell |Address|Channel|Frequency|Encryption|Quality|Signal level|Last beacon|Mode|Group Cipher|Pairwise Ciphers|Authentication Suites|ESSID"', </span> <span>wifiProcessFunction: processWifiStdoutForLinux </span> <span>}; </span> <span>case 'darwin': //MAc OsX </span> <span>... </span> <span>} </span> <span>}</span>
Endpoint
Adding the new endpoint to our server is trivial thanks to Node’s HHTP module and the helper methods we’ve created in the previous tutorial. We just need to define the Regular Expression for the paths we want to respond to, and add an if statement to the server callback, triggered on incoming requests:<span>function processWifiStdoutForLinux(stdout) { </span> <span>var networks = {}; </span> <span>var net_cell = ""; </span> <span>var cell = {}; </span> stdout<span>.split('\n').map(trimParam).forEach(function (line) { </span> <span>if (line.length > 0) { </span> <span>//check if the line starts a new cell </span> <span>if (stringStartsWith(line, NET_CELL_PREFIX)) { </span> <span>if (net_cell.length > 0) { </span> networks<span>[net_cell] = mapWifiKeysForLinux(cell); </span> <span>} </span> cell <span>= {}; </span> line <span>= line.split("-"); </span> net_cell <span>= line[0].trim(); </span> line <span>= line[1]; </span> <span>} </span> <span>//Either way, now we are sure we have a non empty line with (at least one) key-value pair </span> <span>// and that cell has been properly initialized </span> <span>processWifiLineForLinux(cell, line); </span> <span>} </span> <span>}); </span> <span>if (net_cell.length > 0) { </span> networks<span>[net_cell] = mapWifiKeysForLinux(cell); </span> <span>} </span> <span>return networks; </span> <span>}</span>
<span>function processWifiLineForLinux(cell<span>, line</span>) { </span> <span>var key; </span> <span>var val; </span> line <span>= line.trim(); </span> <span>if (line.length > 0) { </span> <span>switch (true) { </span> <span>case stringStartsWith(line, NET_ADDRESS_PREFIX): </span> line <span>= line.split(':'); </span> line<span>.splice(0, 1); </span> <span>//INVARIANT: Address in the format Address: DC:0B:1A:47:BA:07 </span> <span>if (line.length > 0) { </span> cell<span>[NET_ADDRESS_PREFIX] = line.join(":"); </span> <span>} </span> <span>break; </span> <span>case stringStartsWith(line, NET_QUALITY_PREFIX): </span> <span>//INVARIANT: this line must have a similar format: Quality=41/70 Signal level=-69 dBm </span> line <span>= line.split(NET_SIGNAL_PREFIX); </span> cell<span>[NET_QUALITY_PREFIX] = line[0].split("=")[1].trim(); </span> <span>if (line.length > 1) { </span> cell<span>[NET_SIGNAL_PREFIX] = line[1].split("=")[1].trim(); </span> <span>} </span> <span>break; </span> <span>case stringStartsWith(line, NET_EXTRA_PREFIX): </span> <span>//INVARIANT: this line must have a similar format: Extra: Last beacon: 1020ms ago </span> line <span>= line.split(":"); </span> <span>//we can ignore the prefix of the string </span> <span>if (line.length > 2) { </span> cell<span>[line[1].trim()] = line[2].trim(); </span> <span>} </span> <span>break; </span> <span>default: </span> <span>//INVARIANT: the field must be formatted as "key : value" </span> line <span>= line.split(":"); </span> <span>if (line.length > 1) { </span> <span>//Just stores the key-value association, so that coupling with client is reduced to the min: </span> <span>//values will be examined only on the client side </span> cell<span>[line[0].trim()] = line[1].trim(); </span> <span>} </span> <span>} </span> <span>} </span> <span>return cell; </span> <span>}</span>
Client
Now, let me introduce you the funniest part of this example. We’re going to massively use Ractive.js for the Web client. It is a lightweight, powerful framework that combines two-way binding (AngularJS-style) with HTML templates (like mustache or Handlebars). The stress on templates (even more than AngularJS, way more than React), is indeed one of Ractive.js’ hallmarks, together with its blazingly fast performance, as a result of a clever engine that always computes the smallest possible DOM elements to be refreshed when data change. We’re going to add two panels to our dashboard:- One for the list of networks in our surroundings (showing a brief summary for each item).
- Another one that only appears once a network is selected and displays detailed information for that WI-FI connection.
Template
Let’s start by discussing the HTML templates to display our data, and then we’ll see how to bind server’s data to them.Wi-Fi List
The most complex template we need is the one showing the list of the available networks. The first dozen of lines just define the container panel, and use Ractive.js’ binding to conditionally show an icon warning about server errors, and a button to pause/resume server polling:<span>var server = http.createServer(function (request<span>, response</span>) { </span> <span>var requestUrl = request.url; </span> <span>var filePath = BASE_URL + requestUrl; </span> <span>if (requestUrl === '/' || requestUrl === '') { </span> response<span>.writeHead(301, </span> <span>{ </span> <span>Location: BASE_URL + 'public/demo.html' </span> <span>}); </span> response<span>.end(); </span> <span>} else if (RE_BATTERY.test(requestUrl)) { </span> <span>getBatteryStatus(response, onBatteryInfo, onError); </span> <span>} else if (RE_NETWORKS.test(requestUrl)) { </span> <span>getWifiStatus(response, onWifiInfo, onError); </span> <span>} </span> <span>... </span> <span>}</span>
The second part of the template is much more interesting. We iterate through the list of networks with {{#wifiNetworks: num}}, capturing the index of each item in the num variable. For each item in the list, we add a callback handling clicks (see below) and show a summary of its values. Notice how closing tags don’t have to match opening tags text:
<span>function switchConfigForCurrentOS () { </span> <span>switch(process.platform) { </span> <span>case 'linux': </span> <span>return { </span> <span>batteryCommand: 'upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep -E "state|time to empty|to full|percentage"', </span> <span>batteryProcessFunction: processBatteryStdoutForLinux, </span> <span>wifiCommand: 'iwlist wlan0 scanning | egrep "Cell |Address|Channel|Frequency|Encryption|Quality|Signal level|Last beacon|Mode|Group Cipher|Pairwise Ciphers|Authentication Suites|ESSID"', </span> <span>wifiProcessFunction: processWifiStdoutForLinux </span> <span>}; </span> <span>case 'darwin': //MAc OsX </span> <span>... </span> <span>} </span> <span>}</span>
Selected Wi-Fi Details
<span>function processWifiStdoutForLinux(stdout) { </span> <span>var networks = {}; </span> <span>var net_cell = ""; </span> <span>var cell = {}; </span> stdout<span>.split('\n').map(trimParam).forEach(function (line) { </span> <span>if (line.length > 0) { </span> <span>//check if the line starts a new cell </span> <span>if (stringStartsWith(line, NET_CELL_PREFIX)) { </span> <span>if (net_cell.length > 0) { </span> networks<span>[net_cell] = mapWifiKeysForLinux(cell); </span> <span>} </span> cell <span>= {}; </span> line <span>= line.split("-"); </span> net_cell <span>= line[0].trim(); </span> line <span>= line[1]; </span> <span>} </span> <span>//Either way, now we are sure we have a non empty line with (at least one) key-value pair </span> <span>// and that cell has been properly initialized </span> <span>processWifiLineForLinux(cell, line); </span> <span>} </span> <span>}); </span> <span>if (net_cell.length > 0) { </span> networks<span>[net_cell] = mapWifiKeysForLinux(cell); </span> <span>} </span> <span>return networks; </span> <span>}</span>
JavaScript
We’ll setup a polling daemon that asynchronously queries the server at given intervals of time. Every Ajax call will provide the updated list of WI-FI networks. All we have to do when we receive a JSON response from the server, is to acknowledge we received a successful response and update the fields in which we store the list of networks inside the ractive object.Setup
As we’ve shown in the previous article, to bind a template to some data, we just need to create a new Ractive object, hook it up with the template’s ID (#meterVizTemplate below), and the target DOM elements, i.e. the node that is going to be the template’s parent in the DOM tree (panels below). Then we just need to add all the objects or values we want to use in the template as fields of ractive.data. This can be done on initialization (as below) or later, using ractive.set().<span>function processWifiLineForLinux(cell<span>, line</span>) { </span> <span>var key; </span> <span>var val; </span> line <span>= line.trim(); </span> <span>if (line.length > 0) { </span> <span>switch (true) { </span> <span>case stringStartsWith(line, NET_ADDRESS_PREFIX): </span> line <span>= line.split(':'); </span> line<span>.splice(0, 1); </span> <span>//INVARIANT: Address in the format Address: DC:0B:1A:47:BA:07 </span> <span>if (line.length > 0) { </span> cell<span>[NET_ADDRESS_PREFIX] = line.join(":"); </span> <span>} </span> <span>break; </span> <span>case stringStartsWith(line, NET_QUALITY_PREFIX): </span> <span>//INVARIANT: this line must have a similar format: Quality=41/70 Signal level=-69 dBm </span> line <span>= line.split(NET_SIGNAL_PREFIX); </span> cell<span>[NET_QUALITY_PREFIX] = line[0].split("=")[1].trim(); </span> <span>if (line.length > 1) { </span> cell<span>[NET_SIGNAL_PREFIX] = line[1].split("=")[1].trim(); </span> <span>} </span> <span>break; </span> <span>case stringStartsWith(line, NET_EXTRA_PREFIX): </span> <span>//INVARIANT: this line must have a similar format: Extra: Last beacon: 1020ms ago </span> line <span>= line.split(":"); </span> <span>//we can ignore the prefix of the string </span> <span>if (line.length > 2) { </span> cell<span>[line[1].trim()] = line[2].trim(); </span> <span>} </span> <span>break; </span> <span>default: </span> <span>//INVARIANT: the field must be formatted as "key : value" </span> line <span>= line.split(":"); </span> <span>if (line.length > 1) { </span> <span>//Just stores the key-value association, so that coupling with client is reduced to the min: </span> <span>//values will be examined only on the client side </span> cell<span>[line[0].trim()] = line[1].trim(); </span> <span>} </span> <span>} </span> <span>} </span> <span>return cell; </span> <span>}</span>
Daemons
We’ll use the same mechanism for the daemon and to pause/restart querying the server as we did for the battery. For the sake of brevity we won’t repeat it here, but if you want to deepen this topic you can take a look at this article or to the GitHub repository.Ajax Calls
The only thing that our new daemon does, is making an Ajax call and then updating our data in case of success or the field signaling network problems, in case of errors.<span>var server = http.createServer(function (request<span>, response</span>) { </span> <span>var requestUrl = request.url; </span> <span>var filePath = BASE_URL + requestUrl; </span> <span>if (requestUrl === '/' || requestUrl === '') { </span> response<span>.writeHead(301, </span> <span>{ </span> <span>Location: BASE_URL + 'public/demo.html' </span> <span>}); </span> response<span>.end(); </span> <span>} else if (RE_BATTERY.test(requestUrl)) { </span> <span>getBatteryStatus(response, onBatteryInfo, onError); </span> <span>} else if (RE_NETWORKS.test(requestUrl)) { </span> <span>getWifiStatus(response, onWifiInfo, onError); </span> <span>} </span> <span>... </span> <span>}</span>
2. You can trust the server you are calling. Since we’re not using user provided content for the URL, one would think that it should not be a concern. However, if our server was to be compromised, then we would have no barrier to protect us from injected code. If an explicit 'dataType' header is not set, then jQuery will try to guess the content from the response, and a response from a malicious server might contain JavaScript code. Although this possibility is not so common, we can’t rule it out completely. For this reason, it’s not a bad idea adding an extra layer of protection at the price of a little more typing.
Updating the Dashboard
The most relevant add-on for this step will be that we respond to clicks on the list and show details for the selected network:<span>function switchConfigForCurrentOS () { </span> <span>switch(process.platform) { </span> <span>case 'linux': </span> <span>return { </span> <span>batteryCommand: 'upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep -E "state|time to empty|to full|percentage"', </span> <span>batteryProcessFunction: processBatteryStdoutForLinux, </span> <span>wifiCommand: 'iwlist wlan0 scanning | egrep "Cell |Address|Channel|Frequency|Encryption|Quality|Signal level|Last beacon|Mode|Group Cipher|Pairwise Ciphers|Authentication Suites|ESSID"', </span> <span>wifiProcessFunction: processWifiStdoutForLinux </span> <span>}; </span> <span>case 'darwin': //MAc OsX </span> <span>... </span> <span>} </span> <span>}</span>
- Call a method that would take the selected network’s ID;
- Use it to find the network object for that ID (likely stored in a dictionary);
- Find the DOM element for the “selected network panel”;
- Remove the old DOM tree inside the panel and iteratively create a new list displaying the key-value associations, mixing a lot of HTML strings inside our JavaScript code.
Conclusions
Finished! We have our dashboard “pimped”. As I said in the introduction, this is just a starting point.If you have followed along, you should now be able to easily display lists of complex items, handle item selection, and safely communicate to the server. You can use these skills for a number of other tasks, not necessarily involving displaying stats for your laptop. From showing a list of restaurants around the user to enumerating home appliances, you can control all through a Web interface or your mobile. The choice is yours, and there is no limit. If you want to deepen the topics covered in this article, I suggest you to take a look at these good resources:
- Creating a Battery viz Using Node.js: Getting Started and Server
- Interactive tutorial about Ractive.js
- Jquery $.getJSON method
- Discussion on Stackoverflow about jQuery.get() method
Frequently Asked Questions (FAQs) on Building a Wi-Fi Dashboard
What are the prerequisites for building a Wi-Fi dashboard?
To build a Wi-Fi dashboard, you need to have a basic understanding of JavaScript and Node.js. You also need to have Node.js and npm (Node Package Manager) installed on your computer. If you don’t have these installed, you can download them from the official Node.js website. Additionally, you’ll need a text editor to write your code. You can use any text editor of your choice, but some popular ones include Visual Studio Code, Atom, and Sublime Text.
How can I install the node-wifi module?
You can install the node-wifi module using npm, which is a package manager for Node.js. Open your terminal or command prompt and navigate to the directory where you want to install the module. Then, run the command ‘npm install node-wifi’. This will download and install the node-wifi module in your current directory.
How can I connect to a Wi-Fi network using the node-wifi module?
The node-wifi module provides a ‘connect’ function that you can use to connect to a Wi-Fi network. You need to pass an object to this function that contains the SSID and password of the network. Here’s an example:
var wifi = require('node-wifi');
wifi.connect({ ssid: 'your network name', password: 'your password' }, function(err) {
if (err) {
console.log(err);
}
console.log('Successfully connected to the network');
});
How can I scan for available Wi-Fi networks?
The node-wifi module provides a ‘scan’ function that you can use to scan for available Wi-Fi networks. This function returns an array of networks. Each network is an object that contains information such as the SSID, signal strength, and security type. Here’s an example:
var wifi = require('node-wifi');
wifi.scan(function(err, networks) {
if (err) {
console.log(err);
}
console.log(networks);
});
How can I disconnect from a Wi-Fi network?
The node-wifi module provides a ‘disconnect’ function that you can use to disconnect from a Wi-Fi network. You don’t need to pass any arguments to this function. Here’s an example:
var wifi = require('node-wifi');
wifi.disconnect(function(err) {
if (err) {
console.log(err);
}
console.log('Successfully disconnected from the network');
});
How can I get the current Wi-Fi status?
The node-wifi module provides a ‘getCurrentConnections’ function that you can use to get the current Wi-Fi status. This function returns an array of networks that the computer is currently connected to. Here’s an example:
var wifi = require('node-wifi');
wifi.getCurrentConnections(function(err, currentConnections) {
if (err) {
console.log(err);
}
console.log(currentConnections);
});
How can I handle errors in the node-wifi module?
The node-wifi module follows the standard Node.js error handling pattern. All functions take a callback as the last argument. This callback is a function that takes two arguments: an error object and the result. If an error occurs, the error object will contain information about the error. Otherwise, the error object will be null and the result will contain the result of the operation.
Can I use the node-wifi module to manage Wi-Fi networks on all operating systems?
The node-wifi module is designed to work on Windows, macOS, and Linux. However, the functionality may vary slightly between different operating systems due to differences in how they manage Wi-Fi networks.
Can I use the node-wifi module with other Node.js modules?
Yes, you can use the node-wifi module with other Node.js modules. For example, you can use it with the express module to create a web server that displays the available Wi-Fi networks.
How can I contribute to the node-wifi module?
The node-wifi module is an open-source project, and contributions are welcome. You can contribute by reporting bugs, suggesting new features, improving the documentation, or writing code. To contribute, you can fork the project on GitHub, make your changes, and then submit a pull request.
The above is the detailed content of How to Build a WI-FI Dashboard Using Node.js and Ractive.js. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Frequently Asked Questions and Solutions for Front-end Thermal Paper Ticket Printing In Front-end Development, Ticket Printing is a common requirement. However, many developers are implementing...

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

There is no absolute salary for Python and JavaScript developers, depending on skills and industry needs. 1. Python may be paid more in data science and machine learning. 2. JavaScript has great demand in front-end and full-stack development, and its salary is also considerable. 3. Influencing factors include experience, geographical location, company size and specific skills.

Learning JavaScript is not difficult, but it is challenging. 1) Understand basic concepts such as variables, data types, functions, etc. 2) Master asynchronous programming and implement it through event loops. 3) Use DOM operations and Promise to handle asynchronous requests. 4) Avoid common mistakes and use debugging techniques. 5) Optimize performance and follow best practices.

Discussion on the realization of parallax scrolling and element animation effects in this article will explore how to achieve similar to Shiseido official website (https://www.shiseido.co.jp/sb/wonderland/)...

How to merge array elements with the same ID into one object in JavaScript? When processing data, we often encounter the need to have the same ID...

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

In-depth discussion of the root causes of the difference in console.log output. This article will analyze the differences in the output results of console.log function in a piece of code and explain the reasons behind it. �...
