web frontend for configuration and local weather display
parent
a96d053502
commit
e5efee686b
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PROJ_ROOT=${PROJ_ROOT:-..}
|
||||
DATA_DIR=${PROJ_ROOT}/data
|
||||
WWW_DIR=${DATA_DIR}/w
|
||||
|
||||
|
||||
# Build data directory
|
||||
printf "\033[33mBuilding data directory:\033[0m ${DATA_DIR}\n"
|
||||
mkdir -p ${DATA_DIR}
|
||||
|
||||
# Build frontend (Not sure if I want to do this)
|
||||
WWWFRONT_DIR=${WWWFRONT:-${PROJ_ROOT}/frontend}
|
||||
#printf "\033[33mBuilding frontend:\033[0m ${WWWFRONT_DIR}\n"
|
||||
#(cd ${WWWFRONT_DIR} && npm run release)
|
||||
|
||||
# Copying static web files
|
||||
printf "\033[33mCopying files to static web directory:\033[0m ${WWW_DIR}\n"
|
||||
rm ${WWW_DIR} -Rf
|
||||
mkdir -p ${WWW_DIR}
|
||||
cp -Lr ${WWWFRONT_DIR}/dist/* ${WWW_DIR}
|
||||
|
||||
# Create gzip compressed versions of www files
|
||||
GZ_MIN_SIZE=${GZ_MIN_SIZE:-"1k"}
|
||||
GZ_MIN_RATIO=${GZ_MIN_RATIO:-30}
|
||||
printf "\033[33mCompressing large files:\033[0m \
|
||||
(size > ${GZ_MIN_SIZE}) by at least ${GZ_MIN_RATIO}%%\n"
|
||||
find ${WWW_DIR} -type f -size +${GZ_MIN_SIZE} | while read file; do
|
||||
real_size=$(cat "${file}" | wc -c)
|
||||
gz_size=$(gzip "${file}" -c | wc -c)
|
||||
ratio=$(echo "scale=2; 100 - ${gz_size} * 100 / ${real_size}" | bc)
|
||||
if [ `echo "${ratio} > ${GZ_MIN_RATIO}" | bc` -ge 1 ]; then
|
||||
gzip -n -k -9 "${file}"
|
||||
printf " \033[37m${file}\033[0m ${real_size} bytes ==> ${gz_size} bytes (\033[32m${ratio}%%\033[0m)\n"
|
||||
else
|
||||
printf " \033[37m${file}\033[0m ${real_size} bytes ==> ${gz_size} bytes (\033[31m${ratio}%%\033[0m) Not compressing!\n"
|
||||
fi
|
||||
done
|
||||
|
||||
# Finish up
|
||||
printf "\n\n\033[32mFinished!\033[0m\n"
|
||||
DATA_SUM=$(find ${DATA_DIR} -type f -exec sha1sum {} \; | sort -k 2 | sha1sum)
|
||||
tree --du -h "${DATA_DIR}"
|
||||
echo "Data checksum: ${DATA_SUM}"
|
||||
echo "Apparent size: $(du -sbh ${DATA_DIR})"
|
|
@ -0,0 +1 @@
|
|||
node_modules/
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Local Firmware Update</title>
|
||||
<link rel="stylesheet" href="css/main.bundle.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-1">Update Firmware</h1>
|
||||
<h2 class="subtitle">Manually upload system firmware</h2>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
|
||||
<div class="field">
|
||||
<div id="firmware-file" class="file has-name is-fullwidth">
|
||||
<label class="file-label">
|
||||
<input class="file-input" type="file" name="firmware">
|
||||
<span class="file-cta">
|
||||
<span class="file-icon">
|
||||
<i class="fas fa-upload"></i>
|
||||
</span>
|
||||
<span class="file-label">
|
||||
Choose a file…
|
||||
</span>
|
||||
</span>
|
||||
<span class="file-name">
|
||||
No file selected
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input type="submit" class="button is-danger" value="Update">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
const fileInput = document.querySelector('#firmware-file input[type=file]');
|
||||
fileInput.onchange = () => {
|
||||
if (fileInput.files.length > 0) {
|
||||
const fileName = document.querySelector('#firmware-file .file-name');
|
||||
fileName.textContent = fileInput.files[0].name;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Sensor overview</title>
|
||||
<link rel="stylesheet" href="css/main.bundle.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Device Setup</title>
|
||||
<link rel="stylesheet" href="css/main.bundle.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<script src="js/setup.js"></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "x-frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "A basic frontend with bulma css",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --mode production",
|
||||
"watch": "webpack --mode development --watch",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"bulma": "^0.7.5",
|
||||
"chart.js": "^2.8.0",
|
||||
"css-loader": "^3.2.1",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"mithril": "^2.0.4",
|
||||
"node-sass": "^4.13.0",
|
||||
"sass-loader": "^8.0.0",
|
||||
"style-loader": "^1.0.1",
|
||||
"webpack": "^4.40.2",
|
||||
"webpack-cli": "^3.3.9"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
#!/usr/bin/node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
const querystring = require('querystring');
|
||||
|
||||
const SERVER_PORT = process.argv[2] || 8000;
|
||||
const DOC_ROOT = path.join(__dirname, '/../dist');
|
||||
|
||||
let wifistate = { connected: false };
|
||||
|
||||
let wifinetworks = {
|
||||
networks: [
|
||||
{
|
||||
ssid: "Network A",
|
||||
rssi: -66,
|
||||
encryption: "wpa2"
|
||||
},
|
||||
{
|
||||
ssid: "Network B",
|
||||
rssi: -75,
|
||||
encryption: "open"
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
let config = {
|
||||
mqtt_host: null,
|
||||
mqtt_prefix: null,
|
||||
mdns_hostname: null
|
||||
}
|
||||
|
||||
let server;
|
||||
|
||||
server = http.createServer(function(req, res) {
|
||||
req.setEncoding('utf8');
|
||||
|
||||
let parsed = url.parse(req.url, true);
|
||||
let pathname = parsed.pathname;
|
||||
let type;
|
||||
|
||||
console.log('[' + new Date() + ']', req.method, pathname);
|
||||
|
||||
// Get request body
|
||||
let body = '';
|
||||
if (req.method === 'POST' || req.method === 'PUT') {
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (pathname === '/')
|
||||
pathname = '/index.html';
|
||||
else if (pathname === '/setup') {
|
||||
pathname = '/setup.html';
|
||||
}
|
||||
|
||||
if (pathname === "/weather/current") {
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify({"time": new Date(), "temperature": 24, "pressure": 1013.25, "humidity": 55}));
|
||||
res.end();
|
||||
return;
|
||||
} else if (pathname === "/wifi/scan") {
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify(wifinetworks));
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
else if (pathname === "/wifi") {
|
||||
if (req.method == 'PUT') {
|
||||
let body = '';
|
||||
req.on('data', function(stream){
|
||||
body += stream;
|
||||
if (body.length > 1e6) { req.connection.destroy(); return; }
|
||||
let data = querystring.parse(body);
|
||||
console.log(data);
|
||||
if (!wifi_connect(data.ssid, data.key)) {
|
||||
res.writeHead(400, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify({
|
||||
connected: false,
|
||||
error: true,
|
||||
message: 'Unable to connect'
|
||||
}));
|
||||
res.end()
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify(wifistate));
|
||||
res.end();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method == 'DELETE') {
|
||||
wifistate = { connected: false };
|
||||
}
|
||||
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify(wifistate));
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
else if (pathname === '/config') {
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify(config));
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
else if (pathname.startsWith('/config/')) {
|
||||
elements = pathname.split('/');
|
||||
propname = elements[2];
|
||||
console.log('property', propname, config[propname]);
|
||||
propval = config[propname];
|
||||
|
||||
if (!propname in config) {
|
||||
res.writeHead(404, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify({
|
||||
error: true,
|
||||
message: 'Unknown property'
|
||||
}));
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === "PUT") {
|
||||
req.on('data', function(stream) {
|
||||
let body = '';
|
||||
body += stream;
|
||||
if (body.length > 1e6) { req.connection.destroy(); return; }
|
||||
let data = body.replace(/\"/g,''); // strip quotes
|
||||
console.log("Set property", propname, data);
|
||||
config[propname] = data;
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify({
|
||||
property: propname,
|
||||
value: config[propname]
|
||||
}));
|
||||
res.end();
|
||||
return;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify({
|
||||
property: propname,
|
||||
value: propval
|
||||
}));
|
||||
res.end();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (/$.htm[l]?/i.test(pathname))
|
||||
type = "text/html";
|
||||
else if (/$.js/i.test(pathname))
|
||||
type = "text/javascript";
|
||||
else if (/$.css/i.test(pathname))
|
||||
type = "text/css";
|
||||
else if (/$.json/i.test(pathname))
|
||||
type = "application/json";
|
||||
else if (/$.ico/i.test(pathname))
|
||||
type = "image/png"; //TODO: better type test
|
||||
|
||||
let stream = fs.createReadStream(path.join(DOC_ROOT + pathname));
|
||||
stream.on('error', function(error) {
|
||||
res.writeHead(404);
|
||||
res.write('Not Found');
|
||||
res.end();
|
||||
});
|
||||
|
||||
if (type)
|
||||
res.writeHead(200, {'Content-Type': type});
|
||||
stream.pipe(res);
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Pretend to connect to wifi
|
||||
*/
|
||||
function wifi_connect(ssid, passphrase) {
|
||||
console.log(ssid, passphrase);
|
||||
if (passphrase === "foo" && ssid == "Network A") {
|
||||
wifistate = {
|
||||
connected: true,
|
||||
ssid: ssid,
|
||||
ipv4: '192.168.1.101',
|
||||
rssi: -65,
|
||||
channel: 1
|
||||
};
|
||||
} else {
|
||||
wifistate = {
|
||||
connected: false,
|
||||
}
|
||||
}
|
||||
return wifistate.connected;
|
||||
}
|
||||
|
||||
|
||||
// Start Server
|
||||
console.log('Starting server on port ' + SERVER_PORT);
|
||||
console.log('Document root: ' + DOC_ROOT);
|
||||
server.listen(SERVER_PORT);
|
|
@ -0,0 +1,6 @@
|
|||
require('./style.scss');
|
||||
var m = require('mithril');
|
||||
var Weather = require("./models/weather.js");
|
||||
var CurrentView = require('./views/CurrentView');
|
||||
|
||||
m.mount(document.body, CurrentView);
|
|
@ -0,0 +1,50 @@
|
|||
const m = require('mithril');
|
||||
const querystring = require('querystring');
|
||||
|
||||
const Config = {
|
||||
properties: {},
|
||||
|
||||
current: {},
|
||||
|
||||
updates: {},
|
||||
|
||||
load: function() {
|
||||
return m.request({
|
||||
method: 'GET',
|
||||
url: '/config'
|
||||
}).then(function(res){
|
||||
Config.properties = res;
|
||||
});
|
||||
},
|
||||
|
||||
update: function(property, value) {
|
||||
if (Config.properties[property] === value)
|
||||
return;
|
||||
Config.properties[property] = value;
|
||||
Config.updates[property] = true;
|
||||
},
|
||||
|
||||
propChanged: function(property) {
|
||||
return Config.updates[property] === true;
|
||||
},
|
||||
|
||||
save: function(property) {
|
||||
if (!Config.propChanged(property))
|
||||
return;
|
||||
return m.request({
|
||||
method: 'PUT',
|
||||
url: '/config/:prop',
|
||||
params: { prop: property },
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
seralize: function(value) { return value; },
|
||||
body: Config.properties[property],
|
||||
}).then(function(res){
|
||||
Config.properties[property] = res.value;
|
||||
Config.updates[property] = false;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = Config;
|
|
@ -0,0 +1,52 @@
|
|||
const m = require('mithril');
|
||||
const querystring = require('querystring');
|
||||
|
||||
const WiFi = {
|
||||
|
||||
current: { connected: false },
|
||||
|
||||
networks: [],
|
||||
|
||||
scan: function() {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "/wifi/scan"
|
||||
}).then(function(res){
|
||||
WiFi.networks = res.networks;
|
||||
})
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "/wifi"
|
||||
}).then(function(res){
|
||||
WiFi.current = res;
|
||||
});
|
||||
},
|
||||
|
||||
connect: function(ssid, passphrase) {
|
||||
return m.request({
|
||||
method: "PUT",
|
||||
url: "/wifi",
|
||||
serialize: querystring.stringify,
|
||||
body: {
|
||||
ssid: ssid,
|
||||
key: passphrase
|
||||
}
|
||||
}).then(function(res) {
|
||||
WiFi.current = res;
|
||||
});
|
||||
},
|
||||
|
||||
disconnect: function() {
|
||||
return m.request({
|
||||
method: "DELETE",
|
||||
url: '/wifi'
|
||||
}).then(function(res) {
|
||||
WiFi.current = { connected: false }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = WiFi;
|
|
@ -0,0 +1,15 @@
|
|||
const m = require("mithril");
|
||||
|
||||
const Weather = {
|
||||
current: null,
|
||||
loadCurrent: function() {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "/weather/current"
|
||||
}).then(function(result){
|
||||
Weather.current = result;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Weather;
|
|
@ -0,0 +1,5 @@
|
|||
const m = require('mithril');
|
||||
const SetupView = require('./views/SetupView');
|
||||
|
||||
|
||||
m.mount(document.body, SetupView);
|
|
@ -0,0 +1,2 @@
|
|||
@charset "utf-8";
|
||||
@import "~bulma/bulma";
|
|
@ -0,0 +1,54 @@
|
|||
const m = require('mithril');
|
||||
const Weather = require('../models/weather');
|
||||
|
||||
var CurrentView = {
|
||||
|
||||
intervalID: null,
|
||||
autorefresh_enabled: true,
|
||||
oninit: function() {
|
||||
CurrentView.intervalID = setInterval(CurrentView.autorefresh, 30000);
|
||||
return Weather.loadCurrent();
|
||||
},
|
||||
view: function() {
|
||||
return m('div.section', [m('div.container',
|
||||
(Weather.current == null) ? [m('.notification', 'Loading current conditions...')] : [
|
||||
m('h1.title', 'Current Conditions'),
|
||||
m('h2.subtitle', 'as of ' + Weather.current.time),
|
||||
m('.level', [
|
||||
m('.level-item.has-text-centered', m("div",[
|
||||
m('p.heading"', 'Temperature'),
|
||||
m('p.title', Weather.current.temperature + ' °C')
|
||||
])),
|
||||
m('.level-item.has-text-centered', m("div",[
|
||||
m('p.heading"', 'Pressure'),
|
||||
m('p.title', Weather.current.pressure + ' mbar')
|
||||
])),
|
||||
m('.level-item.has-text-centered', m("div",[
|
||||
m('p.heading"', 'Humidity'),
|
||||
m('p.title', Weather.current.humidity + '%')
|
||||
])),
|
||||
]),
|
||||
m('.field.is-grouped', [
|
||||
m('.control', m('button.button.is-small', {
|
||||
disabled: CurrentView.autorefresh_enabled,
|
||||
onclick: CurrentView.refresh
|
||||
}, 'Refresh')),
|
||||
m('.control', m('label.checkbox', [ m('input[type=checkbox]',{
|
||||
checked: CurrentView.autorefresh_enabled,
|
||||
onchange: function(e) { CurrentView.autorefresh_enabled = e.target.checked; }
|
||||
}), " Auto"]))
|
||||
])
|
||||
])]);
|
||||
},
|
||||
|
||||
autorefresh: function() {
|
||||
if (CurrentView.autorefresh_enabled) CurrentView.refresh();
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
Weather.loadCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = CurrentView;
|
|
@ -0,0 +1,116 @@
|
|||
const m = require('mithril');
|
||||
const Config = require('../models/Config');
|
||||
const WiFiSetupView = require('./WiFiSetupView');
|
||||
|
||||
const ConfigTextInput = {
|
||||
view: function(vnode) {
|
||||
return [
|
||||
m('label.label', { for: vnode.attrs.name }, vnode.attrs.title),
|
||||
m('.field.has-addons', [
|
||||
m('.control.is-expanded', m('input.input[type=text]#' + vnode.attrs.name, {
|
||||
value: Config.properties[vnode.attrs.name],
|
||||
placeholder: vnode.attrs.placeholder,
|
||||
oninput: function (e) { Config.update(vnode.attrs.name, e.target.value) },
|
||||
})),
|
||||
m('.control', m('button.button.is-info',{
|
||||
disabled: !Config.updates[vnode.attrs.name],
|
||||
onclick: function() {
|
||||
console.log(Config.save(vnode.attrs.name));
|
||||
}
|
||||
}, 'Save'))
|
||||
])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const ConfigSelectInput = {
|
||||
view: function(vnode) {
|
||||
return [
|
||||
m('.field',[
|
||||
m('.label.label', {for: vnode.attrs.name}, vnode.attrs.title),
|
||||
m('.control', m('.select',
|
||||
m('select', {
|
||||
oninput: function(e) { Config.update(vnode.attrs.name, e.target.value); Config.save(vnode.attrs.name); }
|
||||
},
|
||||
vnode.attrs.options.map(function(option) {
|
||||
return m('option', {
|
||||
selected: (Config.properties[vnode.attrs.name] == option)
|
||||
},option);
|
||||
}))
|
||||
))
|
||||
])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const configurables = [
|
||||
{
|
||||
group: "Networking",
|
||||
properties: [
|
||||
{
|
||||
name: "mdns_hostname",
|
||||
title: "mDNS Hostname",
|
||||
value: Config.properties.mdns_hostname,
|
||||
placeholder: "mDNS disabled",
|
||||
},
|
||||
{
|
||||
name: "enable_http",
|
||||
title: "HTTP Server",
|
||||
value: Config.properties.enable_http,
|
||||
options: ['Enabled','Disabled']
|
||||
},
|
||||
{
|
||||
name: "ntp_host",
|
||||
title: "NTP Host",
|
||||
value: Config.properties.ntp_host,
|
||||
placeholder: 'Default NTP server',
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "MQTT",
|
||||
properties: [
|
||||
{
|
||||
name: "mqtt_host",
|
||||
title: "Remote MQTT Host",
|
||||
value: Config.properties.mqtt_host,
|
||||
placeholder: 'MQTT Disabled'
|
||||
},
|
||||
|
||||
{
|
||||
name: "mqtt_prefix",
|
||||
title: "MQTT Topic Prefix",
|
||||
value: Config.properties.mqtt_prefix,
|
||||
placeholder: "/"
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const SetupView = {
|
||||
oninit: function() {
|
||||
return Config.load();
|
||||
},
|
||||
|
||||
view: function() {
|
||||
return m('.section', m('.container', [
|
||||
m('h1.title.is-1', 'Device Setup'),
|
||||
m(WiFiSetupView),
|
||||
configurables.map(function(group) {
|
||||
return [
|
||||
m('h2.title', group.group),
|
||||
group.properties.map(function(prop) {
|
||||
if (prop.options != undefined) {
|
||||
return m(ConfigSelectInput, prop);
|
||||
} else
|
||||
return m(ConfigTextInput, prop);
|
||||
})
|
||||
];
|
||||
})
|
||||
]));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = SetupView;
|
|
@ -0,0 +1,123 @@
|
|||
const m = require('mithril');
|
||||
const WiFi = require('../models/WiFi');
|
||||
const WiFiSetupView = {
|
||||
|
||||
oninit: function(vnode) {
|
||||
return WiFi.load()
|
||||
},
|
||||
|
||||
error_message: null,
|
||||
|
||||
ssid: null,
|
||||
|
||||
passphrase: null,
|
||||
|
||||
is_scanning: false,
|
||||
|
||||
scan: function() {
|
||||
if (WiFiSetupView.is_scanning) return;
|
||||
WiFiSetupView.is_scanning = true;
|
||||
WiFi.scan().then(function() {
|
||||
WiFiSetupView.is_scanning = false;
|
||||
});
|
||||
},
|
||||
|
||||
connect: function() {
|
||||
WiFiSetupView.error_message = null;
|
||||
if (!WiFiSetupView.ssid) return;
|
||||
WiFi.connect(WiFiSetupView.ssid, WiFiSetupView.passphrase).catch(function (e) {
|
||||
WiFiSetupView.error_message = e.response.message;
|
||||
});
|
||||
},
|
||||
|
||||
disconnect: function() {
|
||||
WiFiSetupView.error_message = null;
|
||||
WiFi.disconnect().catch(function(e) {
|
||||
WiFiSetupView.error_message = e.response.message;
|
||||
});
|
||||
},
|
||||
|
||||
view: function() {
|
||||
return [
|
||||
m('h2.title', 'WiFi'),
|
||||
m('.columns', [
|
||||
|
||||
// Setup form
|
||||
m('.column', [
|
||||
|
||||
// Error Messages
|
||||
(
|
||||
WiFiSetupView.error_message != null
|
||||
? m('.notification.is-danger', WiFiSetupView.error_message)
|
||||
: null
|
||||
),
|
||||
|
||||
// Current Network
|
||||
(
|
||||
WiFi.current.connected
|
||||
? m('.notification.is-info', [
|
||||
m('p', [m('strong', 'SSID: '), WiFi.current.ssid]),
|
||||
m('p', [m('strong', 'IPv4: '), WiFi.current.ipv4]),
|
||||
m('p', [m('strong', 'RSSI: '), WiFi.current.rssi + ' dBm']),
|
||||
m('p', [m('strong', 'Channel: '), WiFi.current.channel]),
|
||||
])
|
||||
: null
|
||||
),
|
||||
|
||||
// SSID Field
|
||||
m('.field', [
|
||||
m('label.label[for="ssid"]', 'SSID'),
|
||||
m('.control', m('input.input[type=text]#ssid', {
|
||||
value: WiFiSetupView.ssid,
|
||||
onchange: function (e) {
|
||||
WiFiSetupView.ssid = e.target.value;
|
||||
}
|
||||
}))
|
||||
]),
|
||||
|
||||
// Passphrase/Key field
|
||||
m('.field', [
|
||||
m('label.label[for="passphrase"]', 'Passphrase/Key'),
|
||||
m('.control', m('input.input[type=text]#passphrase', {
|
||||
value: WiFiSetupView.passphrase,
|
||||
onchange: function(e) { WiFiSetupView.passphrase = e.target.value; },
|
||||
placeholder: 'Empty'
|
||||
}))
|
||||
]),
|
||||
|
||||
// Connect/Disconnect buttons
|
||||
m('.field.is-grouped', [
|
||||
m('p.control', m('a.button.is-primary', {
|
||||
disabled: (WiFi.current.connected && WiFi.current.ssid == WiFiSetupView.ssid),
|
||||
onclick: WiFiSetupView.connect,
|
||||
}, 'Connect')),
|
||||
m('p.control', m('a.button.is-outlined.is-danger', {
|
||||
disabled: WiFi.current.connected != true,
|
||||
onclick: WiFiSetupView.disconnect,
|
||||
}, 'Disconnect')),
|
||||
])
|
||||
]),
|
||||
|
||||
// Nearby networks
|
||||
m('.column', m('.panel',[
|
||||
m('p.panel-heading', "Nearby Networks"),
|
||||
m('.panel-block', m('button', {
|
||||
class: "button is-outlined is-fullwidth" + (WiFiSetupView.is_scanning ? ' is-loading' : ''),
|
||||
onclick: WiFiSetupView.scan,
|
||||
}, "Scan")),
|
||||
WiFi.networks.map(function(i){
|
||||
return m('a.panel-block', {
|
||||
key: i.ssid,
|
||||
onclick: function() {
|
||||
WiFiSetupView.ssid = i.ssid;
|
||||
WiFiSetupView.passphrase = null;
|
||||
}
|
||||
}, i.ssid);
|
||||
})
|
||||
]))
|
||||
])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WiFiSetupView;
|
|
@ -0,0 +1,37 @@
|
|||
const path = require('path');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
main: './src/index.js',
|
||||
setup: './src/setup.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'js/[name].js'
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.s[ac]ss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader'
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
// options...
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'css/[name].bundle.css'
|
||||
}),
|
||||
]
|
||||
};
|
Loading…
Reference in New Issue