Lots of remote changes.
dht is now processing commands! hurray!
This commit is contained in:
10
config/config.json
Normal file
10
config/config.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"development": {
|
||||
"username": "root",
|
||||
"password": "Zelen0ku4e",
|
||||
"database": "iot",
|
||||
"host": "127.0.0.1",
|
||||
"dialect": "mysql",
|
||||
"operatorsAliases": false
|
||||
}
|
||||
}
|
||||
69
database.js
Normal file
69
database.js
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
const Sequelize = require("sequelize")
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
let DeviceMessageSchema = new Schema({
|
||||
_id: {type: Number, required: true},
|
||||
device_id: {type: String, required: true, max: 100}
|
||||
});
|
||||
|
||||
let DevicesSchema = new Schema({
|
||||
id: {type: Number, required: true},
|
||||
url: {type: String, required: true, max: 100}
|
||||
});
|
||||
|
||||
|
||||
var sqlz = new Sequelize('iot', 'iot', '!iot_popovi',{dialect: 'mysql'})
|
||||
var Device = sqlz.define('device', {
|
||||
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
|
||||
name: Sequelize.STRING,
|
||||
baseurl: Sequelize.TEXT,
|
||||
apikey: Sequelize.TEXT,
|
||||
//config: Sequelize.JSON,
|
||||
lastseen: Sequelize.DATE
|
||||
});
|
||||
|
||||
var DeviceMessage = sqlz.define('devicemessage', {
|
||||
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
|
||||
device_id: { type: Sequelize.INTEGER, allowNull: false},
|
||||
//id,"device_id","field_name","field_value","timestamp"
|
||||
field_name: { type: Sequelize.STRING(120), allowNull: false},
|
||||
field_value: { type: Sequelize.TEXT, allowNull: false},
|
||||
timestamp: { type: Sequelize.DATE, allowNull: false},
|
||||
});
|
||||
|
||||
var DeviceCommand = sqlz.define("command", {
|
||||
device: { type: Sequelize.STRING},
|
||||
command: {type: Sequelize.TEXT},
|
||||
info: {type: Sequelize.STRING},
|
||||
ac_power: Sequelize.BOOLEAN,
|
||||
ac_mode: Sequelize.ENUM('Auto', 'Heat', 'Cool', "Fan"),
|
||||
ac_fan: Sequelize.ENUM('Auto', 'Low', 'Med', "Hi"),
|
||||
ac_temp: Sequelize.FLOAT,
|
||||
ac_turbo: Sequelize.BOOLEAN,
|
||||
ac_swing: Sequelize.BOOLEAN,
|
||||
ac_display: Sequelize.BOOLEAN,
|
||||
ac_econo: Sequelize.BOOLEAN,
|
||||
ac_health: Sequelize.BOOLEAN,
|
||||
|
||||
});
|
||||
|
||||
sqlz.sync(
|
||||
//{ force: true }
|
||||
)
|
||||
.then(() => {
|
||||
console.log(`Database & tables created!`)
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
init: function() {
|
||||
//console.log(DeviceCommand.rawAttributes.states.values);
|
||||
sqlz.sync();
|
||||
},
|
||||
sqlz,
|
||||
Device,
|
||||
DeviceCommand,
|
||||
DeviceMessage
|
||||
//etc
|
||||
}
|
||||
1
db/index.js
Normal file
1
db/index.js
Normal file
@@ -0,0 +1 @@
|
||||
exports.users = require('./users');
|
||||
28
db/users.js
Normal file
28
db/users.js
Normal file
@@ -0,0 +1,28 @@
|
||||
var records = [
|
||||
{ id: 1, username: 'jack', password: 'secret', displayName: 'Jack', emails: [ { value: 'jack@example.com' } ] }
|
||||
, { id: 2, username: 'jill', password: 'birthday', displayName: 'Jill', emails: [ { value: 'jill@example.com' } ] }
|
||||
, { id: 3, username: 'popov', password: 'Zelenakrav@', displayName: 'Doby', emails: [ { value: 'db@example.com' } ] }
|
||||
];
|
||||
|
||||
exports.findById = function(id, cb) {
|
||||
process.nextTick(function() {
|
||||
var idx = id - 1;
|
||||
if (records[idx]) {
|
||||
cb(null, records[idx]);
|
||||
} else {
|
||||
cb(new Error('User ' + id + ' does not exist'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.findByUsername = function(username, cb) {
|
||||
process.nextTick(function() {
|
||||
for (var i = 0, len = records.length; i < len; i++) {
|
||||
var record = records[i];
|
||||
if (record.username === username) {
|
||||
return cb(null, record);
|
||||
}
|
||||
}
|
||||
return cb(null, null);
|
||||
});
|
||||
}
|
||||
276
dht.js
276
dht.js
@@ -1,6 +1,7 @@
|
||||
// ./src/index.js
|
||||
|
||||
// importing the dependencies
|
||||
const util = require('util');
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const cors = require('cors');
|
||||
@@ -9,7 +10,81 @@ const morgan = require('morgan');
|
||||
const cron = require('node-cron');
|
||||
const request = require('request');
|
||||
const got = require('got');
|
||||
const Sequelize = require("sequelize")
|
||||
|
||||
//auth ++
|
||||
var session = require('express-session')
|
||||
var passport = require('passport')
|
||||
var Strategy = require('passport-local').Strategy;
|
||||
var db = require('./db');
|
||||
var ensureLoggedIn = require("connect-ensure-login").ensureLoggedIn("/n/login");
|
||||
|
||||
|
||||
// passport.use(new Strategy(
|
||||
// // {
|
||||
// // usernameField: 'email',
|
||||
// // passwordField: 'passwd',
|
||||
// // session: false
|
||||
// // },
|
||||
// function(username, password, done) {
|
||||
|
||||
// console.log("executing auth strategy: local");
|
||||
// if(username == "popov" & password == "test12345")
|
||||
// {
|
||||
// done(null, new {username:"popov"});
|
||||
// }else
|
||||
// {
|
||||
// return done(null, false);
|
||||
// }
|
||||
// }
|
||||
// ));
|
||||
// passport.serializeUser(function(user, cb) {
|
||||
|
||||
// console.log("serializeUser()");
|
||||
// cb(null, user.id);
|
||||
// });
|
||||
|
||||
// passport.deserializeUser(function(id, cb) {
|
||||
// console.log("de-serializeUser()");
|
||||
// cb(null, new {username:"popov"});
|
||||
// });
|
||||
|
||||
//! Configure the local strategy for use by Passport.
|
||||
//
|
||||
// The local strategy require a `verify` function which receives the credentials
|
||||
// (`username` and `password`) submitted by the user. The function must verify
|
||||
// that the password is correct and then invoke `cb` with a user object, which
|
||||
// will be set at `req.user` in route handlers after authentication.
|
||||
passport.use(new Strategy(
|
||||
function(username, password, cb) {
|
||||
console.log('requesting authentication for user '+ username);
|
||||
db.users.findByUsername(username, function(err, user) {
|
||||
if (err) { return cb(err); }
|
||||
if (!user) { return cb(null, false); }
|
||||
if (user.password != password) { return cb(null, false); }
|
||||
return cb(null, user);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
// Configure Passport authenticated session persistence.
|
||||
//
|
||||
// In order to restore authentication state across HTTP requests, Passport needs
|
||||
// to serialize users into and deserialize users out of the session. The
|
||||
// typical implementation of this is as simple as supplying the user ID when
|
||||
// serializing, and querying the user record by ID from the database when
|
||||
// deserializing.
|
||||
passport.serializeUser(function(user, cb) {
|
||||
cb(null, user.id);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(id, cb) {
|
||||
db.users.findById(id, function (err, user) {
|
||||
if (err) { return cb(err); }
|
||||
cb(null, user);
|
||||
});
|
||||
});
|
||||
|
||||
// auth --
|
||||
|
||||
//!https endpoint
|
||||
var fs = require("fs");
|
||||
@@ -29,56 +104,164 @@ var con = mysql.createConnection({
|
||||
password : '!iot_popovi',
|
||||
database : 'iot'
|
||||
});
|
||||
var sqlz = new Sequelize('iot', 'iot', '!iot_popovi',{dialect: 'mysql'})
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
let DeviceMessageSchema = new Schema({
|
||||
_id: {type: Number, required: true},
|
||||
device_id: {type: String, required: true, max: 100}
|
||||
});
|
||||
|
||||
let DevicesSchema = new Schema({
|
||||
id: {type: Number, required: true},
|
||||
url: {type: String, required: true, max: 100}
|
||||
});
|
||||
|
||||
var Device = sqlz.define('device', {
|
||||
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
|
||||
name: Sequelize.STRING,
|
||||
baseurl: Sequelize.TEXT,
|
||||
apikey: Sequelize.TEXT,
|
||||
//config: Sequelize.JSON,
|
||||
lastseen: Sequelize.DATE
|
||||
});
|
||||
|
||||
sqlz.sync();
|
||||
|
||||
|
||||
// defining the Express app
|
||||
const app = express();
|
||||
var httpServer = http.createServer(app);
|
||||
var httpsServer = https.createServer(credentials, app);
|
||||
|
||||
// defining an array to work as the database (temporary solution)
|
||||
const ads = [
|
||||
{title: 'Hello, world (again)!'}
|
||||
];
|
||||
|
||||
// adding Helmet to enhance your API's security
|
||||
app.use(helmet());
|
||||
|
||||
// using bodyParser to parse JSON bodies into JS objects
|
||||
//app.use(bodyParser.text({ type: 'text/html' }))
|
||||
//app.use(bodyParser.json({ type: 'application/*+json' }));
|
||||
//app.use(bodyParser.text());
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(express.static('public'))
|
||||
// enabling CORS for all requests
|
||||
app.use(cors());
|
||||
//Authentication ++
|
||||
app.use(session({
|
||||
secret: 'че първият ще генерира грешка, ако изгледът не дефинира съдържание за този раздел',
|
||||
resave: true,
|
||||
saveUninitialized: false
|
||||
}));
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
//Authentication --
|
||||
|
||||
//app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'ejs');
|
||||
app.use(require('express-ejs-layouts'));//https://www.npmjs.com/package/express-ejs-layouts
|
||||
//app.set("layout extractScripts", true)
|
||||
//app.set('view engine', 'vash');//https://www.npmjs.com/package/vash#layout-helpers
|
||||
|
||||
|
||||
// adding morgan to log HTTP requests
|
||||
app.use(morgan('combined'));
|
||||
//app.use(morgan('combined'));
|
||||
|
||||
// defining endpoints
|
||||
|
||||
//Authentication ++
|
||||
app.get('/login', function(req, res) {
|
||||
res.render('login', { user: req.user });
|
||||
});
|
||||
|
||||
app.post('/login',
|
||||
passport.authenticate('local', {
|
||||
successRedirect: '/n/accontrol',
|
||||
failureRedirect: '/n/login' }),
|
||||
// authenticated user.
|
||||
function(req, res) {
|
||||
|
||||
console.log("logged in. session:" + req.session);
|
||||
res.redirect(req.session);
|
||||
}
|
||||
);
|
||||
|
||||
app.get('/logout', function(req, res){
|
||||
req.logout();
|
||||
res.redirect('/n/login');
|
||||
});
|
||||
|
||||
app.get('/accontrol', ensureLoggedIn, function(req, res){
|
||||
console.log("viewing /accontrol as authenticated user:"+ req.user);
|
||||
res.render('accontrol', { model: { user: req.user, data:{} }});
|
||||
});
|
||||
|
||||
app.post('/accontrol', ensureLoggedIn, function(req, res){
|
||||
var model = { model: {user: req.user, data: req.body} };
|
||||
console.log("power:"+ req.body.power);
|
||||
console.log("heat:"+ req.body.heat);
|
||||
console.log("temp:"+ req.body.temp);
|
||||
console.log("econo:"+ req.body.econo);
|
||||
var where = {ac_power: req.body.power?true:false};
|
||||
if(where.ac_power){
|
||||
where.ac_mode = req.body.heat?"Heat":"Cool";
|
||||
if(req.body.temp){ where.ac_temp = req.body.temp; }else{where.ac_temp = 23}
|
||||
|
||||
//where.ac_econo = req.body.econo?true:false;
|
||||
//// if(req.body.turbo){ where.ac_turbo = req.body.turbo?true:false; }
|
||||
//// if(req.body.swing){ where.ac_swing = req.body.swing?true:false; }
|
||||
//// if(req.body.display){ where.ac_display = req.body.display?true:false; }
|
||||
//// if(req.body.health){ where.ac_health = req.body.health?true:false; }
|
||||
}
|
||||
data.DeviceCommand.findAll({ where: where }).then(function(com){
|
||||
console.log("FOUND "+ com.length + " RESULTS");
|
||||
if(com.length > 0)
|
||||
{
|
||||
model.command = com[0];
|
||||
model.info = model.command.info;
|
||||
console.log("executing command "+ model.info+ "");
|
||||
request.post(
|
||||
'http://192.168.1.126/ir',
|
||||
{ form: { cmd: "RAW:" + model.command.command } },
|
||||
function (error, response, body) {
|
||||
if (!error && response.statusCode == 200) {
|
||||
//console.log("GOT " + body);
|
||||
var m = model;
|
||||
res.render('accontrol', {model:{data:req.body, user:req.user, command:com[0], info:com[0].info}});
|
||||
}else{
|
||||
model.info = "Error executing command " + model.command.info + ". Server resturned:" + req.statusCode
|
||||
}
|
||||
}
|
||||
);
|
||||
} else
|
||||
{
|
||||
model.info = "Command not executed. Found " + com.length + "commands"
|
||||
res.render('accontrol', model);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/chart', ensureLoggedIn,
|
||||
function(req, res){
|
||||
res.render('chart', { user: req.user });
|
||||
});
|
||||
|
||||
|
||||
|
||||
//Authentication --
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app.post('/dht/ping', (req, res) => { (async (res) => {
|
||||
try {
|
||||
console.log("HEADERS:" + req.headers); res.sendStatus(200);
|
||||
} catch (error) {
|
||||
console.log("PING Error:" + error); res.sendStatus(500);
|
||||
} })(res);
|
||||
});
|
||||
|
||||
app.use('/ir', bodyParser.text(), function(rq, rs) {
|
||||
console.log("REQ:"+rq.headers);
|
||||
console.log("BODY:"+rq.body);
|
||||
rs.sendStatus(200);
|
||||
});
|
||||
|
||||
// app.post('/ir', (req, res) => {
|
||||
// (async (res) => {
|
||||
// try {
|
||||
// //console.log(req.params);
|
||||
// console.log("REQ:"+req.body);
|
||||
// console.log(`post/${util.inspect(req.body,false,null)}`);
|
||||
// res.sendStatus(200);
|
||||
// } catch (error) {
|
||||
// console.log("IR Error:" + error); //..response.body);
|
||||
// }
|
||||
// })(res);
|
||||
// });
|
||||
|
||||
// defining an endpoint to return all ads
|
||||
app.get('/dht', (req, res) => { (async (res) => {
|
||||
try {
|
||||
const response = await got('http://192.168.1.126/json')
|
||||
@@ -126,16 +309,37 @@ app.put('/dht/:device_id/:field_name/:field_value', (req, res) => {
|
||||
|
||||
});
|
||||
|
||||
app.get('/ac', (req, res) => { (async (res) => {
|
||||
try {
|
||||
const response = await got('http://192.168.1.126/json')
|
||||
res.send(response.body);
|
||||
} catch (error) {
|
||||
console.log("DHT Error:" + error); //..response.body);
|
||||
} })(res);
|
||||
});
|
||||
|
||||
|
||||
//Startup
|
||||
|
||||
var httpServer = http.createServer(app);
|
||||
var httpsServer = https.createServer(credentials, app);
|
||||
|
||||
httpsServer.listen(8443, () => {
|
||||
console.log('HTTPS server listening on port 8443');
|
||||
});
|
||||
httpServer.listen(81, () => {
|
||||
console.log('HTTP server listening on port 81');
|
||||
});
|
||||
var device = Device.build({
|
||||
|
||||
var data = require('./database.js');
|
||||
//require('./database.js')();
|
||||
//data.init();
|
||||
|
||||
var device = data.Device.build({
|
||||
name: 'A23',
|
||||
url: "http://192.168.1.126/"
|
||||
});
|
||||
|
||||
// device.save().then().catch(
|
||||
// err => {console.log(err);}
|
||||
// )
|
||||
@@ -157,7 +361,7 @@ cron.schedule(' */30 * * * *', () => {//cron.schedule('*/5 * * * * *', () =>
|
||||
StoreSensorReadingsAsync();
|
||||
}).start();
|
||||
|
||||
StoreSensorReadings();
|
||||
//StoreSensorReadings();
|
||||
|
||||
async function StoreSensorReadingsAsync()
|
||||
{
|
||||
|
||||
37
models/index.js
Normal file
37
models/index.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Sequelize = require('sequelize');
|
||||
const basename = path.basename(__filename);
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const config = require(__dirname + '/../config/config.json')[env];
|
||||
const db = {};
|
||||
|
||||
let sequelize;
|
||||
if (config.use_env_variable) {
|
||||
sequelize = new Sequelize(process.env[config.use_env_variable], config);
|
||||
} else {
|
||||
sequelize = new Sequelize(config.database, config.username, config.password, config);
|
||||
}
|
||||
|
||||
fs
|
||||
.readdirSync(__dirname)
|
||||
.filter(file => {
|
||||
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
|
||||
})
|
||||
.forEach(file => {
|
||||
const model = sequelize['import'](path.join(__dirname, file));
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
db.sequelize = sequelize;
|
||||
db.Sequelize = Sequelize;
|
||||
|
||||
module.exports = db;
|
||||
768
package-lock.json
generated
768
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -18,8 +18,13 @@
|
||||
"homepage": "",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
"cors": "2.8.5",
|
||||
"ejs": "^3.0.2",
|
||||
"express": "^4.8.7",
|
||||
"express-ejs-layouts": "^2.5.0",
|
||||
"express-namespace": "^0.1.1",
|
||||
"express-session": "^1.17.0",
|
||||
"got": "^10.7.0",
|
||||
"helmet": "3.21.3",
|
||||
"jsonwebtoken": "^7.3.0",
|
||||
@@ -31,9 +36,15 @@
|
||||
"mysql2": "^2.1.0",
|
||||
"node-cron": "^2.0.3",
|
||||
"node-uuid": "^1.4.8",
|
||||
"passport": "^0.4.1",
|
||||
"passport-auth0": "^1.3.2",
|
||||
"passport-local": "^1.0.0",
|
||||
"plaintextparser": "^1.0.3",
|
||||
"request": "^2.88.2",
|
||||
"sequelize": "^5.21.5",
|
||||
"sequelize-cli": "^5.5.1",
|
||||
"swagger-ui-express": "^2.0.13",
|
||||
"sync-request": "^4.0.2"
|
||||
"sync-request": "^4.0.2",
|
||||
"vash": "^0.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
142
public/css/bootstrap4-toggle.css
vendored
Normal file
142
public/css/bootstrap4-toggle.css
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
/*\
|
||||
|*| ========================================================================
|
||||
|*| Bootstrap Toggle: bootstrap4-toggle.css v3.6.1
|
||||
|*| https://gitbrent.github.io/bootstrap4-toggle/
|
||||
|*| ========================================================================
|
||||
|*| Copyright 2018-2019 Brent Ely
|
||||
|*| Licensed under MIT
|
||||
|*| ========================================================================
|
||||
\*/
|
||||
|
||||
/*
|
||||
* @added 3.0.0: Return support for "*-xs" removed in Bootstrap-4
|
||||
* @see: [Comment](https://github.com/twbs/bootstrap/issues/21881#issuecomment-341972830)
|
||||
*/
|
||||
.btn-group-xs > .btn, .btn-xs {
|
||||
padding: .35rem .4rem .25rem .4rem;
|
||||
font-size: .875rem;
|
||||
line-height: .5;
|
||||
border-radius: .2rem;
|
||||
}
|
||||
|
||||
.checkbox label .toggle, .checkbox-inline .toggle {
|
||||
margin-left: -1.25rem;
|
||||
margin-right: .35rem;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.toggle.btn.btn-light, .toggle.btn.btn-outline-light {
|
||||
/* bootstrap-4 - add a border so toggle is delineated */
|
||||
border-color: rgba(0, 0, 0, .15);
|
||||
}
|
||||
.toggle input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
.toggle-group {
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
transition: left 0.35s;
|
||||
-webkit-transition: left 0.35s;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.toggle-group label, .toggle-group span { cursor: pointer; }
|
||||
.toggle.off .toggle-group {
|
||||
left: -100%;
|
||||
}
|
||||
.toggle-on {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 50%;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
.toggle-off {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none; /* Bootstrap 4.0 Support via (Issue #186)[https://github.com/minhur/bootstrap-toggle/issues/186]) */
|
||||
}
|
||||
.toggle-handle {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
height: 100%;
|
||||
width: 0px;
|
||||
border-width: 0 1px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.toggle.btn-outline-primary .toggle-handle {
|
||||
background-color: var(--primary);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
.toggle.btn-outline-secondary .toggle-handle {
|
||||
background-color: var(--secondary);
|
||||
border-color: var(--secondary);
|
||||
}
|
||||
.toggle.btn-outline-success .toggle-handle {
|
||||
background-color: var(--success);
|
||||
border-color: var(--success);
|
||||
}
|
||||
.toggle.btn-outline-danger .toggle-handle {
|
||||
background-color: var(--danger);
|
||||
border-color: var(--danger);
|
||||
}
|
||||
.toggle.btn-outline-warning .toggle-handle {
|
||||
background-color: var(--warning);
|
||||
border-color: var(--warning);
|
||||
}
|
||||
.toggle.btn-outline-info .toggle-handle {
|
||||
background-color: var(--info);
|
||||
border-color: var(--info);
|
||||
}
|
||||
.toggle.btn-outline-light .toggle-handle {
|
||||
background-color: var(--light);
|
||||
border-color: var(--light);
|
||||
}
|
||||
.toggle.btn-outline-dark .toggle-handle {
|
||||
background-color: var(--dark);
|
||||
border-color: var(--dark);
|
||||
}
|
||||
.toggle[class*="btn-outline"]:hover .toggle-handle {
|
||||
background-color: var(--light);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* NOTE: Must come first, so classes below override as needed */
|
||||
/* [default] (bootstrap-4.1.3 - .btn - h:38px) */
|
||||
.toggle.btn { min-width: 3.7rem; min-height: 2.15rem; }
|
||||
.toggle-on.btn { padding-right: 1.5rem; }
|
||||
.toggle-off.btn { padding-left: 1.5rem; }
|
||||
|
||||
/* `lg` (bootstrap-4.1.3 - .btn - h:48px) */
|
||||
.toggle.btn-lg { min-width: 5rem; min-height: 2.815rem; }
|
||||
.toggle-on.btn-lg { padding-right: 2rem; }
|
||||
.toggle-off.btn-lg { padding-left: 2rem; }
|
||||
.toggle-handle.btn-lg { width: 2.5rem; }
|
||||
|
||||
/* `sm` (bootstrap-4.1.3 - .btn - h:31px) */
|
||||
.toggle.btn-sm { min-width: 3.125rem; min-height: 1.938rem; }
|
||||
.toggle-on.btn-sm { padding-right: 1rem; }
|
||||
.toggle-off.btn-sm { padding-left: 1rem; }
|
||||
|
||||
/* `xs` (bootstrap-3.3 - .btn - h:22px) */
|
||||
.toggle.btn-xs { min-width: 2.19rem; min-height: 1.375rem; }
|
||||
.toggle-on.btn-xs { padding-right: .8rem; }
|
||||
.toggle-off.btn-xs { padding-left: .8rem; }
|
||||
187
public/js/bootstrap4-toggle.js
vendored
Normal file
187
public/js/bootstrap4-toggle.js
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
/*\
|
||||
|*| ========================================================================
|
||||
|*| Bootstrap Toggle: bootstrap4-toggle.js v3.6.1
|
||||
|*| https://gitbrent.github.io/bootstrap4-toggle/
|
||||
|*| ========================================================================
|
||||
|*| Copyright 2018-2019 Brent Ely
|
||||
|*| Licensed under MIT
|
||||
|*| ========================================================================
|
||||
\*/
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// TOGGLE PUBLIC CLASS DEFINITION
|
||||
// ==============================
|
||||
|
||||
var Toggle = function (element, options) {
|
||||
this.$element = $(element)
|
||||
this.options = $.extend({}, this.defaults(), options)
|
||||
this.render()
|
||||
}
|
||||
|
||||
Toggle.VERSION = '3.7.0-beta'
|
||||
|
||||
Toggle.DEFAULTS = {
|
||||
on: 'On',
|
||||
off: 'Off',
|
||||
onstyle: 'primary',
|
||||
offstyle: 'light',
|
||||
size: 'normal',
|
||||
style: '',
|
||||
width: null,
|
||||
height: null
|
||||
}
|
||||
|
||||
Toggle.prototype.defaults = function() {
|
||||
return {
|
||||
on: this.$element.attr('data-on') || Toggle.DEFAULTS.on,
|
||||
off: this.$element.attr('data-off') || Toggle.DEFAULTS.off,
|
||||
onstyle: this.$element.attr('data-onstyle') || Toggle.DEFAULTS.onstyle,
|
||||
offstyle: this.$element.attr('data-offstyle') || Toggle.DEFAULTS.offstyle,
|
||||
size: this.$element.attr('data-size') || Toggle.DEFAULTS.size,
|
||||
style: this.$element.attr('data-style') || Toggle.DEFAULTS.style,
|
||||
width: this.$element.attr('data-width') || Toggle.DEFAULTS.width,
|
||||
height: this.$element.attr('data-height') || Toggle.DEFAULTS.height
|
||||
}
|
||||
}
|
||||
|
||||
Toggle.prototype.render = function () {
|
||||
this._onstyle = 'btn-' + this.options.onstyle
|
||||
this._offstyle = 'btn-' + this.options.offstyle
|
||||
var size
|
||||
= this.options.size === 'large' || this.options.size === 'lg' ? 'btn-lg'
|
||||
: this.options.size === 'small' || this.options.size === 'sm' ? 'btn-sm'
|
||||
: this.options.size === 'mini' || this.options.size === 'xs' ? 'btn-xs'
|
||||
: ''
|
||||
var $toggleOn = $('<label for="'+ this.$element.prop('id') +'" class="btn">').html(this.options.on)
|
||||
.addClass(this._onstyle + ' ' + size)
|
||||
var $toggleOff = $('<label for="'+ this.$element.prop('id') +'" class="btn">').html(this.options.off)
|
||||
.addClass(this._offstyle + ' ' + size)
|
||||
var $toggleHandle = $('<span class="toggle-handle btn btn-light">')
|
||||
.addClass(size)
|
||||
var $toggleGroup = $('<div class="toggle-group">')
|
||||
.append($toggleOn, $toggleOff, $toggleHandle)
|
||||
var $toggle = $('<div class="toggle btn" data-toggle="toggle" role="button">')
|
||||
.addClass( this.$element.prop('checked') ? this._onstyle : this._offstyle+' off' )
|
||||
.addClass(size).addClass(this.options.style)
|
||||
|
||||
this.$element.wrap($toggle)
|
||||
$.extend(this, {
|
||||
$toggle: this.$element.parent(),
|
||||
$toggleOn: $toggleOn,
|
||||
$toggleOff: $toggleOff,
|
||||
$toggleGroup: $toggleGroup
|
||||
})
|
||||
this.$toggle.append($toggleGroup)
|
||||
|
||||
var width = this.options.width || Math.max($toggleOn.outerWidth(), $toggleOff.outerWidth())+($toggleHandle.outerWidth()/2)
|
||||
var height = this.options.height || Math.max($toggleOn.outerHeight(), $toggleOff.outerHeight())
|
||||
$toggleOn.addClass('toggle-on')
|
||||
$toggleOff.addClass('toggle-off')
|
||||
this.$toggle.css({ width: width, height: height })
|
||||
if (this.options.height) {
|
||||
$toggleOn.css('line-height', $toggleOn.height() + 'px')
|
||||
$toggleOff.css('line-height', $toggleOff.height() + 'px')
|
||||
}
|
||||
this.update(true)
|
||||
this.trigger(true)
|
||||
}
|
||||
|
||||
Toggle.prototype.toggle = function () {
|
||||
if (this.$element.prop('checked')) this.off()
|
||||
else this.on()
|
||||
}
|
||||
|
||||
Toggle.prototype.on = function (silent) {
|
||||
if (this.$element.prop('disabled')) return false
|
||||
this.$toggle.removeClass(this._offstyle + ' off').addClass(this._onstyle)
|
||||
this.$element.prop('checked', true)
|
||||
if (!silent) this.trigger()
|
||||
}
|
||||
|
||||
Toggle.prototype.off = function (silent) {
|
||||
if (this.$element.prop('disabled')) return false
|
||||
this.$toggle.removeClass(this._onstyle).addClass(this._offstyle + ' off')
|
||||
this.$element.prop('checked', false)
|
||||
if (!silent) this.trigger()
|
||||
}
|
||||
|
||||
Toggle.prototype.enable = function () {
|
||||
this.$toggle.removeClass('disabled')
|
||||
this.$toggle.removeAttr('disabled')
|
||||
this.$element.prop('disabled', false)
|
||||
}
|
||||
|
||||
Toggle.prototype.disable = function () {
|
||||
this.$toggle.addClass('disabled')
|
||||
this.$toggle.attr('disabled', 'disabled')
|
||||
this.$element.prop('disabled', true)
|
||||
}
|
||||
|
||||
Toggle.prototype.update = function (silent) {
|
||||
if (this.$element.prop('disabled')) this.disable()
|
||||
else this.enable()
|
||||
if (this.$element.prop('checked')) this.on(silent)
|
||||
else this.off(silent)
|
||||
}
|
||||
|
||||
Toggle.prototype.trigger = function (silent) {
|
||||
this.$element.off('change.bs.toggle')
|
||||
if (!silent) this.$element.change()
|
||||
this.$element.on('change.bs.toggle', $.proxy(function() {
|
||||
this.update()
|
||||
}, this))
|
||||
}
|
||||
|
||||
Toggle.prototype.destroy = function() {
|
||||
this.$element.off('change.bs.toggle')
|
||||
this.$toggleGroup.remove()
|
||||
this.$element.removeData('bs.toggle')
|
||||
this.$element.unwrap()
|
||||
}
|
||||
|
||||
// TOGGLE PLUGIN DEFINITION
|
||||
// ========================
|
||||
|
||||
function Plugin(option) {
|
||||
var optArg = Array.prototype.slice.call( arguments, 1 )[0]
|
||||
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.toggle')
|
||||
var options = typeof option == 'object' && option
|
||||
|
||||
if (!data) $this.data('bs.toggle', (data = new Toggle(this, options)))
|
||||
if (typeof option === 'string' && data[option] && typeof optArg === 'boolean') data[option](optArg)
|
||||
else if (typeof option === 'string' && data[option]) data[option]()
|
||||
//else if (option && !data[option]) console.log('bootstrap-toggle: error: method `'+ option +'` does not exist!');
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.bootstrapToggle
|
||||
|
||||
$.fn.bootstrapToggle = Plugin
|
||||
$.fn.bootstrapToggle.Constructor = Toggle
|
||||
|
||||
// TOGGLE NO CONFLICT
|
||||
// ==================
|
||||
|
||||
$.fn.toggle.noConflict = function () {
|
||||
$.fn.bootstrapToggle = old
|
||||
return this
|
||||
}
|
||||
|
||||
// TOGGLE DATA-API
|
||||
// ===============
|
||||
|
||||
$(function() {
|
||||
$('input[type=checkbox][data-toggle^=toggle]').bootstrapToggle()
|
||||
})
|
||||
|
||||
$(document).on('click.bs.toggle', 'div[data-toggle^=toggle]', function(e) {
|
||||
var $checkbox = $(this).find('input[type=checkbox]')
|
||||
$checkbox.bootstrapToggle('toggle')
|
||||
e.preventDefault()
|
||||
})
|
||||
}(jQuery);
|
||||
11
public/js/bootstrap4-toggle.min.js
vendored
Normal file
11
public/js/bootstrap4-toggle.min.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/*\
|
||||
|*| ========================================================================
|
||||
|*| Bootstrap Toggle: bootstrap4-toggle.js v3.6.1
|
||||
|*| https://gitbrent.github.io/bootstrap4-toggle/
|
||||
|*| ========================================================================
|
||||
|*| Copyright 2018-2019 Brent Ely
|
||||
|*| Licensed under MIT
|
||||
|*| ========================================================================
|
||||
\*/
|
||||
!function(a){"use strict";function l(t,e){this.$element=a(t),this.options=a.extend({},this.defaults(),e),this.render()}l.VERSION="3.6.0",l.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"light",size:"normal",style:"",width:null,height:null},l.prototype.defaults=function(){return{on:this.$element.attr("data-on")||l.DEFAULTS.on,off:this.$element.attr("data-off")||l.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||l.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||l.DEFAULTS.offstyle,size:this.$element.attr("data-size")||l.DEFAULTS.size,style:this.$element.attr("data-style")||l.DEFAULTS.style,width:this.$element.attr("data-width")||l.DEFAULTS.width,height:this.$element.attr("data-height")||l.DEFAULTS.height}},l.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var t="large"===this.options.size||"lg"===this.options.size?"btn-lg":"small"===this.options.size||"sm"===this.options.size?"btn-sm":"mini"===this.options.size||"xs"===this.options.size?"btn-xs":"",e=a('<label for="'+this.$element.prop("id")+'" class="btn">').html(this.options.on).addClass(this._onstyle+" "+t),s=a('<label for="'+this.$element.prop("id")+'" class="btn">').html(this.options.off).addClass(this._offstyle+" "+t),o=a('<span class="toggle-handle btn btn-light">').addClass(t),i=a('<div class="toggle-group">').append(e,s,o),l=a('<div class="toggle btn" data-toggle="toggle" role="button">').addClass(this.$element.prop("checked")?this._onstyle:this._offstyle+" off").addClass(t).addClass(this.options.style);this.$element.wrap(l),a.extend(this,{$toggle:this.$element.parent(),$toggleOn:e,$toggleOff:s,$toggleGroup:i}),this.$toggle.append(i);var n=this.options.width||Math.max(e.outerWidth(),s.outerWidth())+o.outerWidth()/2,h=this.options.height||Math.max(e.outerHeight(),s.outerHeight());e.addClass("toggle-on"),s.addClass("toggle-off"),this.$toggle.css({width:n,height:h}),this.options.height&&(e.css("line-height",e.height()+"px"),s.css("line-height",s.height()+"px")),this.update(!0),this.trigger(!0)},l.prototype.toggle=function(){this.$element.prop("checked")?this.off():this.on()},l.prototype.on=function(t){if(this.$element.prop("disabled"))return!1;this.$toggle.removeClass(this._offstyle+" off").addClass(this._onstyle),this.$element.prop("checked",!0),t||this.trigger()},l.prototype.off=function(t){if(this.$element.prop("disabled"))return!1;this.$toggle.removeClass(this._onstyle).addClass(this._offstyle+" off"),this.$element.prop("checked",!1),t||this.trigger()},l.prototype.enable=function(){this.$toggle.removeClass("disabled"),this.$toggle.removeAttr("disabled"),this.$element.prop("disabled",!1)},l.prototype.disable=function(){this.$toggle.addClass("disabled"),this.$toggle.attr("disabled","disabled"),this.$element.prop("disabled",!0)},l.prototype.update=function(t){this.$element.prop("disabled")?this.disable():this.enable(),this.$element.prop("checked")?this.on(t):this.off(t)},l.prototype.trigger=function(t){this.$element.off("change.bs.toggle"),t||this.$element.change(),this.$element.on("change.bs.toggle",a.proxy(function(){this.update()},this))},l.prototype.destroy=function(){this.$element.off("change.bs.toggle"),this.$toggleGroup.remove(),this.$element.removeData("bs.toggle"),this.$element.unwrap()};var t=a.fn.bootstrapToggle;a.fn.bootstrapToggle=function(o){var i=Array.prototype.slice.call(arguments,1)[0];return this.each(function(){var t=a(this),e=t.data("bs.toggle"),s="object"==typeof o&&o;e||t.data("bs.toggle",e=new l(this,s)),"string"==typeof o&&e[o]&&"boolean"==typeof i?e[o](i):"string"==typeof o&&e[o]&&e[o]()})},a.fn.bootstrapToggle.Constructor=l,a.fn.toggle.noConflict=function(){return a.fn.bootstrapToggle=t,this},a(function(){a("input[type=checkbox][data-toggle^=toggle]").bootstrapToggle()}),a(document).on("click.bs.toggle","div[data-toggle^=toggle]",function(t){a(this).find("input[type=checkbox]").bootstrapToggle("toggle"),t.preventDefault()})}(jQuery);
|
||||
//# sourceMappingURL=bootstrap4-toggle.min.js.map
|
||||
110
server.js
Normal file
110
server.js
Normal file
@@ -0,0 +1,110 @@
|
||||
var express = require('express');
|
||||
var passport = require('passport');
|
||||
var Strategy = require('passport-local').Strategy;
|
||||
var db = require('./db');
|
||||
|
||||
|
||||
// Configure the local strategy for use by Passport.
|
||||
//
|
||||
// The local strategy require a `verify` function which receives the credentials
|
||||
// (`username` and `password`) submitted by the user. The function must verify
|
||||
// that the password is correct and then invoke `cb` with a user object, which
|
||||
// will be set at `req.user` in route handlers after authentication.
|
||||
passport.use(new Strategy({
|
||||
passReqToCallback: true},
|
||||
function(username, password, cb) {
|
||||
console.log('requesting authentication for user '+ username);
|
||||
db.users.findByUsername(username, function(err, user) {
|
||||
if (err) { return cb(err); }
|
||||
if (!user) { return cb(null, false); }
|
||||
if (user.password != password) { return cb(null, false); }
|
||||
return cb(null, user);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
// Configure Passport authenticated session persistence.
|
||||
//
|
||||
// In order to restore authentication state across HTTP requests, Passport needs
|
||||
// to serialize users into and deserialize users out of the session. The
|
||||
// typical implementation of this is as simple as supplying the user ID when
|
||||
// serializing, and querying the user record by ID from the database when
|
||||
// deserializing.
|
||||
passport.serializeUser(function(user, cb) {
|
||||
cb(null, user.id);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(id, cb) {
|
||||
db.users.findById(id, function (err, user) {
|
||||
if (err) { return cb(err); }
|
||||
cb(null, user);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// Create a new Express application.
|
||||
var app = express();
|
||||
|
||||
// Configure view engine to render EJS templates.
|
||||
//app.set('views', __dirname + '/views');
|
||||
//app.set('view engine', 'ejs');
|
||||
app.set('view engine', 'vash');
|
||||
app.use(express.static('public'))
|
||||
|
||||
// Use application-level middleware for common functionality, including
|
||||
// logging, parsing, and session handling.
|
||||
app.use(require('morgan')('combined'));
|
||||
app.use(require('body-parser').urlencoded({ extended: true }));
|
||||
app.use(require('express-session')({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));
|
||||
|
||||
// Initialize Passport and restore authentication state, if any, from the
|
||||
// session.
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// Define routes.
|
||||
app.get('/',
|
||||
function(req, res) {
|
||||
res.render('layout', { user: req.user });
|
||||
});
|
||||
|
||||
app.get('/login',
|
||||
function(req, res){
|
||||
res.render('login');
|
||||
});
|
||||
|
||||
app.post('/login',
|
||||
passport.authenticate('local', { failureRedirect: '/n/login' }),
|
||||
function(req, res) {
|
||||
res.redirect('/n/');
|
||||
});
|
||||
|
||||
app.get('/logout',
|
||||
function(req, res){
|
||||
req.logout();
|
||||
res.redirect('/n/');
|
||||
});
|
||||
|
||||
app.get('/accontrol',
|
||||
// passport.authenticate('local', {
|
||||
// failureRedirect: '/n/login' ,
|
||||
// successRedirect: '/n/accontrol'}),
|
||||
require('connect-ensure-login').ensureLoggedIn('/n/login'),
|
||||
function(req, res){
|
||||
res.render('accontrol', { user: req.user });
|
||||
});
|
||||
|
||||
//app.listen(81);
|
||||
|
||||
var fs = require("fs");
|
||||
var https = require('https');
|
||||
var privateKey = fs.readFileSync('/etc/letsencrypt/live/iot.d-popov.com/privkey.pem', 'utf8');
|
||||
var certificate = fs.readFileSync('/etc/letsencrypt/live/iot.d-popov.com/cert.pem', 'utf8');
|
||||
var credentials = {key: privateKey, cert: certificate};
|
||||
|
||||
var httpsServer = https.createServer(credentials, app);
|
||||
httpsServer.listen(8443, () => {
|
||||
console.log('HTTP server listening on port 8443');
|
||||
});
|
||||
20
views/accontrol.ejs
Normal file
20
views/accontrol.ejs
Normal file
@@ -0,0 +1,20 @@
|
||||
<h2>AC Control for <%= model.user.username %></h2>
|
||||
<form action="/n/accontrol" class="form-inline" method="POST">
|
||||
<div class="form-group">
|
||||
<input type='checkbox' name='power' <%= model.data.power|true ? "checked" : "" %> data-toggle='toggle'
|
||||
data-style='android' data-on='ON' data-off='Off' />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type='checkbox' name='heat' <%= model.data.heat|true ? "checked" : "" %> data-toggle='toggle' data-on='Heat'
|
||||
data-off='Cool' data-onstyle='warning' data-offstyle='info' />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type='number' name='temp' value='<%= model.data.temp|23%>' min='16' max='32' step='0.5' />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<!-- <input type='checkbox' name='econo'<%= model.data.econo ? "checked" : "" %> data-toggle='toggle' data-style='android' data-on='Eco' data-off='Normal' data-onstyle='info' data-offstyle='warning'/> -->
|
||||
<input type='submit' class='button' value='SET!'>
|
||||
</div>
|
||||
</form>
|
||||
INFO:
|
||||
<%= model.info %>
|
||||
128
views/chart.ejs
Normal file
128
views/chart.ejs
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
<div id="chartContainer" style="height: 370px; width: 100%;"></div>
|
||||
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
|
||||
|
||||
<%- contentFor('head') %>
|
||||
<script>
|
||||
var current;
|
||||
window.onload = function () {
|
||||
var dataPoints1 = [];
|
||||
var dataPoints2 = [];
|
||||
/*{ x: new Date(2017, 0, 3), y: 650 },
|
||||
{ x: new Date(2017, 0, 4), y: 700 },
|
||||
*/
|
||||
|
||||
var chart = new CanvasJS.Chart("chartContainer", {
|
||||
animationEnabled: true,
|
||||
theme: "light2",
|
||||
zoomEnabled: true,
|
||||
zoomType: "x",
|
||||
exportEnabled: true,
|
||||
title:{
|
||||
text: "Retrieving current A23 conditions..."
|
||||
},
|
||||
/* subtitles:[{
|
||||
text: "X Axis scale is Logarithmic",
|
||||
fontSize: 14
|
||||
}],*/
|
||||
axisX:{
|
||||
valueFormatString: "HH:mm DD MMM",
|
||||
crosshair: {
|
||||
enabled: true,
|
||||
snapToDataPoint: true
|
||||
}
|
||||
},
|
||||
axisY: {
|
||||
title: "Temperature",
|
||||
lineColor: "#C24642",
|
||||
titleFontColor: "#C24642",
|
||||
labelFontColor: "#C24642",
|
||||
crosshair: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
axisY2: [
|
||||
{
|
||||
title: "Relative hum %",
|
||||
titleFontColor: "#51CDA0",
|
||||
labelFontColor: "#51CDA0"
|
||||
}
|
||||
],
|
||||
//{title: "Temperature",
|
||||
//lineColor: "#C24642",
|
||||
//titleFontColor: "#C0504E",
|
||||
//labelFontColor: "#C0504E"
|
||||
//},
|
||||
toolTip:{
|
||||
shared:true
|
||||
},
|
||||
legend:{
|
||||
cursor:"pointer",
|
||||
verticalAlign: "bottom",
|
||||
horizontalAlign: "left",
|
||||
dockInsidePlotArea: true,
|
||||
itemclick: toogleDataSeries
|
||||
},
|
||||
data: [{
|
||||
name: "Temperature",
|
||||
type: "spline",//line
|
||||
showInLegend: true,
|
||||
markerType: "square",
|
||||
xValueFormatString: "HH:mm on DD MMM, YYYY",
|
||||
color: "#F08080",
|
||||
dataPoints: dataPoints2
|
||||
},
|
||||
{
|
||||
name: "Humidiry",
|
||||
type: "spline",
|
||||
axisYType: "secondary",
|
||||
axisYIndex: 1,
|
||||
showInLegend: true,
|
||||
lineDashType: "dash",
|
||||
dataPoints: dataPoints1
|
||||
}]
|
||||
});
|
||||
|
||||
|
||||
function toogleDataSeries(e){
|
||||
if (typeof(e.dataSeries.visible) === "undefined" || e.dataSeries.visible) {
|
||||
e.dataSeries.visible = false;
|
||||
} else {
|
||||
e.dataSeries.visible = true;
|
||||
}
|
||||
chart.render();
|
||||
}
|
||||
|
||||
function addData(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if(data[i].field_name == "A23_DHT"){
|
||||
var dht = JSON.parse(data[i].field_value).dht;
|
||||
var date = new Date(data[i]["timestamp"]);
|
||||
if(dht && dht.hum <= 100){
|
||||
dataPoints1.push({
|
||||
x: date,
|
||||
y: dht.hum
|
||||
});
|
||||
dataPoints2.push({
|
||||
x: date,
|
||||
y: dht.temp
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
chart.render();
|
||||
|
||||
$.getJSON("/n/dht", function(data){
|
||||
if(data && data.dht){
|
||||
chart.title.set("text", "A23 Currently is " + data.dht.temp + "°C, " + data.dht.hum +"% RH" );
|
||||
}else {
|
||||
chart.title.set("text", "A23 conditions");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$.getJSON("/n/dht/A23_DHT", addData);
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
28
views/layout.ejs
Normal file
28
views/layout.ejs
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="data:,">
|
||||
<title>ESP8266 Weather Server</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/n/css/bootstrap4-toggle.css" type="text/css" >
|
||||
<style>
|
||||
html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
|
||||
.button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}
|
||||
.button2 {background-color: #77878A;}
|
||||
</style>
|
||||
<%- defineContent("head") %>
|
||||
</head>
|
||||
<body>
|
||||
<%- include('nav'); -%>
|
||||
<main role='main' class='container'>
|
||||
<%- body %>
|
||||
</main>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
|
||||
<!-- <script src='https://code.jquery.com/jquery-3.2.1.slim.min.js' integrity='sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN' crossorigin='anonymous'></script> -->
|
||||
<script src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js' integrity='sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q' crossorigin='anonymous'></script>
|
||||
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js' integrity='sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl' crossorigin='anonymous'></script>
|
||||
<script src="/n/js/bootstrap4-toggle.js" integrity="sha256-8hY+ssbh4ap2bCD3tlbINeezcjxRL5IlWDmzNLQBO/U="></script>
|
||||
<%- defineContent("scripts") %>
|
||||
</body>
|
||||
</html>
|
||||
13
views/login.ejs
Normal file
13
views/login.ejs
Normal file
@@ -0,0 +1,13 @@
|
||||
<form action="/n/login" method="post">
|
||||
<div class="form-group">
|
||||
<label>Username:</label>
|
||||
<input type="text" name="username" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password:</label>
|
||||
<input type="password" name="password" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" class='btn btn-default button' value="Log In" />
|
||||
</div>
|
||||
</form>
|
||||
4
views/nav.ejs
Normal file
4
views/nav.ejs
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
<a href="/n/accontrol" >control</a>
|
||||
<a href="/n/logout" >log out</a>
|
||||
<a href="/n/chart"> chart</a>
|
||||
Reference in New Issue
Block a user