This commit is contained in:
2024-01-30 14:44:56 -05:00
98 changed files with 12618 additions and 777 deletions

View File

@ -2,12 +2,10 @@ const express = require("express");
const { rateLimit } = require("express-rate-limit");
const path = require("path");
const app = express();
const port = 3000;
const ejs = require("ejs");
module.exports = app;
process.nextTick(() => require("./mqttApp"));
//process.nextTick(() => require("./mqttApp"));
app.use(express.json());
app.set("json spaces", 2);
@ -50,49 +48,48 @@ app.use("/", require("./routes/render"));
// Catch 404 and forward to error handler. If none of the above routes are
// used, this is what will be called.
app.use(function (req, res, next) {
//application/json; charset=utf-8
if (req.is("application/json" || "application/json; charset=utf-8")) {
var err = new Error("Not Found");
err.message = "Page not found";
err.status = 404;
next(err);
} else {
res.status(404).render("404");
}
//application/json; charset=utf-8
var err = new Error("Not Found");
err.message = "Page not found";
err.status = 404;
next(err);
});
// Error handler. This is where `next()` will go on error
app.use(function (err, req, res, next) {
console.error(err.status || res.status, err.name, req.method, req.url);
if (![404].includes(err.status || res.status)) {
console.error(err.message);
console.error(err.stack);
console.error("=========================================");
}
console.error(err.status || res.status, err.name, req.method, req.url);
console.log(err.name + " validation error");
// Parse key error for Sequilzw
let keyErrors = {};
if (["SequelizeValidationError"].includes(err.name) && err.errors) {
for (let item of err.errors) {
if (item.path) {
keyErrors[item.path] = item.message;
}
}
res.status = 422;
}
// Parse key error for Sequilzw
let keyErrors = {};
if (["SequelizeValidationError"].includes(err.name) && err.errors) {
for (let item of err.errors) {
if (item.path) {
keyErrors[item.path] = item.message;
}
}
res.status = 422;
}
if (![404, 422].includes(err.status || res.status)) {
console.error(err.message);
console.error(err.stack);
console.error("=========================================");
}
if (![404, 401, 422].includes(err.status || res.status)) {
console.error(err.message);
console.error(err.stack);
console.error("=========================================");
}
res.status(err.status || 500);
// res.status(err.status || 500);
res.status(err.status || 500);
res.json({
name: err.name || "Unknown error",
message: err.message,
keyErrors,
});
if (req.get('Content-Type') && req.get('Content-Type').includes("json")) {
res.json({
name: err.name || "Unknown error",
message: err.message,
keyErrors,
});
}
else {
res.json({
name: err.name || "Unknown error",
message: err.message,
keyErrors,
});
}
});

View File

@ -107,4 +107,6 @@ const sensorModel = sequelize.define(
}
);
//sensorModel.belongsTo(locationModel);
module.exports = { sensorModel };

View File

