Skip to content

Commit

Permalink
Merge pull request #7 from sudomesh/devtools
Browse files Browse the repository at this point in the history
Dashboard development sans node
  • Loading branch information
bennlich authored Dec 6, 2018
2 parents cdde4ad + f218b6f commit efdb7b0
Show file tree
Hide file tree
Showing 9 changed files with 457 additions and 61 deletions.
52 changes: 45 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,57 @@

![](homeScreen.png)

This is the dashboard that is shown to users of PeoplesOpen.net to allow them to learn about and configure their home nodes. It has 2 main goals: allow less-advanced users to see what their node has been doing and to allow more-advanced users to configure their node more easily.
This is the welcome page and dashboard for PeoplesOpen.net home node owners. People can use this web app to check in on and configure their home nodes.

For the less-advanced users, the front page has some statistics and information about their node, such as what neighbors it is connected to, how much it has shared, etc. This information is designed to present a friendly face and make the idea of mesh networks fun and approachable. Another goal of this page is to convert people from just hosting a home node to actually sharing their bandwidth.
The __Home__ page lets you set how much bandwidth you want to share on your public wifi network.

For the more-advanced users, there are in-depth configuration options, as well as a diagram of the router's connection to help debug installations.
The __Wifi Settings__ page lets you set the name and password for your private wifi network.

## How to develop on this app
The __Connections__ page shows you what computers are currently connected to your public and private networks. It also shows you if your node is connected to an exitnode (TODO: link to exitnode documentation).

To write code for this app, you need a PeoplesOpen.net home node. This is because the backend is run with OpenWRT's [ubus](https://wiki.openwrt.org/doc/techref/ubus) system. It may also be possible to run this in a virtual machine, but it hasn't been tried.
## Development

It's probably easiest to ask someone for one of the pre-flashed nodes that we keep on hand, but in case you want to flash your own, check the [wiki](https://sudoroom.org/wiki/Mesh/WalkThrough). If the node is correctly flashed, you should be able to connect to the node's private wifi or ethernet network and `ping 172.30.0.1` should return something like `64 bytes from 172.30.0.1: icmp_seq=0 ttl=64 time=1.067 ms`.
This web app runs on home nodes. It uses OpenWRT's [ubus](https://openwrt.org/docs/techref/ubus) interface to write to config files on the node. It also uses a script in `/cgi-bin` to gather information about the node.

Now, run `npm start` from this directory, and open Chrome with the [--disable-web-security and --user-data-dir](http://stackoverflow.com/questions/3102819/disable-same-origin-policy-in-chrome) flags. You should be able to log into the control panel (you'll need the admin password of course) at localhost:3000 and see it live-update as you edit the code.
### Getting started

1) Clone the repo

2) `npm install` the dependencies

### Developing without a node

Run `npm build-dev` to build the web app in "development" mode. This configures the app to use stubbed API responses so that you can develop on your local machine (instead of on a home node).

Run `npm start` to start serving the app.

#### Writing new API calls

When adding a new ubus API call to the app (woo! you rock!), be sure to add a stubbed reply. This allows people to continue developing without having a home node on-hand.

Stubbed replies live in [`src/actions/stubs/`](src/actions/stubs/), and they are imported and used by [`src/actions/ubusAPI.js`](src/actions/ubusAPI.js).

### Testing with a node

This is important if you are adding new pages that interface with ubus or cgi-bin.

1) Acquire a home node (see [the walkthrough](https://sudoroom.org/wiki/Mesh/WalkThrough))

2) Connect to your home node's private network, either via the private SSID or by plugging directly into the private ethernet port

3) Run `./publish-to-node.sh`. This will build the app in production mode (i.e. stop using stubbed API replies) and copy the build directory to your node's `www/` folder. It will ask for confirmation. You may want to `ssh` in and backup the existing `www/` directory before doing this.

4) Visit `http://172.30.0.1` and voila! Now you can test the real calls to ubus.

### Developing with and without a node (kind of hacky)

If you want to serve the web app from your computer, but still point the app to a real home node, you can do this by disabling your browser's CORS checks.

First, `npm build-prod` to build the app for production.

Now run `npm start`, but this time open Chrome with the [--disable-web-security and --user-data-dir](http://stackoverflow.com/questions/3102819/disable-same-origin-policy-in-chrome) flags. You should be able to log into the control panel (you'll need the admin password of course) at localhost:3000 and see it live-update as you edit the code.

__NOTE:__ "Developing with and without a node" does not reflect the real environment in which the app will be running (notably, your browser is running with non-standard security settings). Your ultimate test should involve copying the build/ directory over to your node as described above in "Testing with a node".

## Application architecture

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
"background-sass-watcher": "node-sass --watch --recursive src/index.scss -o src/css & WATCH_SASS_PID=$!",
"compile-sass": "node-sass --recursive src/index.scss -o src/css",
"start": "npm run compile-sass && npm run background-sass-watcher && npm run dev-server",
"build": "npm run compile-sass && react-scripts build",
"build-prod": "echo \"export default 'production'\" > src/env.js && npm run compile-sass && react-scripts build",
"build-dev": "echo \"export default 'development'\" > src/env.js && echo 'WARN: Building for development; using stubbed API calls.' && npm run compile-sass && react-scripts build",
"build": "npm run build-prod",
"test": "react-scripts test"
}
}
25 changes: 25 additions & 0 deletions publish-to-node.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

# Use this script to deploy the dashboard app
# to a home node. You might want to do this to test a new
# version of the dashboard.

set -e

# NODE_IP is 172.30.0.1 by default
NODE_IP=${1:-"172.30.0.1"}
DEST_DIR=/www

echo "Executing npm run build-prod..."
npm run build-prod

echo "Copying build/ to root@$NODE_IP:$DEST_DIR"
read -r -p "Are you sure? This will overwrite the $DEST_DIR folder on your node. [yN] " response

if [[ "$response" == "y" ]]
then
scp -r build/* root@$NODE_IP:$DEST_DIR
else
echo "Yeah... maybe you should back that up first. Aborting."
fi

64 changes: 11 additions & 53 deletions src/actions/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import Ubus from '../libs/ubus.js'
import check from 'check-types'
import config from '../config.js'
import actionType from './types.js'
import sampleRouterInfo from '../libs/__test__/sampleRouterOutput.js'

const ubus = new Ubus(config.ubusUrl)
import ubusAPI from './ubusAPI.js'
import infogatherAPI from './infogatherAPI.js'

export function login (password) {
return async (dispatch, getState) => {
const {
ubus_rpc_session
} = await ubus.call(null, 'session', 'login', { username: config.username, password })
const { ubus_rpc_session } = await ubusAPI.login(config.username, password)

check.assert.nonEmptyString(
ubus_rpc_session,
Expand Down Expand Up @@ -49,7 +45,7 @@ export function logout (dispatch) {
return async (dispatch, getState) => {
const sessionID = localStorage.getItem('sessionID')
if (sessionID) {
await dispatch(callUbus('session', 'destroy', { sessionID }))
await dispatch(ubusAPI.logout(sessionID))
}
localStorage.setItem('sessionID', null)

Expand All @@ -59,10 +55,6 @@ export function logout (dispatch) {
}
}

export function changePassword (credentials) {
return callUbus('password', 'set', credentials)
}

export function fetchUciConfigs () {
return async (dispatch, getState) => {
const configNames = [
Expand All @@ -72,7 +64,7 @@ export function fetchUciConfigs () {
const configs = {}

await Promise.all(configNames.map(async config => {
const { values } = await dispatch(callUbus('uci', 'get', { config }))
const { values } = await dispatch(ubusAPI.getConfig(config))
configs[config] = values
}))

Expand All @@ -86,56 +78,22 @@ export function fetchUciConfigs () {
export function changeWirelessConfig (ifname, toChange, value) {
return async (dispatch, getState) => {
const { uciConfigs: { wireless: { interfaces } } } = getState()
await dispatch(callUbus('uci', 'set', {
config: 'wireless',
section: interfaces[ifname]['.name'],
values: {
[toChange]: value
}
}))

return dispatch(callUbus('uci', 'commit', {
config: 'wireless'
}))
const section = interfaces[ifname]['.name']
await dispatch(ubusAPI.setWirelessConfig(section, toChange, value))
return dispatch(ubusAPI.commitConfig('wireless'))
}
}

export function changeTunneldiggerConfig (toChange, value) {
return async (dispatch, getState) => {
await dispatch(callUbus('uci', 'set', {
config: 'tunneldigger',
section: 'main',
values: {
[toChange]: value
}
}))

return dispatch(callUbus('uci', 'commit', {
config: 'tunneldigger'
}))
}
}

export function callUbus (object, method, args) {
return async (dispatch, getState) => {
const sessionID = localStorage.getItem('sessionID') || null

try {
return ubus.call(sessionID, object, method, args)
} catch (e) {
if (e.message.match(/session_expired/)) {
dispatch(logout())
} else {
throw e
}
}
await dispatch(ubusAPI.setTunneldiggerConfig(toChange, value))
return dispatch(ubusAPI.commitConfig('tunneldigger'))
}
}

export function getRouterInfo () {
return async (dispatch, getState) => {
const response = await (await fetch(config.infogatherUrl)).text()

const response = await infogatherAPI.getRouterInfo()
return dispatch({
type: actionType('got router info'),
payload: response
Expand Down
17 changes: 17 additions & 0 deletions src/actions/infogatherAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import config from '../config.js'
import routerInfo from './stubs/getRouterInfo.stub.js'
import env from '../env.js'

function getRouterInfo() {
return fetch(config.infogatherUrl).then((res) => res.text())
}

function getRouterInfoStub() {
return routerInfo
}

const useStubs = env === 'development'

export default {
getRouterInfo: useStubs ? getRouterInfoStub : getRouterInfo
}
4 changes: 4 additions & 0 deletions src/actions/stubs/fetchUCIConfigs.stub.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit efdb7b0

Please sign in to comment.