@ -48,6 +48,14 @@ const tokenModel = sequelize.define(
isIn: [["canRead", "canWrite",]],
},
},
isKey: {
type: DataTypes.STRING,
allowNull: true,
length: 45,
validate:{
isIn: [["isKey" , "isNotKey"]],
}
},
expiration: {
type: DataTypes.DATE,
allowNull: false,

View File

@ -13,14 +13,16 @@ const sequelize = new Sequelize(
dialect: process.env.DB_dialect,
storage: process.env.DB_storage,
logging: process.env.DB_logging,
// attributeBehavior?: 'escape' | 'throw' | 'unsafe-legacy';
attributeBehavior: 'escape',
dialectOptions: {
ssl: {
ca: fs.readFileSync(path.resolve(__dirname, '../cert/DigiCertGlobalRootCA.crt.pem')),
ca: fs.readFileSync(path.resolve(__dirname, '../cert/DigiCertGlobalRootCA.crt_3.pem')),
},
},
},
);
sequelize.authenticate().then(() => {

View File

@ -1,24 +1,48 @@
const { hash, compareHash } = require("./bcrypt.js");
const { tokenModel } = require("../database/model/tokenModel.js");
const { userModel } = require("../database/model/userModel");
const { hash, compareHash } = require("./bcrypt.js");
const { generateUUID } = require("./generateUUID.js");
const { isValid , resetIsValid } = require("./isValid");
/*
1) take userid
2) generate random api key
3) hash the api key
4) append userid with - and api key
5) you give the user rowid-uuidv4
6) store in database
*/
//can be used for api key or token. Both are the same logic
async function addToken(userId, permission , expiry) {
async function getTokenByToken(token) {
const splitAuthToken = token.split("-");
const rowid = splitAuthToken[0];
const suppliedToken = splitAuthToken.slice(1).join("-");
if (!suppliedToken) return false;
token = await tokenModel.findByPk(rowid, { include: userModel });
token.isValid = await compareHash(suppliedToken, token.token); //true
console.log("function api getTokenByToken token", token.isValid);
token.isValid = token.isValid && isValid(token.expiration);
console.log("function api getTokenByToken token", token.isValid);
if (!token.isValid) {
//add boolean to token table
token.destroy();
}
/*
console.log(
"function api getTokenByToken token",
await compareHash(suppliedToken, token.token),
isValid("token" , token.expiration)
);
*/
console.log(token.isValid);
return token;
}
async function addToken(userId, permission, isKey ,expiry) {
let uuid = await generateUUID();
let hashtoken = await hash(uuid);
//console.log("user id", userId);
// return { token: token, userid: userRes.id, username: userRes.username };
// let token = await addToken(userRes.id , "canRead" , tokenToLive);
let token = await tokenModel.create({
userid: userId,
token: hashtoken,
permission: permission,
isKey: isKey,
expiration: expiry,
});
@ -26,23 +50,58 @@ async function addToken(userId, permission , expiry) {
return token.id + "-" + uuid;
}
async function checkToken(Supplied, rowid) {
try {
const retrivedToken = await tokenModel.findOne({
raw: true,
attributes: ["token", "permission"],
where: {
id: rowid,
},
});
//console.log(retrivedKey.apikey);
if (compareHash(Supplied, retrivedToken.token)) {
//return true;
return retrivedToken.permission;
}
} catch (error) {
console.error(error);
}
async function addPasswordResetToken(data , token){
let hashtoken = await hash(token);
let currentDate = new Date();
let tokenToLive = new Date(currentDate.getTime() + 5 * 60000);
let tokenRes = await tokenModel.create({
userid: data.id,
token: hashtoken,
permission: "canRead",
isKey: "isNotKey",
expiration: tokenToLive,
});
return tokenRes.id
}
module.exports = { addToken , checkToken };
async function checkToken(id) {
let tokenRes = await tokenModel.findOne(
{
where: {
userid: id,
}
}
);
return tokenRes;
}
async function checkTokenByrowID(token) {
if (!token) return false;
//split
const splitAuthToken = token.split("-");
const rowid = splitAuthToken[0];
const suppliedToken = splitAuthToken.slice(1).join("-");
let tokenRes = await tokenModel.findByPk(rowid);
//console.log(tokenRes);
if (!tokenRes) return false;
if (!compareHash(suppliedToken, tokenRes.token)) return false;
//pass tokemRes.expiration to isValid
if (!isValid(tokenRes.expiration)) {
//add boolean to token table
tokenRes.destroy();
return false;
}
return tokenRes;
}
module.exports = { addToken, getTokenByToken , checkToken , addPasswordResetToken , checkTokenByrowID};

View File

@ -0,0 +1,11 @@
const { api_log_Model } = require("../database/model/apiLogModel");
const {Op} = require("sequelize");
async function getAllLog(){
return await api_log_Model.findAll();
}
module.exports = { getAllLog };

View File

@ -1,20 +1,26 @@
const moment = require("moment");
const currentTime = moment().format("YYYY-MM-DD HH:mm:ss");
//time is taken from the token
function isValid(time){
const timeDiff = moment(currentTime).diff(time, "minutes");
function isValid(time) {
if (timeDiff > 1) {
console.log(timeDiff);
return false;
}
return true;
if (
Math.floor(new Date(time).getTime() / 1000) <
Math.floor(new Date().getTime() / 1000)
) {
return false;
}
return true;
}
//5 minutes
function resetIsValid(time) {
if (
Math.floor(new Date(time).getTime() / 1000) <
Math.floor(new Date().getTime() / 1000)
) {
return false;
}
return true;
}
module.exports = { isValid };
module.exports = { isValid , resetIsValid };

View File

@ -1,4 +1,5 @@
const {locationModel} = require("../database/model/locationModel");
const {Op} = require("sequelize");
async function getLocation() {
const location = await locationModel.findAll();
@ -35,7 +36,32 @@ async function deleteLocation(id) {
},
});
}
/*
const { Op } = require("sequelize");
var options = {
where: {
[Op.or]: [
{ 'subject': { [Op.like]: '%' + query + '%' } },
{ '$Comment.body$': { [Op.like]: '%' + query + '%' } }
]
},
include: [{ model: Comment }]
};
*/
async function getLocationByName(name) {
const location = await locationModel.findAll({
where: {
[Op.or]: [
{name: {[Op.like]: "%" + name + "%"}},
{added_by: {[Op.like]: "%" + name + "%"}},
{description: {[Op.like]: "%" + name + "%"}},
],
},
});
return location;
}
async function getLocationById(id) {
const location = await locationModel.findAll({
@ -51,5 +77,6 @@ module.exports = {
addLocation,
updateLocation,
deleteLocation,
getLocationByName,
getLocationById,
};

View File

@ -0,0 +1,108 @@
const { transporter } = require("../modules/nodemailer");
const path = require("path");
require("dotenv").config({ path: path.resolve(__dirname, "../.env") });
/*
var message = {
from: "sender@server.com",
to: "receiver@sender.com",
subject: "Message title",
text: "Plaintext version of the message",
html: "<p>HTML version of the message</p>",
};
//send mail with defined transport object
transporter.sendMail(data[, callback])
*/
async function sendContactEmail(email, name, message) {
console.log(email, name, message);
try {
let contactMessage = await transporter.sendMail({
to: process.env.euser,
subject: "Contact us Message",
html: `
<h1>Contact us Message</h1>
<p><strong>From:</strong> ${name}</p>
<p><strong>User Email:</strong> ${email}</p>
<p><strong>Message:</strong> ${message}</p>
<p>Thank you for contacting us. We will get back to you as soon as possible.</p>
<p>Regards,</p>
<p>EcoSaver Team</p>
<p><a href="https://ecosaver.teeseng.uk/">EcoSaver Website</a></p>
<p>Please do not reply to this email.</p>
`,
});
transporter.sendMail({ contactMessage }, function (error, info) {
if (error) {
console.log(error);
} else {
console.log("Email sent: " + info.response);
}
});
} catch (error) {
console.error(error);
}
}
async function sendTokenEmail(email, token) {
try {
let tokenMessage = await transporter.sendMail({
to: email,
from: process.env.euser,
subject: "API Token",
html: `
<h1>API Token</h1>
<p><strong>Token:</strong> ${token}</p>
<p>Please do not lose this token and do not share your token with anyone!</p>
<p>Thank you for using EcoSaver.</p>
<p>Regards,</p>
<p>EcoSaver Team</p>
<p><a href="https://ecosaver.teeseng.uk/">EcoSaver Website</a></p>
<p>Please do not reply to this email.</p>
`,
});
transporter.sendMail({ tokenMessage }, function (error, info) {
if (error) {
console.log(error);
} else {
console.log("Email sent: " + info.response);
}
});
} catch (error) {
console.error(error);
}
}
async function sendResetPasswordEmail(email, resetToken) {
try {
let resetMessage = await transporter.sendMail({
to: email,
from: process.env.euser,
subject: "Reset Password",
html: `
<h1>Reset Password</h1>
<p><strong>Reset Password Link:</strong> <a href="localhost/resetpassword/${resetToken}">Reset Password Link </p>
<p><strong>From:</strong> Eco Saver</p>
<p>Kindly click on the link to reset your password!</p>
<p>Regards,</p>
<p>EcoSaver Team</p>
<p><a href="https://ecosaver.teeseng.uk/">EcoSaver Website</a></p>
<p>Please do not reply to this email.</p>
`,
});
transporter.sendMail({ resetMessage }, function (error, info) {
if (error) {
console.log(error);
} else {
console.log("Email sent: " + info.response);
}
});
} catch (error) {
console.error(error);
}
}
module.exports = { sendContactEmail , sendTokenEmail, sendResetPasswordEmail };

View File

@ -1,4 +1,5 @@
const { sensorModel } = require("../database/model/sensorModel");
const {Op} = require("sequelize");
async function getSensor() {
const sensor = await sensorModel.findAll();
@ -55,6 +56,33 @@ async function deleteSensor(id) {
},
});
}
/*
async function getLocationByName(name) {
const location = await locationModel.findAll({
where: {
[Op.or]: [
{name: {[Op.like]: "%" + name + "%"}},
{added_by: {[Op.like]: "%" + name + "%"}},
{description: {[Op.like]: "%" + name + "%"}},
],
},
});
return location;
}
*/
async function getSensorByName(name) {
const sensor = await sensorModel.findAll({
where: {
[Op.or]: [
{name: {[Op.like]: "%" + name + "%"}},
{added_by: {[Op.like]: "%" + name + "%"}},
{mac_address: {[Op.like]: "%" + name + "%"}},
{description: {[Op.like]: "%" + name + "%"}},
],
},
});
return sensor;
}
async function getSensorById(id) {
const sensor = await sensorModel.findAll({
@ -70,5 +98,6 @@ module.exports = {
addSensor,
updateSensor,
deleteSensor,
getSensorByName,
getSensorById,
};

View File

@ -18,15 +18,17 @@ async function getSensorData() {
const sensorData = await sensorDataModel.findAll();
return sensorData;
}
async function addSensorData(id_sensor, id_location, sensordata) {
async function addSensorData(sensorid , locationid , measurement) {
const sensorData = await sensorDataModel.create({
sensorid: id_sensor,
locationid: id_location,
measurement: sensordata,
sensorid: sensorid,
locationid: locationid,
measurement: measurement
});
io().emit('sensorData:new', sensorData)
//console.log("sensorData", sensorData);
//console.log("sensorData", sensordata.measurement);
//console.log("sensorData", sensorData.measurement);
io().emit('sensorData:new', sensorData.measurement);
return sensorData;
}
@ -61,6 +63,14 @@ async function getSensorDataById(id) {
});
return sensorData;
}
async function getLatestData() {
const sensorData = await sensorDataModel.findAll({
limit: 6,
order: [["createdAt", "DESC"]],
});
return sensorData;
}
var ormQuery = {};
var whereClause = {};
var whereDate = {};
@ -664,6 +674,7 @@ async function getDatabyRange(queryString) {
queryString.limit = queryString.pagesize;
whereDate = {};
ormQuery = {};
for (let query in queryString) {
if (buildFunc[query]) {
await buildFunc[query](queryString);
@ -690,6 +701,8 @@ async function getDatabyRange(queryString) {
}
} else {
whereDate = {};
ormQuery = {};
for (let query in queryString) {
if (buildFunc[query]) {
await buildFunc[query](queryString);
@ -724,5 +737,6 @@ module.exports = {
getSensorDataById,
getData,
getDatabyRange,
getLatestData,
};

View File

@ -2,7 +2,6 @@ const { Op } = require('sequelize')
const { hash, compareHash } = require("./bcrypt.js");
const { addToken } = require("./api");
const { userModel } = require("../database/model/userModel");
moment = require('moment')
@ -21,6 +20,16 @@ async function getUserByID(userid) {
return userRes;
}
async function getUserByEmail(email) {
let userRes = await userModel.findOne({
where: {
email: email,
},
});
if (!userRes) return false;
return userRes;
}
//api/v0/auth/register
/* Registering new user
1) req.body is taken from html form or wtv
@ -71,9 +80,11 @@ async function loginUser(user) {
if (!match) return false;
//console.log('loginUser', userRes.id, userRes.username);
//generate token and permission and experiation time
const currentTime = moment().format('YYYY-MM-DD HH:mm:ss');
let token = await addToken(userRes.id , "canRead" , currentTime);
//generate token and permission and experiation time + 30 mins
//let tokenToLive = moment().add(30, 'minutes').format();
let currentDate = new Date();
let tokenToLive = new Date(currentDate.getTime() + 30 * 60000);
let token = await addToken(userRes.id , "canRead" , "isNotKey" , tokenToLive);
return { token: token, userid: userRes.id, username: userRes.username };
}
@ -130,9 +141,54 @@ async function updateProfile(user, body) {
}
}
async function checkEmail(email) {
let emailRes = await userModel.findOne({
where: {
email: email,
},
});
if (!emailRes) return false;
return true;
}
async function checkEmailDetails(email) {
let emailRes = await userModel.findOne({
where: {
email: email,
},
});
if (!emailRes) return false;
return emailRes;
}
async function resetPass(userid , data ){
let hashed = await hash(data.password);
let updateUser = await userModel.update(
{
password: hashed,
},
{
where: {
id: userid,
},
}
);
if (!updateUser) return false;
return true;
}
module.exports = {
getUserByID,
getUserByEmail,
addUser,
loginUser,
updateProfile,
checkEmail,
checkEmailDetails,
resetPass,
};

View File

@ -1,5 +1,9 @@
var validator = require("validator");
/*
All the validation functions are used by database model.
*/
const dateRegex = /^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
function isValidDateString(value) {

View File

@ -1,68 +1,48 @@
const { tokenModel } = require("../database/model/tokenModel");
const { userModel } = require("../database/model/userModel");
const { compareHash } = require("../functions/bcrypt");
const { checkToken } = require("../functions/api");
const { isValid } = require("../functions/isValid");
const { getTokenByToken } = require("../functions/api");
const permissionError = new Error('PermissionError')
permissionError.name = "Inadequate Permission Error"
permissionError.status = 401
permissionError.message = "Inadequate permission to complete this response"
async function auth(req, res, next) {
try {
const authToken = req.header("auth-token");
if (!authToken) {
const error = new Error("No Token key was supplied. Invalid request");
throw error;
const token = await getTokenByToken(req.header("auth-token"));
if (!token || !token.isValid){
throw permissionError;
}
const splitAuthToken = authToken.split("-");
const rowid = splitAuthToken[0];
const suppliedToken = splitAuthToken.slice(1).join("-");
const token = await tokenModel.findByPk(rowid, { include: userModel });
if (!token) {
const error = new Error("Token key not found. Invalid request");
throw error;
}
const isMatch = await compareHash(suppliedToken, token.token);
console.log(isMatch);
if (!isMatch) {
const error = new Error("Token key not found. Invalid request");
throw error;
}
//if token is a match
req.token = token;
req.user = await token.getUser();
const permission = await checkToken(suppliedToken, rowid);
const route = req.originalUrl.split("?")[0]; // Removing query parameters
//if route is from user/ and permission is canRead allow it to do CRUD
if (route.includes("/user/") && permission === "canRead") {
next();
if (route.includes("/user/") || route.includes("/token/") && token.permission === "canRead") {
console.log("user route");
return next();
}
if ((req.method === "GET" && permission === "canRead") || (["GET", "POST", "PUT", "DELETE"].includes(req.method) && permission === "canWrite")) {
next();
}
if (!isValid(token.expiration)){
req.token.destroy();
throw new Error("Token expired");
if ((req.method === "GET" && token.permission === "canRead")){
console.log("wtf you shldnt be here");
return next();
}
if (["GET", "POST", "PUT", "DELETE"].includes(req.method) && token.permission === "canWrite") {
console.log("wtf you shldnt be here");
return next();
}
/*
if ((req.method === "GET" && token.permission === "canRead") ||
(["GET", "POST", "PUT", "DELETE"].includes(req.method) && token.permission === "canWrite")) {
return next();
}
*/
throw permissionError
} catch (error) {
next(error);
}
}
module.exports = { auth };
/*
else {
const error = new Error("Insufficient permission");
error.status = 401;
throw error;
}
*/

View File

@ -2,6 +2,7 @@ const nodemailer = require("nodemailer");
const dotenv = require("dotenv");
const path = require('path')
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
//.env
let transporter = nodemailer.createTransport({
service: 'gmail',

View File

@ -3812,6 +3812,7 @@
.card-text {
color: #000000;
font-size: 16px;
}
/* edit profile */

View File

@ -528,22 +528,22 @@ body.one-content-column-version .content thead {
.generate-key-button {
float: right; /* Align the button to the right */
margin-right: 85%;
margin-top: -40px; /* Adjust the margin-top value based on your layout */
/* Add any additional styling you want for the button */
}
#content-get-api .generate-key-button {
margin-top: -40px;
margin-left: 25px;
background-color: #4caf50; /* Green background color */
color: white; /* White text color */
color: #ffffff;
padding: 5px 11px; /* Padding for the button */
border: none; /* Remove button border */
border-radius: 5px; /* Add border-radius for rounded corners */
cursor: pointer; /* Add pointer cursor on hover */
font-size: 14px; /* Font size */
}
.api-form {
margin-top: 20px;
margin-left: 25px;
}
#content-get-api .generate-key-button:hover {
.generate-key-button:hover {
background-color: #45a049; /* Darker green on hover */
}
}

View File

@ -0,0 +1,61 @@
.air-quality-container {
display: flex;
flex-direction: column;
width: 800px;
/* Adjust width as needed */
margin: 0 auto;
position: relative;
}
.chart-container {
background-color: #f5f5f5;
padding: 20px;
position: relative;
}
.button-container {
display: flex;
justify-content: space-around; /* Change this value to adjust the spacing */
margin: 20px 0;
}
#download-container {
display: flex;
align-items: center;
margin-top: 20px; /* Adjust the margin-top for spacing */
}
button {
border: 1px solid #ddd;
border-radius: 5px;
padding: 5px 10px;
font-size: 14px;
cursor: pointer;
}
.data-table {
border-collapse: collapse;
width: 100%;
}
.graphbutton-container {
display: flex;
justify-content: center; /* Center the buttons horizontally */
margin: 20px 0;
}
button#barButton,
button#lineButton {
border: 1px solid #ddd;
border-radius: 5px;
padding: 5px 10px;
font-size: 14px;
cursor: pointer;
margin: 0 10px;
}

View File

@ -100,3 +100,16 @@ body {
background-color: #213f6d;
color: #fff;
}
.custom-btn {
display: inline-block;
padding: 10px 20px; /* Adjust padding as needed */
background-color: #3498db; /* Change background color */
color: #ffffff; /* Change text color */
text-decoration: none;
border-radius: 5px; /* Add rounded corners if desired */
}
.custom-btn:hover {
background-color: #2980b9; /* Change background color on hover */
}

View File

@ -15,12 +15,11 @@ body {
}
.wrapper {
position: relative;
max-width: 470px;
max-width: 600px; /* Increase the maximum width */
width: 100%;
border-radius: 12px;
padding: 20px
30px
120px;
padding: 40px 50px 150px; /* Adjust the padding */
background: #4070f4;
box-shadow: 0
5px

View File

@ -147,13 +147,13 @@ button.btn-secondary:hover{
border: none;
}
.services-bar .card h4.card-header{
background-color: #4e3914;
background-color: #ffffff;
color: #4eae3a;
font-size: 18px;
font-weight: 400;
}
.services-bar .card .card-footer{
background-color: #4e3914;
background-color: #ffffff;
}
.about-main{
padding: 30px 0px;

View File

@ -186,9 +186,7 @@ app.auth = (function (app) {
function isLoggedIn(callback) {
if (getToken()) {
console.log("you shldnt appear at all");
return app.api.get("user/me", function (error, data) {
console.log(error, data);
if (!error) app.auth.user = data;
return callback(error, data);
});
@ -245,6 +243,10 @@ app.auth = (function (app) {
location.replace(`/profile`);
}
function checkEmailRedirect(){
location.replace(`/checkemail`);
}
return {
getToken: getToken,
setToken: setToken,
@ -254,6 +256,7 @@ app.auth = (function (app) {
logInRedirect,
homeRedirect,
profileRedirect,
checkEmailRedirect,
};
})(app);
@ -281,13 +284,15 @@ function formAJAX(btn, del) {
var $form = $(btn).closest("[action]"); // gets the 'form' parent
var formData = $form.find("[name]").serializeObject(); // builds query formDataing
var method = $form.attr("method") || "post";
console.log("Form data", formData);
console.log("Form method", method);
app.util.actionMessage("Loading...", $form, "info");
//console.log('Data being sent to', $form.attr('action'), formData)
app.api[method]($form.attr("action"), formData, function (error, data) {
//console.log('Data back from the server', error, data)
console.log('Data back from the server', error, data)
app.util.actionMessage(data.message, $form, error ? "danger" : "success"); //re-populate table
if (!error) {
$form.trigger("reset");

View File

@ -0,0 +1,121 @@
//getting button from DOM id
const buttons = document.querySelectorAll(".button-container button");
const weeklybuttons = document.querySelectorAll(".weeklybutton-container button");
const queryButton = document.getElementById("querybutton-container");
$(document).ready(async function () {
//https://stackoverflow.com/questions/9045868/javascript-date-getweek
Date.prototype.getWeek = function () {
var onejan = new Date(this.getFullYear(), 0, 1);
var today = new Date(this.getFullYear(), this.getMonth(), this.getDate());
var dayOfYear = (today - onejan + 86400000) / 86400000;
return Math.ceil(dayOfYear / 7);
};
let date = new Date();
var week = date.getWeek() - 1; // Subtracting 1 to get the actual week number
var today = date.getDate();
var month = date.getMonth() + 1; // Adding 1 to get the actual month number
var year = date.getFullYear();
// Initialize initialData for chart
const initialData = {
labels: [], // Array to store timestamps
datasets: [
{
label: "Average Measurement Data",
data: [], // Array to store measurements objects
backgroundColor: "green",
borderColor: "green",
},
],
};
// Create Chart.js chart
const ctx = document.getElementById("DailyDataChart").getContext("2d");
const chart = new Chart(ctx, {
type: "bar",
data: initialData,
options: {
responsive: true,
title: {
display: true,
text: "Average measurement metric data by Hour",
},
},
});
// Function to update chart data based on button clicked
function updateChart(metric) {
const queryParams = `sensor-data/data?month=${month}&week=${week}&day=${today}`;
app.api.get(queryParams, function (error, data) {
// Clear previous data
initialData.labels = []; //timestamp
initialData.datasets[0].data = []; //measurement data dependinbg on metric
// Group data by hour and calculate average value
const hourlyData = {};
for (let row of data) {
//each row contains a timestamp and measurement data of each sensor and location
const createdAt = new Date(row.createdAt); //set to local time
const hourString = new Date(
createdAt.getFullYear(),
createdAt.getMonth(),
createdAt.getDate(),
createdAt.getHours()
).toISOString();
if (!hourlyData[hourString]) {
hourlyData[hourString] = [];
}
hourlyData[hourString].push(row.measurement[metric]); //pushing measurement data into hourlyData
}
// Calculate average value for each hour
//console.log(hourlyData); //24 values for each hour of the day
for (let hourString in hourlyData) {
const averageValue =
hourlyData[hourString].reduce((acc, val) => acc + val, 0) /
hourlyData[hourString].length;
initialData.labels.push(
new Date(hourString).toLocaleString("en-US", {
timeZone: "UTC",
hour12: false,
})
);
initialData.datasets[0].data.push(averageValue);
}
// Update chart
chart.update();
});
}
// Event listeners for buttons
document.getElementById("psiButton").addEventListener("click", function () {
updateChart("psi");
});
document.getElementById("tempButton").addEventListener("click", function () {
updateChart("temperature");
});
document.getElementById("humButton").addEventListener("click", function () {
updateChart("humidity");
});
document.getElementById("o3Button").addEventListener("click", function () {
updateChart("o3");
});
document.getElementById("no2Button").addEventListener("click", function () {
updateChart("no2");
});
document.getElementById("so2Button").addEventListener("click", function () {
updateChart("so2");
});
document.getElementById("coButton").addEventListener("click", function () {
updateChart("co");
});
});

View File

@ -1,21 +1,18 @@
document.addEventListener("DOMContentLoaded", function () {
function updateAdditionalInfo(region) {
const infoContainer = document.getElementById("additional-info");
// Replace the following with actual data retrieval based on the region
const aqi = "15";
const temperature = "25°C";
const humidity = "60%";
const pm25 = "10";
const pm10 = "20";
const so2 = "5";
const o3 = "35";
const co = "0.5";
const no2 = "15";
// Replace the following with actual data retrieval based on the region
const aqi = "15";
const temperature = "25°C";
const humidity = "60%";
const so2 = "5";
const o3 = "35";
const co = "0.5";
const no2 = "15";
infoContainer.innerHTML = `
infoContainer.innerHTML = `
<div class="additional-info-box">
<h3>Additional Information - ${region}</h3>
<button id="downloadCsvButton">Download CSV</button>
<div class="info-item">
<span class="info-label">Air Quality Index:</span>
<span class="info-value">${aqi}</span>
@ -28,14 +25,6 @@ infoContainer.innerHTML = `
<span class="info-label">Humidity:</span>
<span class="info-value">${humidity}</span>
</div>
<div class="info-item">
<span class="info-label">PM2.5:</span>
<span class="info-value">${pm25}</span>
</div>
<div class="info-item">
<span class="info-label">PM10:</span>
<span class="info-value">${pm10}</span>
</div>
<div class="info-item">
<span class="info-label">SO2:</span>
<span class="info-value">${so2}</span>
@ -54,23 +43,16 @@ infoContainer.innerHTML = `
</div>
</div>
`;
// Remove the 'active' class from all info-box elements
const infoBoxes = document.querySelectorAll('.info-box');
infoBoxes.forEach(box => box.classList.remove('active'));
// Add the 'active' class to the clicked info-box
const clickedBox = document.getElementById(region.toLowerCase());
clickedBox.classList.add('active');
}
const defaultRegion = "North";
const defaultBox = document.getElementById(defaultRegion.toLowerCase());
defaultBox.classList.add('active');
const defaultAqi = "--"; // Replace with actual data retrieval
updateAdditionalInfo(defaultRegion, defaultAqi);
// Event listeners for each region's info-box
document.getElementById("north").addEventListener("click", function () {
const northAqi = "--"; // Replace with actual data retrieval
@ -117,3 +99,4 @@ document.getElementById("central").addEventListener("click", function () {
updateAdditionalInfo("Central");
});

View File

@ -18,5 +18,11 @@ router.use('/sensor', [auth, APIlogger], require('./sensor.js'));
//sensor data route
router.use('/sensor-data', [auth, APIlogger], require('./sensorData.js'));
//apilog route
router.use('/apilog', [auth, APIlogger], require('./apilog.js'));
//latest sensor data to display on dashboard
router.use('/latest-sensor-data', [APIlogger], require('./latestsensorData.js'));
module.exports = router;

View File

@ -0,0 +1,37 @@
//functions if needed
const {
getAllLog
} = require("../functions/apilog.js");
const express = require("express");
const router = express.Router();
//get all
router.get("/", async (req, res, next) => {
let Res = await getAllLog();
res.json(Res);
});
/*
//get by route name?
router.get("/route/:name", async (req, res, next) => {
});
//get ny status?
router.get("/status/:status", async (req, res, next) => {
});
//by method
router.get("/method/:method", async (req, res, next) => {
});
//by ip
router.get("/ip/:ip", async (req, res, next) => {
});
*/
module.exports = router;

View File

@ -1,6 +1,19 @@
const { addUser, loginUser } = require("../functions/user");
const {
addUser,
loginUser,
checkEmail,
checkEmailDetails,
resetPass,
} = require("../functions/user");
const { sendContactEmail } = require("../functions/nodeMail");
const { generateUUID } = require("../functions/generateUUID");
const { addPasswordResetToken } = require("../functions/api");
const { sendResetPasswordEmail } = require("../functions/nodeMail");
const { checkTokenByrowID } = require("../functions/api");
const express = require("express");
const { render } = require("ejs");
const router = express.Router();
// /user/register
@ -12,12 +25,11 @@ router.post("/register", async (req, res, next) => {
error.message = "The user failed to be craated";
error.status = 400;
return next(error);
} else {
return res.json({
message: "User created successfully",
});
}
else{
return res.json({
message: "User created successfully",
});
}
} catch (error) {
console.error(error);
next(error);
@ -29,20 +41,18 @@ router.post("/login", async (req, res, next) => {
try {
let Res = await loginUser(req.body);
if (Res == false) {
let error = new Error("User Login Failed");
let error = new Error("User Login Failed");
error.status = 400;
return next(error);
} else {
//pass res back to form to be set in local storage
return res.json({
message: "User login successfully",
token: Res.token,
userid: Res.userid,
username: Res.username,
});
}
else{
//pass res back to form to be set in local storage
return res.json({
message: "User login successfully",
token: Res.token,
userid: Res.userid,
username: Res.username,
});
}
} catch (error) {
console.error(error);
next(error);
@ -52,6 +62,87 @@ router.post("/login", async (req, res, next) => {
//contact
//auth/contact
router.post("/contact", async (req, res, next) => {
try {
//console.log(req.body);
let Res = await checkEmail(req.body.email);
if (!Res) {
let error = new Error("Email not found");
error.status = 400;
return next(error);
} else {
//console.log(Res);
sendContactEmail(req.body.email, req.body.name, req.body.message);
return res.json({
message: "Email sent successfully",
});
}
} catch (error) {
console.error(error);
next(error);
}
});
//reset
router.post("/checkemail", async (req, res, next) => {
try {
let Res = await checkEmail(req.body.email);
if (!Res) {
let error = new Error("Email not found");
error.status = 400;
return next(error);
} else {
//user info lookup
let data = await checkEmailDetails(req.body.email);
//console.log(data);
//token generation and insert into token table
let token = await generateUUID();
let tokenRes = await addPasswordResetToken(data, token);
//email user with temp token link
if (!tokenRes) return false;
//apend table id to token
token = tokenRes + "-" + token;
//email logic to send reset password link
sendResetPasswordEmail(req.body.email, token);
return res.json({
message: "Reset Password Link has successfully sent to your email!",
});
}
} catch (error) {
console.error(error);
next(error);
}
});
//reset password
router.post("/resetpassword/:token", async (req, res, next) => {
console.log(req.body);
console.log(req.params.token);
//if token is valid
let tokenRes = await checkTokenByrowID(req.params.token);
if (!tokenRes) {
let error = new Error("Token not found");
error.status = 400;
return next(error);
}
//token is valid and reset password
else{
let Res = await resetPass(tokenRes.userid, req.body);
if (!Res) return false;
else{
res.json({
message: "Password reset successfully",
});
tokenRes.destroy();
}
}
});

View File

@ -0,0 +1,21 @@
const {
getLatestData,
} = require("../functions/sensorData");
const express = require("express");
const router = express.Router();
router.get("/data", async (req, res, next) => {
try {
console.log(req.query);
const data = await getLatestData();
res.status(200).json(data);
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;

View File

@ -2,6 +2,7 @@ const {
addLocation,
getLocation,
getLocationById,
getLocationByName,
updateLocation,
deleteLocation,
} = require("../functions/location");
@ -58,6 +59,19 @@ router.delete("/delete", async (req, res, next) => {
}
});
//get location by name
router.get("/name/:name", async (req, res, next) => {
try {
//get params
const { name } = req.params;
const location = await getLocationByName(name);
res.status(200).json(location);
} catch (error) {
console.error(error);
next(error);
}
});
//get location by id

View File

@ -1,4 +1,5 @@
"use strict";
const { checkTokenByrowID } = require("../functions/api");
var router = require("express").Router();
@ -33,16 +34,26 @@ router.get("/forgotpassword", function (req, res, next) {
res.render("forgotpassword");
});
//resetted password page
//resetting password page
router.get("/resetpassword", function (req, res, next) {
res.render("resetpassword");
});
//check email page
router.get("/checkemail", function (req, res, next) {
res.render("checkemail");
});
//contact page
router.get("/contact", function (req, res, next) {
res.render("contact");
});
//data page
router.get("/viewdata", function (req, res, next) {
res.render("viewdata");
});
//api doc
router.get("/api", function (req, res, next) {
res.render("api");
@ -53,4 +64,31 @@ router.get("/sensor-data", function (req, res, next) {
res.render("sensor-data");
});
//reset password page
router.get("/resetpassword/:token", async (req, res, next) => {
try{
//pass token to reset password page
//console.log(req.params.token);
//check if token is valid
let tokenRes = await checkTokenByrowID(req.params.token);
if (!tokenRes) {
let error = new Error("Token not found");
error.status = 400;
return next(error);
}
else {
let token = req.params.token;
console.log(token);
res.render("resetpassword", { token: token });
}
}catch(error){
console.error(error);
next(error);
}
});
module.exports = router;

View File

@ -3,6 +3,7 @@ const {
addSensor,
updateSensor,
deleteSensor,
getSensorByName,
getSensorById
} = require("../functions/sensor.js");
@ -52,6 +53,16 @@ router.delete("/delete", async (req, res, next) => {
next(error);
}
});
router.get("/name/:name", async (req, res, next) => {
try {
const sensor = await getSensorByName(req.params.name);
res.status(200).json(sensor);
} catch (error) {
console.error(error);
next(error);
}
});
router.get("/:id", async (req, res, next) => {
try {
const sensor = await getSensorById(req.params.id);

View File

@ -23,8 +23,9 @@ router.get("/", async (req, res, next) => {
router.post("/new", async (req, res, next) => {
try {
const { id_sensor, id_location, sensordata } = req.body;
let data = await addSensorData(id_sensor, id_location, sensordata);
//locationid
const { sensorid , locationid , measurement } = req.body;
let data = await addSensorData(sensorid , locationid , measurement);
res.json({ message: "SensorData " + data.id + " added", ...data });
} catch (error) {
console.error(error);
@ -34,8 +35,8 @@ router.post("/new", async (req, res, next) => {
router.put("/update", async (req, res, next) => {
try {
const { id, id_sensor, id_location, sensordata } = req.body;
await updateSensorData(id, id_sensor, id_location, sensordata);
const { id , sensorid , locationid , measurement } = req.body;
await updateSensorData(id, sensorid , locationid , measurement);
res.status(200).json({ message: "SensorData " + id + " updated" });
} catch (error) {
console.error(error);
@ -55,7 +56,6 @@ router.delete("/delete", async (req, res, next) => {
});
router.get("/data", async (req, res, next) => {
try {
console.log(req.query);
const data = await getData(req.query);
res.status(200).json(data);

View File

@ -1,5 +1,6 @@
const { addToken } = require("../functions/api");
const { addToken, checkToken } = require("../functions/api");
const { checkEmail, getUserByEmail } = require("../functions/user");
const { sendTokenEmail } = require("../functions/nodeMail");
const express = require("express");
const router = express.Router();
@ -11,18 +12,43 @@ const router = express.Router();
4) store the api key in database
*/
//token/new
//curl localhost:3000/api/v0/token/new -H "Content-Type: application/json" -X POST -d
//curl localhost:3000/api/v0/token/new -H "Content-Type: application/json" -X POST -d
//'{"userid": "5", "permission": "canRead" ,}'
router.post("/new", async (req, res, next) => {
try {
const token = await addToken(req.body.userid, req.body.permission , "2204-01-24 07:34:36" );
res.json({token: token});
//console.log(req.body);
const Res = await checkEmail(req.body.email);
if (!Res) {
let error = new Error("Email not found");
error.status = 400;
return next(error);
} else {
let userid = await getUserByEmail(req.body.email);
if (!userid) return false;
const tokenRes = await checkToken(userid.id);
if (tokenRes.isKey !== "null" && tokenRes.isKey !== "isKey") {
//allow user to create token
const token = await addToken(
userid.id,
"canRead",
"isKey",
"2204-01-24 07:34:36"
);
if (!token) return false;
sendTokenEmail(req.body.email, token);
res.json({
message: "Token generated successfully and sent to email",
});
}
}
//const token = await addToken(req.body.userid, "canRead" , "2204-01-24 07:34:36" );
//res.json({token: token});
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;

View File

@ -7,8 +7,8 @@ const router = express.Router();
//getbyid
router.get("/me", async function (req, res, next) {
try {
let user = await getUserByID(req.user);
console.log(user);
let user = await getUserByID(req.user); //req.user assigned in middleware!
//console.log(user);
res.json({
user: user,
});

File diff suppressed because it is too large Load Diff

View File

@ -77,11 +77,12 @@
</div>
<div class="container text-center">
<br>
<p>All Rights Reserved. &copy; 2023 <a href="/">EcoSaver</a>
<p>All Rights Reserved. &copy; 2024 <a href="/">EcoSaver</a>
</p>
</div>
</footer>
<script src="js/search.js"></script>
<script src="/js/search.js"></script>
</body>
</html>

View File

@ -0,0 +1,19 @@
<%- include('logintop') %>
<body>
<section class="wrapper">
<div class="form">
<!-- -->
<div class="error-contents">
<h3>Please check your email for the reset password link</h3>
</div>
<br>
<br>
<br>
<a>Dont have an account?</a> <a href="/login">Sign Up</a>
<br>
<a>Already have an account?</a> <a href="/login">Login</a>
</div>
</section>
</body>

View File

@ -42,7 +42,8 @@
</p>
<p>
<abbr title="Email">E</abbr>:
<a href="mailto:name@example.com">leongdingxuan@gmail.com
<a href="mailto:name@example.com">ecosaverx@gmail.com
</a>
</p>
<p>
@ -55,17 +56,18 @@
<!-- Contact Form -->
<!-- In order to set the email address and subject line for the contact form go to the bin/contact_me.php file. -->
<div class="row">
<div class="form contact iot-card">
<div class="col-lg-8 mb-4 contact-left">
<h3>Send us a Message</h3>
<form id="form">
<input type="hidden" name="access_key" value="">
<form action="auth/contact" onsubmit="formAJAX(this)">
<div class="card-header shadow actionMessage" style="display:none"></div>
<div class="mb-3">
<label for="name">Full Name</label>
<input type="text" name="name" id="name" required>
<input type="text" name="name" id="name" required pattern="^[a-zA-Z]{3,}( {1,2}[a-zA-Z]{3,}){0,}$">
</div>
<div class="mb-3">
<label for="email">Email address</label>
<input type="email" name="email" id="email" required>
<input type="email" name="email" id="email" required pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">
</div>
<div class="mb-3">
<label for="message">Message</label>
@ -75,6 +77,8 @@
</form>
</div>
</div>
</div>
</div>
<!-- /.row -->

View File

@ -4,10 +4,9 @@
<section class="wrapper">
<div class="form">
<header>Reset Password</header>
<form action="/resetpassword">
<input type="text" id="email" placeholder="Email" required />
<input type="password" id="password" placeholder="Password" required />
<input type="password" id="confirmPassword" placeholder="Confirm Password" required />
<form action="auth/checkemail" onsubmit="formAJAX(this) "evalAJAX="app.auth.checkEmailRedirect();">
<input type="email" name="email" placeholder="Email" required
pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" />
<input type="submit" value="Reset Password" />
</form>
<br>

View File

@ -1,11 +1,60 @@
<%- include('top') %>
<script>
//call socket.io
app.socket.on("sensorData:new", function (data) {
console.log("new data!!")
console.log(data);
<script type="text/javascript">
function extractNumbers(str) {
if (typeof str === 'number') return str;
return str.match(/\d+/)[0];
}
function calculateAverage(numbers) {
if (numbers.length === 0) return 0
const sum = numbers.reduce((acc, num) => acc + num, 0);
return sum / numbers.length;
}
const values = {
psi: [],
humidity: [],
temperature: [],
windspeed: [],
};
function parseRowToTemplace(row) {
values.psi.unshift(extractNumbers(row.measurement.psi))
values.humidity.unshift(extractNumbers(row.measurement.humidity))
values.temperature.unshift(extractNumbers(row.measurement.temperature))
values.windspeed.unshift(extractNumbers(row.measurement.windspeed))
return {
average: {
psi: parseInt(calculateAverage(values.psi)),
humidity: parseInt(calculateAverage(values.humidity)),
temperature: parseInt(calculateAverage(values.temperature)),
windspeed: parseInt(calculateAverage(values.windspeed)),
},
latest: {
psi: values.psi[0],
humidity: values.humidity[0],
temperature: values.temperature[0],
windspeed: values.windspeed[0],
}
}
}
$(document).ready(async function () {
app.api.get('latest-sensor-data/data', function (error, data) {
for (let row of data) {
//console.log(row);
$.scope.LatestSensorData.update(parseRowToTemplace(row));
}
});
//call socket.io to get live data
app.socket.on("sensorData:new", function (data) {
$.scope.LatestSensorData.update(parseRowToTemplace(data));
});
});
</script>
@ -53,56 +102,60 @@
</header>
<!-- Page Content -->
<div class="container">
<div class="services-bar">
<h1 class="my-4">Services</h1>
<!-- Services Section -->
<div class="row">
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Air Quality Index</h4>
<div class="card-body text-center">
<p class="card-text display-4">15 - 18 PSI</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
<div class="services-bar" jq-repeat="LatestSensorData">
<h1 class="my-4">Services</h1>
<!-- Services Section -->
<div class="row">
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Air Quality Index</h4>
<div class="card-body text-center">
<p class="card-text display-4"> Average: {{average.psi}} PSI</p>
<p class="card-text display-4"> Latest: {{latest.psi}} PSI</p>
</div>
</div>
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Humidity</h4>
<div class="card-body text-center">
<p class="card-text display-4">70% - 75%</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Temperature</h4>
<div class="card-body text-center">
<p class="card-text display-4">30&deg; - 37&deg;</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
</div>
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Humidity</h4>
<div class="card-body text-center">
<p class="card-text display-4"> Average: {{average.humidity}} %</p>
<p class="card-text display-4"> Latest: {{latest.humidity}} %</p>
</div>
</div>
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Another Category</h4>
<div class="card-body text-center">
<p class="card-text display-4">values</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Temperature</h4>
<div class="card-body text-center">
<p class="card-text display-4"> Average: {{average.temperature}}&deg;</p>
<p class="card-text display-4"> Latest: {{latest.temperature}}&deg;</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
</div>
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Wind Speed</h4>
<div class="card-body text-center">
<p class="card-text display-4"> Average: {{average.windspeed}} Km/h</p>
<p class="card-text display-4"> Latest: {{latest.windspeed}} Km/h</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
</div>
</div>
<!-- /.row -->
</div>
<!-- About Section -->

View File

@ -38,7 +38,11 @@
<br>
<br>
<script src="js/learnmore.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<%- include('bot') %>

View File

@ -6,14 +6,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="shortcut icon" type="images/logo.ico" href="images/logo.ico" />
<link rel="shortcut icon" type="images/logo.ico" href="/images/logo.ico" />
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<!-- Custom styles for this template -->
<link rel="stylesheet" href="css/sp.css" />
<link rel="stylesheet" href="/css/sp.css" />
<!-- jQuery library -->
@ -27,10 +27,10 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.js"></script>
<!-- socket.io scriot -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.min.js"></script>
<!-- jquery app.js -->
<script src="js/app.js"></script>
<script src="/js/app.js"></script>
</head>
@ -44,25 +44,29 @@
$(document).ready(function () {
//check if user is logged in
app.auth.isLoggedIn(function (error, data) {
if (data) {
$('#cl-logout-button').show('fast');
$('#cl-profile-button').show('fast');
$('#cl-login-button').hide('fast');
if (!error) {
$("#cl-logout-button").show("fast");
$("#cl-viewdata-button").show("fast");
$("#cl-api-button").show("fast");
$("#cl-profile-button").show("fast");
$("#cl-login-button").hide("fast");
} else {
$('#cl-login-button').show('fast');
}
$('body').show('fast')
});
});
</script>
</script>
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-light top-nav fixed-top">
<div class="container">
<a class="navbar-brand" href="/">
<img src="images/logo.png" alt="logo" />
<img src="/images/logo.png" alt="logo" />
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
@ -79,11 +83,16 @@
<li class="nav-item">
<a class="nav-link" href="/contact">Contact</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/api">API Doc</a>
</li>
<!-- profile button -->
<div class="form-inline mt-2 mt-md-0">
<a id="cl-viewdata-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/viewdata"
style="display: none">
<i class="fas fa-sign-out"></i>Data
</a>
<a id="cl-api-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/api"
style="display: none">
<i class="fas fa-sign-out"></i> API
</a>
<a id="cl-profile-button" class="btn btn-outline-info my-2 my-sm-0" href="/profile"
style="display: none;">
<i class="fas fa-sign-out"></i>
@ -103,5 +112,4 @@
</ul>
</div>
</div>
</nav>
</nav>

View File

@ -1,4 +1,8 @@
<%- include('top') %>
<script type="text/javascript">
// Require login to see this page.
app.auth.forceLogin()
</script>
<br>
<br>
@ -6,9 +10,9 @@
<div class="profile">
<!-- <li jq-repeat="getUsername" class="nav-item"> -->
<div class="edit_information" jq-repeat="getUserDetails">
<div class="card-header shadow actionMessage" style="display:none"></div>
<form id="profileForm" action="user/update" method="put" onsubmit="formAJAX(this)"
evalAJAX="app.auth.profileRedirect();">
<div class="card-header shadow actionMessage" style="display:none"></div>
<h3 class="text-center">Edit Personal Information</h3>
<br>
<div class="row">

View File

@ -1,39 +1,46 @@
<!DOCTYPE html>
<html>
<%- include('logintop') %>
<head>
<title>Your password has been resetted</title>
<link href="css/reset.css" rel="stylesheet">
</head>
<section class="wrapper">
<div class="form">
<header>Reset Password</header>
<div class="card-header shadow actionMessage" style="display:none"></div>
<!-- <form action="auth/resetpassword/<%= token.token%>" method="post" onsubmit="formAJAX(this)"> -->
<form action="auth/resetpassword/<%= token%>" method="post" onsubmit="formAJAX(this)" evalAJAX="app.auth.logInRedirect();">
<input type="password" id="password" name="password" placeholder="Password" required />
<input type="password" id="confirmPassword" name="confirmPassword" placeholder="Confirm Password"
required />
<input type="submit" value="Reset Password" />
</form>
<br>
<a>Dont have an account?</a> <a href="/login">Sign Up</a>
</div>
</section>
<body>
<table class="main-header">
<tr>
<td style="text-align: center;">
<img src="images/passwordreset.png" alt="Image">
<h1>Your password has been resetted</h1>
</td>
</tr>
</table>
<script>
//both password fields must match
var password = document.getElementById("password");
var confirm_password = document.getElementById("confirmPassword");
<table class="content-section">
<tr>
<td>
<p>Hello,</p>
<p>Please check your email to reset your password.</p>
</td>
</tr>
</table>
function validatePassword() {
var passwordValue = password.value;
<table class="footer">
<tr>
<td>
<p>&copy; 2023 EcoSaver</p>
</td>
</tr>
</table>
// Strong password regex pattern
var strongPasswordPattern = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
if (passwordValue != confirm_password.value) {
confirm_password.setCustomValidity("Passwords Don't Match");
} else if (!strongPasswordPattern.test(passwordValue)) {
confirm_password.setCustomValidity("Password must be at least 8 characters long and include at least one letter, one number, and one special character.");
} else {
confirm_password.setCustomValidity('');
}
}
password.onchange = validatePassword;
confirm_password.onkeyup = validatePassword;
</script>
</body>
</html>

View File

@ -1,39 +0,0 @@
<%- include('top') %>
<style>
#sensorDataList {
padding-top: 2.5em;
}
</style>
<ul id="sensorDataList">
<li jq-repeat='sensorData'>
rowid: {{ id }}
sensorId: {{ sensorid }}
created: {{ createdAt }}
location: {{ locationid }}
<br />
co: {{ measurement.co }}
humidity: {{ measurement.humidity }}
no2: {{ measurement.no2 }}
o3: {{ measurement.o3 }}
psi: {{ measurement.psi }}
so2: {{ measurement.so2 }}
temperature: {{ measurement.temperature }}
windspeed: {{ measurement.windspeed }}
<hr />
</li>
<li jq-repeat-defualt='sensorData'>
Loading...
</li>
</ul>
<script type="text/javascript">
$(document).ready(async function () {
app.api.get('sensor-data/data?order=DESC&limit=40', function(error, data){
$.scope.sensorData.push(...data);
})
})
</script>
<%- include('bot') %>

View File

@ -4,7 +4,6 @@
//app.auth.redirectIfLoggedIn();
</script>
<body>
<section class="wrapper">
<div class="form signup iot-card">
<!--<div class="form signup card" -->
@ -67,7 +66,7 @@
confirm_password.onkeyup = validatePassword;
const wrapper = document.querySelector(".wrapper"),
signupHeader = document.querySelector(".signup header"),
loginHeader = document.querySelector(".login header");
@ -83,4 +82,4 @@
</body>
</html>
</html>

View File

@ -9,7 +9,7 @@
<meta http-equiv="cleartype" content="on" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" type="images/logo.ico" href="images/logo.ico" />
<link rel="shortcut icon" type="images/logo.ico" href="/images/logo.ico" />
<!-- Bootstrap core CSS -->
@ -17,8 +17,8 @@
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
<!-- Custom styles for this template -->
<link href="css/all.css" rel="stylesheet" />
<link href="css/style.css" rel="stylesheet" />
<link href="/css/all.css" rel="stylesheet" />
<link href="/css/style.css" rel="stylesheet" />
<!-- weird api page cdn -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
@ -42,13 +42,14 @@
</script>
<!-- socket.io scriot -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.min.js"></script>
<!-- fancy table cdn -->
<!-- <script src="https://cdn.jsdelivr.net/npm/jquery.fancytable/dist/fancyTable.min.js"></script> -->
<!-- jq-repeat -->
<script src="js/jq-repeat.js"></script>
<script src="/js/jq-repeat.js"></script>
<!-- jquery app.js -->
<script src="js/app.js"></script>
<!-- jquery public app.js -->
<script src="/js/app.js"></script>
</head>
<!-- javascript function to check if user is auth -->
<script>
@ -57,13 +58,12 @@
//check if user is logged in
app.auth.isLoggedIn(function (error, data) {
if (!error) {
console.log(error);
$.scope.getUsername.update(data);
if (location.pathname == "/profile") {
$.scope.getUserDetails.update(data);
}
$("#cl-logout-button").show("fast");
$("#cl-viewdata-button").show("fast");
$("#cl-api-button").show("fast");
$("#cl-profile-button").show("fast");
$("#cl-login-button").hide("fast");
@ -80,7 +80,7 @@
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-light top-nav fixed-top">
<div class="container">
<a class="navbar-brand" href="/">
<img src="images/logo.png" alt="logo" />
<img src="/images/logo.png" alt="logo" />
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
@ -100,14 +100,13 @@
<li class="nav-item">
<a class="nav-link" href="/contact">Contact</a>
</li>
<!--
<li class="nav-item">
<a class="nav-link" href="/api">API Doc</a>
</li>
-->
<!-- profile button -->
<div class="form-inline mt-2 mt-md-0">
<a id="cl-viewdata-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/viewdata"
style="display: none">
<i class="fas fa-sign-out"></i>Data
</a>
<a id="cl-api-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/api"
style="display: none">
<i class="fas fa-sign-out"></i> API
@ -133,4 +132,4 @@
</ul>
</div>
</div>
</nav>
</nav>

View File

@ -0,0 +1,259 @@
<%- include('top') %>
<link href="css/data.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script type="text/javascript">
// Require login to see this page.
app.auth.forceLogin()
//get Location data
app.api.get('location', function (error, data) {
for (let row in data){
//format time to local time than push
data[row].createdAt = new Date(data[row].createdAt).toLocaleString();
data[row].updatedAt = new Date(data[row].updatedAt).toLocaleString();
$.scope.LocationTable.push(data[row]);
}
})
//get sensor data
app.api.get('sensor', function (error, data) {
for (let row in data){
//format time to local time than push
data[row].createdAt = new Date(data[row].createdAt).toLocaleString();
data[row].updatedAt = new Date(data[row].updatedAt).toLocaleString();
$.scope.SensorTable.push(data[row]);
}
})
</script>
<br>
<br>
<body>
<div class="air-quality-container">
<!-- header -->
<div class="header-container">
<h1>Daily Air Quality Data Chart</h1>
</div>
<br>
<div class="chart-container">
<canvas id="DailyDataChart"></canvas>
</div>
<div class="button-container">
<button id="psiButton">PSI</button>
<button id="tempButton">Temperature</button>
<button id="humButton">Humidity</button>
<button id="o3Button">O3</button>
<button id="no2Button">NO2</button>
<button id="so2Button">SO2</button>
<button id="coButton">CO</button>
</div>
<br>
<div class="header-container">
<h1>Location Table</h1>
</div>
<table id="LocationTable" class="table table-striped LocationTable">
<thead>
<tr>
<th>ID</th>
<th>Location Name</th>
<th>Added_By</th>
<th>Description</th>
<th>CreatedAt</th>
<th>UpdatedAt</th>
</tr>
</thead>
<tbody>
<!-- Table Content Goes Here -->
<tr jq-repeat="LocationTable">
<td>{{id}}</td>
<td>{{name}}</td>
<td>{{added_by}}</td>
<td>{{description}}</td>
<td>{{createdAt}}</td>
<td>{{updatedAt}}</td>
</tbody>
</table>
<div class="header-container">
<h1>Sensor Table</h1>
</div>
<table id="sensorTable" class="table table-striped sensorTable">
<thead>
<tr>
<th>ID</th>
<th>Sensor Name</th>
<th>Added_By</th>
<th>Mac Address</th>
<th>Description</th>
<th>Location ID</th>
<th>CreatedAt</th>
<th>UpdatedAt</th>
</tr>
</thead>
<tbody>
<!-- Table Content Goes Here -->
<tr jq-repeat="SensorTable">
<td>{{id}}</td>
<td>{{name}}</td>
<td>{{added_by}}</td>
<td>{{mac_address}}</td>
<td>{{description}}</td>
<td>{{locationid}}</td>
<td>{{createdAt}}</td>
<td>{{updatedAt}}</td>
</tbody>
</table>
<br>
<div class="download-container">
<p>Download sensor data here:</p>
<button id="downloadCSVButton" onclick="downloadCSV()">Download CSV</button>
<br><br>
<button id="downloadCSVButton" onclick="downloadExcel()">Download Excel Sheet</button>
</div>
</div>
</body>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<script src="js/data.js"></script>
<!-- //download csv and excel -->
<script type="text/javascript">
//https://stackoverflow.com/questions/14964035/how-to-export-javascript-array-info-to-csv-on-client-side
function exportToCsv(filename, rows) {
var processRow = function (row) {
var finalVal = '';
for (var j = 0; j < row.length; j++) {
var innerValue = row[j] === null ? '' : row[j].toString();
if (row[j] instanceof Date) {
innerValue = row[j].toLocaleString();
};
var result = innerValue.replace(/"/g, '""');
if (result.search(/("|,|\n)/g) >= 0)
result = '"' + result + '"';
if (j > 0)
finalVal += ',';
finalVal += result;
}
return finalVal + '\n';
};
var csvFile = '';
for (var i = 0; i < rows.length; i++) {
csvFile += processRow(rows[i]);
}
var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob, filename);
} else {
var link = document.createElement("a");
if (link.download !== undefined) { // feature detection
// Browsers that support HTML5 download attribute
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download", filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
//content="application/vnd.ms-excel;charset=utf-8"
function exportToExcel(filename, rows) {
var processRow = function (row) {
var finalVal = '';
for (var j = 0; j < row.length; j++) {
var innerValue = row[j] === null ? '' : row[j].toString();
if (row[j] instanceof Date) {
innerValue = row[j].toLocaleString();
}
var result = innerValue.replace(/"/g, '""');
if (result.search(/("|,|\n)/g) >= 0) {
result = '"' + result + '"';
}
if (j > 0) {
finalVal += '\t'; // Use tabs for Excel format
}
finalVal += result;
}
return finalVal + '\n';
};
var excelFile = '';
for (var i = 0; i < rows.length; i++) {
excelFile += processRow(rows[i]);
}
var blob = new Blob([excelFile], { type: "application/vnd.ms-excel;charset=utf-8" });
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob, filename + '.xls');
} else {
var link = document.createElement("a");
if (link.download !== undefined) { // Feature detection
// Browsers that support HTML5 download attribute
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download", filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
//onclick call
function downloadCSV() {
app.api.get('sensor-data/', function (error, data) {
//to loop through the data and assign into map array
var formattedData = data.map(function (item) {
return [
item.id,
item.sensorid,
item.locationid,
JSON.stringify(item.measurement), //to handle measurement object
item.createdAt
];
});
exportToCsv('export.csv', [
['id', 'sensorid', 'locationid', 'measurement', 'createdAt'],
...formattedData
]);
});
}
function downloadExcel() {
app.api.get('sensor-data/', function (error, data) {
//to loop through the data and assign into map array
var formattedData = data.map(function (item) {
return [
item.id,
item.sensorid,
item.locationid,
JSON.stringify(item.measurement), //to handle measurement object
item.createdAt
];
});
exportToExcel('export.xls', [
['id', 'sensorid', 'locationid', 'measurement', 'createdAt'],
...formattedData
]);
});
}
</script>
<%- include('bot') %>