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

View File

@ -9,13 +9,13 @@ const sequelize = new Sequelize(
process.env.DB_USER,
process.env.DB_PASS,
{
host: "mpsqldatabase.mysql.database.azure.com",
dialect: 'mysql',
host: "mpsqldatabasean.mysql.database.azure.com",
dialect: 'mysql',
// 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')),
},
},

View File

@ -7,16 +7,15 @@ i repeat DO NOT USE CREDS IN CODE! Please use .env files (https://www.npmjs.com/
## Workload
1) Ti Seng
* Webserver Microservices
* IoT sensor
* Most Database / Backend Functions of this repo
* consumer website api and user function
2) Sean
* Admin Website Microservice
Micro Service
1) api.blah handle api
2) admin.blah admin website
3) consumer.blah comsumer
4) proxy.blah reverproxy
5) mqtt.blah mqtt service
1) admin.blah admin website
2) consumer.blah comsumer
3) proxy.blah reverproxy
4) mqtt.blah mqtt service

View File

@ -14,3 +14,5 @@ let transporter = nodemailer.createTransport({
},
});
module.exports = { transporter };

47
Sean/modules/otpUtils.js Normal file
View File

@ -0,0 +1,47 @@
const nodemailer = require("nodemailer");
const otpGenerator = require('otp-generator');
const path = require('path');
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
const generateOTP = () => {
const otp = otpGenerator.generate(8, { upperCase: true, specialChars: true });
const expirationTime = Date.now() + 5 * 60 * 1000; // 5 minutes expiration
return { otp, expirationTime };
};
const sendOTPByEmail = async (email, otp) => {
try {
const transporter = nodemailer.createTransport({
service: 'gmail',
host: 'smtp.gmail.com',
port: 587, // use the appropriate port for your SMTP server
secure: false, // true for 465, false for other ports
auth: {
user: process.env.euser, // replace with your email
pass: process.env.epass // replace with your email password
}
});
const mailOptions = {
from: process.env.euser,
to: email,
subject: 'Login OTP',
text: `Your OTP for login is: ${otp}`
};
await transporter.sendMail(mailOptions);
console.log('OTP sent successfully to', email);
} catch (error) {
console.error('Error sending OTP:', error);
throw error;
}
};
module.exports = {
generateOTP,
sendOTPByEmail
};

View File

@ -0,0 +1,13 @@
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
message: 'Too many login attempts from this IP, please try again later.',
standardHeaders: "draft-7", // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
legacyHeaders: false, // Disable the `X-RateLimit-*` headers.
});
module.exports = limiter;

View File

@ -0,0 +1,106 @@
const {body } = require('express-validator');
const locationValidation = [
body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(),
body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(),
body('description').trim().escape(),
];
const locationValidationUpdate = [
body('id').trim().escape(),
body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(),
body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(),
body('description').trim().escape(),
];
const locationdeleteValidation = [
body('id').trim().escape()
];
const sensorValidation = [
body('sensorname').trim().isLength({ min: 1 }).withMessage('Sensor Name must not be empty').escape(),
body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(),
body('macAddress').escape().custom(value => {
const macAddressRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
if (!macAddressRegex.test(value)) {
throw new Error('Invalid MAC address format');
}
return true;
}).withMessage('Invalid MAC address format').escape(),
body('description').trim().escape(),
body('location').trim().escape()
];
const sensorupdateValidation = [
body('id').trim().escape(),
body('sensorname').trim().isLength({ min: 1 }).withMessage('Sensor Name must not be empty').escape(),
body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(),
body('macAddress').escape().custom(value => {
const macAddressRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
if (!macAddressRegex.test(value)) {
throw new Error('Invalid MAC address format');
}
return true;
}).withMessage('Invalid MAC address format').escape(),
body('description').trim().escape(),
body('location').trim().escape()
];
const sensordeleteValidation = [
body('id').trim().escape()
];
const loginValidation = [
body('username').escape().trim().isLength({ min: 1 }).withMessage('Username must not be empty'),
body('password').escape().trim().isLength({ min: 1 }).withMessage('Password must not be empty'),
];
const otpValidation = [
body('otp').escape().trim().isLength({ min: 1 }).withMessage('OTP must not be empty'),
];
const createValidation = [
body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(),
body('username').trim().isLength({ min: 1 }).withMessage('Username must not be empty').escape(),
body('email').isEmail().withMessage('Invalid email address').normalizeEmail(),
body('password').escape().trim().custom((value) => {
if (!isStrongPassword(value)) { throw new Error('Password does not meet complexity requirements'); } return true;
}),
body('jobTitle').trim().isLength({ min: 1 }).withMessage('Job title must not be empty').escape(),
];
function isStrongPassword(password) {
// Password must be at least 10 characters long
if (password.length < 10) {
return false;
}
// Password must contain at least one uppercase letter
if (!/[A-Z]/.test(password)) {
return false;
}
// Password must contain at least one lowercase letter
if (!/[a-z]/.test(password)) {
return false;
}
// Password must contain at least one digit
if (!/\d/.test(password)) {
return false;
}
// Password must contain at least one symbol
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
return false;
}
return true;
}
module.exports = {
locationValidation,locationValidationUpdate,locationdeleteValidation
,sensorValidation,sensorupdateValidation,sensordeleteValidation,loginValidation,otpValidation
,createValidation
};

View File

@ -1,26 +1,26 @@
const express = require("express");
const session = require("express-session");
const rateLimit = require('express-rate-limit');
const cookieParser = require('cookie-parser');
const bodyParser = require("body-parser");
const bcrypt = require("bcrypt");
const crypto = require("crypto");
const nodemailer = require("nodemailer");
const otpGenerator = require('otp-generator');
const { body, validationResult } = require('express-validator');
const validator = require('validator');
const axios = require('axios');
const pidusage = require('pidusage');
const { validationResult } = require('express-validator');
const { locationValidation, locationValidationUpdate, locationdeleteValidation
,sensorValidation, sensorupdateValidation, sensordeleteValidation, loginValidation
,otpValidation, createValidation } = require('./modules/validationMiddleware');
const rateLimit = require('./modules/rateLimitMiddleware');
const { generateOTP, sendOTPByEmail } = require('./modules/otpUtils');
const { format } = require('date-fns');
const helmet = require('helmet');
const { Sequelize } = require('sequelize');
const { transporter } = require("./modules/nodeMailer");
const { sequelize, User } = require("./modules/mysql");
const userLogs= require('./models/userLogs')(sequelize); // Adjust the path based on your project structure
const userLogs= require('./models/userLogs')(sequelize);
const app = express();
const nonce = crypto.randomBytes(16).toString('base64');
console.log('Nonce:', nonce);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser());
@ -30,17 +30,6 @@ require("dotenv").config();
app.use(bodyParser.urlencoded({ extended: true }));
app.set("view engine", "ejs");
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'",`'nonce-${nonce}'`],
scriptSrc: ["'self'",`'nonce-${nonce}'`,"'strict-dynamic'", 'cdn.jsdelivr.net', 'fonts.googleapis.com', 'stackpath.bootstrapcdn.com', 'code.jquery.com', 'cdnjs.cloudflare.com'],
styleSrc: ["'self'",`'nonce-${nonce}'`, 'cdn.jsdelivr.net', 'fonts.googleapis.com'],
imgSrc: ["'self'"],
fontSrc: ["'self'", 'fonts.gstatic.com'],
},
})
);
app.use(session({
secret: process.env.key,
@ -56,84 +45,39 @@ function isAuthenticated(req, res, next) {
if (req.session && req.session.authenticated) {
return next();
} else {
res.redirect("/login");
res.redirect("/index");
}
}
const generateOTP = () => {
const otp = otpGenerator.generate(6, { upperCase: false, specialChars: false });
const expirationTime = Date.now() + 5 * 60 * 1000; // 5 minutes expiration
return { otp, expirationTime };
};
const sendOTPByEmail = async (email, otp) => {
try {
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.euser, // replace with your email
pass: process.env.epass // replace with your email password
}
});
const mailOptions = {
from: process.env.euser,
to: email,
subject: 'Login OTP',
text: `Your OTP for login is: ${otp}`
};
await transporter.sendMail(mailOptions);
console.log('OTP sent successfully to', email);
} catch (error) {
console.error('Error sending OTP:', error);
throw error;
}
};
app.get('/index', (req, res) => {
res.render('index');
});
app.get("/login", (req, res) => {
res.render("login", { error: null });
});
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 3 requests per windowMs
message: 'Too many login attempts from this IP, please try again later.',
});
app.use('/login', limiter);
app.post('/login', [
body('username').escape().trim().isLength({ min: 1 }).withMessage('Username must not be empty'),
body('password').escape().trim().isLength({ min: 1 }).withMessage('Password must not be empty'),
],
async (req, res) => {
try {
const errors = validationResult(req);
app.use('/login', rateLimit);
app.post('/login', loginValidation, async (req, res) => {
try {const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.render('login', { error: 'Invalid input. Please check your credentials.', csrfToken: req.session.csrfToken });
}
let { username, password } = req.body;
username = username.trim();
const user = await User.findOne({ where: { username } });
if (user) {
const isLoginSuccessful = await bcrypt.compare(password, user.password);
if (isLoginSuccessful) {
await userLogs.create({ username, success: true, activity: "Credentials entered correctly" });
const { otp, expirationTime } = generateOTP();
req.session.otp = otp;
req.session.otpExpiration = expirationTime;
req.session.save();
console.log(otp);
try {
await sendOTPByEmail(user.email, otp);
@ -174,17 +118,12 @@ async (req, res) => {
// OTP verification route
app.post("/verify-otp", [
body('otp').escape().trim().isLength({ min: 1 }).withMessage('OTP must not be empty'),
],
async (req, res) => {
app.post("/verify-otp", otpValidation ,async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.render('otp', { error: 'Invalid OTP. Please try again.'});
}
const enteredOTP = req.body.otp;
if (!req.session) {
@ -222,17 +161,10 @@ app.post("/verify-otp", [
req.session.authenticated = true;
req.session.username = req.body.username;
req.session.sessionToken = sessionToken;
csrfTokenSession = crypto.randomBytes(32).toString('hex');
// Log anti-CSRF token
console.log(`Generated Anti-CSRF Token: ${csrfTokenSession}`);
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
res.cookie('sessionToken', sessionToken, { secure: true, httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000) }); // Expires in 1 day
console.log(`Generated Session Token: ${sessionToken}`);
res.redirect("/home");
} else {
if (req.body.username) {
@ -267,7 +199,7 @@ app.post("/verify-otp", [
}
// Redirect to the login page after logout
res.redirect("/login");
res.redirect("/index");
});
} catch (error) {
console.error("Error in logout route:", error);
@ -275,17 +207,33 @@ app.post("/verify-otp", [
}
});
const getDatabaseStatus = async () => {
try {
await sequelize.authenticate();
return { connected: true };
} catch (error) {
console.error('Database connection error:', error);
return { connected: false, error: error.message };
}
};
app.get("/home", isAuthenticated, (req, res) => {
// Render the home page with sensor data
res.render("home", {
username: req.session.username,
});
const getSystemHealth = async () => {
const cpuInfo = await pidusage(process.pid);
const databaseStatus = await getDatabaseStatus();
return {
serverStatus: { uptime: process.uptime() },
databaseStatus,
resourceUtilization: {
cpuUsage: cpuInfo.cpu,
memoryUsage: process.memoryUsage(),
},
};
};
app.get("/home", isAuthenticated, async (req, res) => {
const systemHealth = await getSystemHealth();
res.render("home", { username: req.session.username, systemHealth});
});
app.get("/inusers", isAuthenticated, async (req, res) => {
try {
// Fetch all user data from the database using Sequelize
@ -294,9 +242,8 @@ app.post("/verify-otp", [
});
const currentUsername = req.session.username;
// Render the inusers page with JSON data
res.render("inusers", { nonce: nonce, allUsers, csrfToken: csrfTokenSession, currentUsername });
res.render("inusers", {allUsers, csrfToken: req.session.csrfToken, currentUsername });
} catch (error) {
console.error("Error fetching all users:", error);
res.status(500).send("Internal Server Error");
@ -332,27 +279,15 @@ function isStrongPassword(password) {
return true;
}
app.post(
'/createUser',
[
body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(),
body('username').trim().isLength({ min: 1 }).withMessage('Username must not be empty').escape(),
body('email').isEmail().withMessage('Invalid email address').normalizeEmail(),
body('password').custom((value) => {
if (!isStrongPassword(value)) { throw new Error('Password does not meet complexity requirements'); } return true;
}),
body('jobTitle').trim().isLength({ min: 1 }).withMessage('Job title must not be empty').escape(),
],
async (req, res) => {
app.post
('/createUser', createValidation,
async (req, res) => {
try {
const errors = validationResult(req);
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const sessionTokencookie = req.cookies['sessionToken'];
// Verify sessionToken with the one stored in the database
@ -364,7 +299,7 @@ app.post(
// Validate the anti-CSRF token
const submittedCSRFToken = req.body.csrf_token;
if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) {
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
@ -375,10 +310,6 @@ app.post(
// Extract the username of the user creating a new user
const creatorUsername = req.session.username; // Adjust this based on how you store the creator's username in your session
// Additional password complexity check
if (!isStrongPassword(password)) {
return res.status(400).json({ error: "Password does not meet complexity requirements" });
}
// Check if the username is already taken
const existingUser = await User.findOne({ where: { username } });
@ -477,24 +408,13 @@ app.post("/forgot-password", async (req, res) => {
const error = "Username or email not found.";
return res.render("forgot-password", { error, success: null });
}
// Generate reset token and update the user
const reset_token = crypto.randomBytes(20).toString("hex");
const reset_token_expiry = new Date(Date.now() + 3600000); // Token expires in 1 hour
// Update the user with the reset token and expiry
await User.update(
{
reset_token,
reset_token_expiry,
},
{
where: {
id: user.id, // Replace 'id' with the actual primary key field of your User model
},
}
await User.update({reset_token,reset_token_expiry,},
{where: {id: user.id},}
);
// Send email with reset link
const resetLink = `http://localhost:3000/reset-password/${reset_token}`;
const mailOptions = {
@ -502,12 +422,9 @@ app.post("/forgot-password", async (req, res) => {
subject: "Password Reset",
text: `Click on the following link to reset your password: ${resetLink}`,
};
await transporter.sendMail(mailOptions);
const success = "Password reset email sent successfully. Check your inbox.";
res.render("forgot-password", { error: null, success });
// Log the successful sending of the reset link in the database
await userLogs.create({
username: user.username,
@ -523,29 +440,22 @@ app.post("/forgot-password", async (req, res) => {
console.error("Error during password reset:", error);
const errorMessage = "An error occurred during the password reset process.";
res.render("forgot-password", { error: errorMessage, success: null });
}
});
}});
app.post("/reset-password/:token", async (req, res) => {
try {
const { token } = req.params;
const { password, confirmPassword } = req.body;
// Sanitize the inputs
const sanitizedToken = validator.escape(token);
const sanitizedPassword = validator.escape(password);
const sanitizedConfirmPassword = validator.escape(confirmPassword);
// Find user with matching reset token and not expired
const user = await User.findOne({
where: {
reset_token: sanitizedToken,
reset_token_expiry: { [Sequelize.Op.gt]: new Date() },
where: {reset_token: sanitizedToken,
reset_token_expiry: { [Sequelize.Op.gt]: new Date() },
},
});
if (!user) {
// Pass the error to the template when rendering the reset-password page
return res.render("reset-password", {
@ -553,7 +463,6 @@ app.post("/forgot-password", async (req, res) => {
resetError: "Invalid or expired reset token",
});
}
// Check if passwords match
if (sanitizedPassword !== sanitizedConfirmPassword) {
// Pass the error to the template when rendering the reset-password page
@ -562,31 +471,24 @@ app.post("/forgot-password", async (req, res) => {
resetError: "Passwords do not match",
});
}
// Check if the new password meets complexity requirements
if (!isStrongPassword(sanitizedPassword)) {
// Pass the error to the template when rendering the reset-password page
return res.render("reset-password", {
token,
resetError:
token, resetError:
"Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.",
});
}
// Hash the new password
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(sanitizedPassword, saltRounds);
// Update user's password and clear reset token
const updateQuery = {
password: hashedPassword,
reset_token: null,
reset_token_expiry: null,
};
const whereCondition = {
reset_token: sanitizedToken,
};
const whereCondition = {reset_token: sanitizedToken,};
await User.update(updateQuery, {
where: whereCondition,
});
@ -629,14 +531,12 @@ app.post("/reset-password", async (req, res) => {
const creatorUsername = req.session.username;
const submittedCSRFToken = req.body.csrf_token;
if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) {
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
const sessionTokencookie = req.cookies['sessionToken'];
// Verify sessionToken with the one stored in the database
const user = await User.findOne({ where: { sessionid: sessionTokencookie } });
if (!user) {
return res.status(403).json({ error: 'Invalid sessionToken' });
}
@ -644,12 +544,10 @@ app.post("/reset-password", async (req, res) => {
const sanitizedUsername = validator.escape(username);
const sanitizedPassword = validator.escape(password);
const sanitizedConfirmPassword = validator.escape(confirmPassword);
// Check if passwords match
if (sanitizedPassword !== sanitizedConfirmPassword) {
return res.status(400).json({ error: "Passwords do not match" });
}
// Check if the new password meets complexity requirements
if (!isStrongPassword(sanitizedPassword)) {
return res.status(400).json({
@ -657,31 +555,25 @@ app.post("/reset-password", async (req, res) => {
"Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.",
});
}
try {
// Find the user in the database
const user = await User.findOne({ where: { username: sanitizedUsername } });
if (!user) {
return res.status(404).json({ error: "User does not exist" });
}
// Generate a random salt and hash the new password
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(sanitizedPassword, saltRounds);
// Update user's password
await User.update(
{ password: hashedPassword },
{ where: { username: sanitizedUsername } }
);
// Log password reset activity
await userLogs.create({
username: creatorUsername,
activity: `Password has been reset for ${sanitizedUsername}`,
});
// Password update successful
return res.status(200).json({ success: "Password updated successfully" });
} catch (error) {
@ -693,10 +585,8 @@ app.post("/reset-password", async (req, res) => {
app.get('/searchUser', async (req, res) => {
const { username } = req.query;
// Sanitize the input
const sanitizedUsername = validator.escape(username);
try {
// Find the user in the database
const user = await User.findOne({ where: { username: sanitizedUsername } });
@ -704,11 +594,7 @@ app.get('/searchUser', async (req, res) => {
if (!user) {
// No user found with the given username
res.status(404).json({ success: false, error: 'User not found' });
} else {
// User found, return user data
res.json(user);
}
} else {res.json(user)}
} catch (error) {
console.error('Sequelize query error:', error);
res.status(500).json({ success: false, error: 'Internal Server Error' });
@ -719,7 +605,6 @@ app.get('/api/users', async (req, res) => {
try {
// Find all users in the database
const users = await User.findAll();
// Return the users in the response
res.json(users);
} catch (error) {
@ -759,7 +644,7 @@ app.delete('/api/deleteUser/:username', async (req, res) => {
const { csrfToken } = req.body;
console.log(csrfToken);
// Compare CSRF token with the one stored in the session
if (csrfToken !== csrfTokenSession) {
if (csrfToken !== req.session.csrfToken) {
return res.status(403).json({ success: false, error: 'CSRF token mismatch' });
}
@ -788,9 +673,6 @@ app.delete('/api/deleteUser/:username', async (req, res) => {
}
});
app.get('/api/getLogs', async (req, res) => {
try {
// Query the database to fetch logs using Sequelize model
@ -814,6 +696,212 @@ app.get('/api/getLogs', async (req, res) => {
}
});
app.get("/locations", isAuthenticated, async (req, res) => {
try {
// Fetch data using Axios
const response = await axios.get(process.env.API_ALLLOCATION);
const locationsData = response.data;
// Render the "locations" page with the fetched JSON data
res.render("locations", { locationsData, csrfToken: req.session.csrfToken});
} catch (error) {
console.error("Error fetching locations:", error);
res.status(500).send("Internal Server Error");
}
});
app.post('/location/new', locationValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const sessionTokencookie = req.cookies['sessionToken'];
const user = await User.findOne({ where: { sessionid: sessionTokencookie } });
if (!user) {
return res.status(403).json({ error: 'Invalid sessionToken' });
}
const submittedCSRFToken = req.body.csrf_token;
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
const { name, added_by, description } = req.body;
const preparedData = {name, added_by, description};
// Make a POST request with the sanitized data using Axios
const axiosResponse = await axios.post(process.env.API_NEWLOCATION, preparedData);
// Send the Axios response back to the client
res.status(axiosResponse.status).json(axiosResponse.data);
} catch (error) {
console.error('Error handling new location submission:', error);
res.status(500).json({ message: 'Internal Server Error' });
}
});
app.post('/location/update', locationValidationUpdate, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const sessionTokencookie = req.cookies['sessionToken'];
const user = await User.findOne({ where: { sessionid: sessionTokencookie } });
if (!user) {
return res.status(403).json({ error: 'Invalid sessionToken' });
}
const submittedCSRFToken = req.body.csrf_token;
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
const { id, name, added_by, description } = req.body;
const preparedData = {id, name, added_by, description};
// Make a POST request with the sanitized data using Axios
const axiosResponse = await axios.post(process.env.API_UPDATELOCATION, preparedData);
// Send the Axios response back to the client
res.status(axiosResponse.status).json(axiosResponse.data);
} catch (error) {
console.error('Error handling new location submission:', error);
res.status(500).json({ message: 'Internal Server Error' });
}
});
app.post('location/delete',locationdeleteValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const sessionTokencookie = req.cookies['sessionToken'];
const user = await User.findOne({ where: { sessionid: sessionTokencookie } });
if (!user) {
return res.status(403).json({ error: 'Invalid sessionToken' });
}
const submittedCSRFToken = req.body.csrf_token;
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
const {id} = req.body;
const preparedData = {id};
// Make a POST request with the sanitized data using Axios
const axiosResponse = await axios.post(process.env.API_DELLOCATION, preparedData);
// Send the Axios response back to the client
res.status(axiosResponse.status).json(axiosResponse.data);
} catch (error) {
console.error('Error handling new sensor submission:', error);
res.status(500).json({ message: 'Internal Server Error' });
}
});
app.get("/sensors", isAuthenticated, async (req, res) => {
try {
// Render the inusers page with JSON data
const response = await axios.get(process.env.API_ALLLOCATION);
const locationsData = response.data;
const response2 = await axios.get(process.env.API_ALLSENSOR);
const sensorData = response2.data;
res.render("sensors",{locationsData, sensorData, csrfToken: req.session.csrfToken});
} catch (error) {
console.error("Error:", error);
res.status(500).send("Internal Server Error");
}
});
app.post('sensor/new',sensorValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const sessionTokencookie = req.cookies['sessionToken'];
const user = await User.findOne({ where: { sessionid: sessionTokencookie } });
if (!user) {
return res.status(403).json({ error: 'Invalid sessionToken' });
}
const submittedCSRFToken = req.body.csrf_token;
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
const { sensorname, added_by, macAddress, description, location} = req.body;
const preparedData = {sensorname, added_by, macAddress, description, location};
// Make a POST request with the sanitized data using Axios
const axiosResponse = await axios.post(process.env.API_NEWSENSOR, preparedData);
// Send the Axios response back to the client
res.status(axiosResponse.status).json(axiosResponse.data);
} catch (error) {
console.error('Error handling new sensor submission:', error);
res.status(500).json({ message: 'Internal Server Error' });
}
});
app.post('sensor/update',sensorupdateValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const sessionTokencookie = req.cookies['sessionToken'];
const user = await User.findOne({ where: { sessionid: sessionTokencookie } });
if (!user) {
return res.status(403).json({ error: 'Invalid sessionToken' });
}
const submittedCSRFToken = req.body.csrf_token;
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
const { id, sensorname, added_by, macAddress, description, location} = req.body;
const preparedData = {id, sensorname, added_by, macAddress, description, location};
// Make a POST request with the sanitized data using Axios
const axiosResponse = await axios.post(process.env.API_NEWSENSOR, preparedData);
// Send the Axios response back to the client
res.status(axiosResponse.status).json(axiosResponse.data);
} catch (error) {
console.error('Error handling new sensor submission:', error);
res.status(500).json({ message: 'Internal Server Error' });
}
});
app.post('sensor/delete',sensordeleteValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const sessionTokencookie = req.cookies['sessionToken'];
const user = await User.findOne({ where: { sessionid: sessionTokencookie } });
if (!user) {
return res.status(403).json({ error: 'Invalid sessionToken' });
}
const submittedCSRFToken = req.body.csrf_token;
if (!req.session.csrfToken || submittedCSRFToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
const {id} = req.body;
const preparedData = {id};
// Make a POST request with the sanitized data using Axios
const axiosResponse = await axios.post(process.env.API_DELSENSOR, preparedData);
// Send the Axios response back to the client
res.status(axiosResponse.status).json(axiosResponse.data);
} catch (error) {
console.error('Error handling new sensor submission:', error);
res.status(500).json({ message: 'Internal Server Error' });
}
});
app.get("/apilog", isAuthenticated, async (req, res) => {
try {
// Fetch data using Axios
const response = await axios.get(process.env.API_LOGS);
const logData = response.data;
// Render the "locations" page with the fetched JSON data
res.render("locations", {logData});
} catch (error) {
console.error("Error fetching locations:", error);
res.status(500).send("Internal Server Error");
}
});
app.use(express.static("views"));
app.listen(PORT, () => {

68
Sean/views/apilog.ejs Normal file
View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Logs</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap">
</head>
<body>
<header>
<h1>ECOSAVER MANAGEMENT</h1>
</header>
<nav>
<a href="/home" id="homeLink">Home</a>
</nav>
<main>
<h2>Welcome to the API Logs Users Page</h2>
</main>
<div id="downloadButtonContainer">
<button id="downloadButton" onclick="downloadAsExcel()">Download as Excel</button>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Time</th>
<th>Method</th>
<th>Host</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<% logData.forEach(entry => { %>
<tr>
<td><%= entry.id %></td>
<td><%= entry.time %></td>
<td><%= entry.method %></td>
<td><%= entry.host %></td>
<td><%= entry.createdAt %></td>
</tr>
<% }); %>
</tbody>
</table>
<script >
const logData = <%- JSON.stringify(logData) %>;
</script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.4/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.2.1/exceljs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.js"></script>
<script src="apil;og.js"></script>
</body>
</html>

54
Sean/views/apilog.js Normal file
View File

@ -0,0 +1,54 @@
function downloadAsExcel() {
// Get the current date
var currentDate = new Date();
var formattedDate = currentDate.toISOString().slice(0, 10); // Format as YYYY-MM-DD
// Create a new workbook
var wb = XLSX.utils.book_new();
// Convert logData to a worksheet
var ws = XLSX.utils.json_to_sheet(logData);
// Add the worksheet to the workbook
XLSX.utils.book_append_sheet(wb, ws, 'Log Data');
// Create a blob with the Excel file content
var blob = XLSX.write(wb, { bookType: 'xlsx', type: 'blob', mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
// Trigger the download with the filename including the current date
saveAs(blob, 'log_data_' + formattedDate + '.xlsx');
}
document.addEventListener('DOMContentLoaded', function() {
// Initialize flatpickr on the date input
flatpickr("#datepicker", {
dateFormat: "Y-m-d",
onChange: function(selectedDates, dateStr, instance) {
// Call a function to filter logs based on the selected date
filterLogsByDate(dateStr);
}
});
function filterLogsByDate(selectedDate) {
// Use logData to filter logs based on the selected date
var filteredLogs = logData.filter(function(entry) {
return entry.createdAt.startsWith(selectedDate);
});
// Render the filtered logs in the table
renderLogsTable(filteredLogs);
}
function renderLogsTable(logs) {
var tableBody = document.getElementById('logsTableBody');
tableBody.innerHTML = '';
logs.forEach(function(entry) {
var row = document.createElement('tr');
row.innerHTML = `
<td>${entry.id}</td>
<td>${entry.time}</td>
<td>${entry.method}</td>
<td>${entry.host}</td>
<td>${entry.createdAt}</td>
`;
tableBody.appendChild(row);
});
}
});

4074
Sean/views/assets/animatecss/animate.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
/*!
* Bootstrap Reboot v5.0.1 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,265 @@
.navbar-dropdown {
left: 0;
padding: 0;
position: absolute;
right: 0;
top: 0;
transition: all 0.45s ease;
z-index: 1030;
background: #282828; }
.navbar-dropdown .navbar-logo {
margin-right: 0.8rem;
transition: margin 0.3s ease-in-out;
vertical-align: middle; }
.navbar-dropdown .navbar-logo img {
height: 3.125rem;
transition: all 0.3s ease-in-out; }
.navbar-dropdown .navbar-logo.mbr-iconfont {
font-size: 3.125rem;
line-height: 3.125rem; }
.navbar-dropdown .navbar-caption {
font-weight: 700;
white-space: normal;
vertical-align: -4px;
line-height: 3.125rem !important; }
.navbar-dropdown .navbar-caption, .navbar-dropdown .navbar-caption:hover {
color: inherit;
text-decoration: none; }
.navbar-dropdown .mbr-iconfont + .navbar-caption {
vertical-align: -1px; }
.navbar-dropdown.navbar-fixed-top {
position: fixed; }
.navbar-dropdown .navbar-brand span {
vertical-align: -4px; }
.navbar-dropdown.bg-color.transparent {
background: none; }
.navbar-dropdown.navbar-short .navbar-brand {
padding: 0.625rem 0; }
.navbar-dropdown.navbar-short .navbar-brand span {
vertical-align: -1px; }
.navbar-dropdown.navbar-short .navbar-caption {
line-height: 2.375rem !important;
vertical-align: -2px; }
.navbar-dropdown.navbar-short .navbar-logo {
margin-right: 0.5rem; }
.navbar-dropdown.navbar-short .navbar-logo img {
height: 2.375rem; }
.navbar-dropdown.navbar-short .navbar-logo.mbr-iconfont {
font-size: 2.375rem;
line-height: 2.375rem; }
.navbar-dropdown.navbar-short .mbr-table-cell {
height: 3.625rem; }
.navbar-dropdown .navbar-close {
left: 0.6875rem;
position: fixed;
top: 0.75rem;
z-index: 1000; }
.navbar-dropdown .hamburger-icon {
content: "";
display: inline-block;
vertical-align: middle;
width: 16px;
-webkit-box-shadow: 0 -6px 0 1px #282828,0 0 0 1px #282828,0 6px 0 1px #282828;
-moz-box-shadow: 0 -6px 0 1px #282828,0 0 0 1px #282828,0 6px 0 1px #282828;
box-shadow: 0 -6px 0 1px #282828,0 0 0 1px #282828,0 6px 0 1px #282828; }
.dropdown-menu .dropdown-toggle[data-toggle="dropdown-submenu"]::after {
border-bottom: 0.35em solid transparent;
border-left: 0.35em solid;
border-right: 0;
border-top: 0.35em solid transparent;
margin-left: 0.3rem; }
.dropdown-menu .dropdown-item:focus {
outline: 0; }
.nav-dropdown {
font-size: 0.75rem;
font-weight: 500;
height: auto !important; }
.nav-dropdown .nav-btn {
padding-left: 1rem; }
.nav-dropdown .link {
margin: .667em 1.667em;
font-weight: 500;
padding: 0;
transition: color .2s ease-in-out; }
.nav-dropdown .link.dropdown-toggle {
margin-right: 2.583em; }
.nav-dropdown .link.dropdown-toggle::after {
margin-left: .25rem;
border-top: 0.35em solid;
border-right: 0.35em solid transparent;
border-left: 0.35em solid transparent;
border-bottom: 0; }
.nav-dropdown .link.dropdown-toggle[aria-expanded="true"] {
margin: 0;
padding: 0.667em 3.263em 0.667em 1.667em; }
.nav-dropdown .link::after,
.nav-dropdown .dropdown-item::after {
color: inherit; }
.nav-dropdown .btn {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0;
margin-bottom: 0;
padding-left: 1.25rem;
padding-right: 1.25rem; }
.nav-dropdown .dropdown-menu {
border-radius: 0;
border: 0;
left: 0;
margin: 0;
padding-bottom: 1.25rem;
padding-top: 1.25rem;
position: relative; }
.nav-dropdown .dropdown-submenu {
margin-left: 0.125rem;
top: 0; }
.nav-dropdown .dropdown-item {
font-weight: 500;
line-height: 2;
padding: 0.3846em 4.615em 0.3846em 1.5385em;
position: relative;
transition: color .2s ease-in-out, background-color .2s ease-in-out; }
.nav-dropdown .dropdown-item::after {
margin-top: -0.3077em;
position: absolute;
right: 1.1538em;
top: 50%; }
.nav-dropdown .dropdown-item:focus, .nav-dropdown .dropdown-item:hover {
background: none; }
@media (max-width: 767px) {
.nav-dropdown.navbar-toggleable-sm {
bottom: 0;
display: none;
left: 0;
overflow-x: hidden;
position: fixed;
top: 0;
transform: translateX(-100%);
-ms-transform: translateX(-100%);
-webkit-transform: translateX(-100%);
width: 18.75rem;
z-index: 999; } }
.nav-dropdown.navbar-toggleable-xl {
bottom: 0;
display: none;
left: 0;
overflow-x: hidden;
position: fixed;
top: 0;
transform: translateX(-100%);
-ms-transform: translateX(-100%);
-webkit-transform: translateX(-100%);
width: 18.75rem;
z-index: 999; }
.nav-dropdown-sm {
display: block !important;
overflow-x: hidden;
overflow: auto;
padding-top: 3.875rem; }
.nav-dropdown-sm::after {
content: "";
display: block;
height: 3rem;
width: 100%; }
.nav-dropdown-sm.collapse.in ~ .navbar-close {
display: block !important; }
.nav-dropdown-sm.collapsing, .nav-dropdown-sm.collapse.in {
transform: translateX(0);
-ms-transform: translateX(0);
-webkit-transform: translateX(0);
transition: all 0.25s ease-out;
-webkit-transition: all 0.25s ease-out;
background: #282828; }
.nav-dropdown-sm.collapsing[aria-expanded="false"] {
transform: translateX(-100%);
-ms-transform: translateX(-100%);
-webkit-transform: translateX(-100%); }
.nav-dropdown-sm .nav-item {
display: block;
margin-left: 0 !important;
padding-left: 0; }
.nav-dropdown-sm .link,
.nav-dropdown-sm .dropdown-item {
border-top: 1px dotted rgba(255, 255, 255, 0.1);
font-size: 0.8125rem;
line-height: 1.6;
margin: 0 !important;
padding: 0.875rem 2.4rem 0.875rem 1.5625rem !important;
position: relative;
white-space: normal; }
.nav-dropdown-sm .link:focus, .nav-dropdown-sm .link:hover,
.nav-dropdown-sm .dropdown-item:focus,
.nav-dropdown-sm .dropdown-item:hover {
background: rgba(0, 0, 0, 0.2) !important;
color: #c0a375; }
.nav-dropdown-sm .nav-btn {
position: relative;
padding: 1.5625rem 1.5625rem 0 1.5625rem; }
.nav-dropdown-sm .nav-btn::before {
border-top: 1px dotted rgba(255, 255, 255, 0.1);
content: "";
left: 0;
position: absolute;
top: 0;
width: 100%; }
.nav-dropdown-sm .nav-btn + .nav-btn {
padding-top: 0.625rem; }
.nav-dropdown-sm .nav-btn + .nav-btn::before {
display: none; }
.nav-dropdown-sm .btn {
padding: 0.625rem 0; }
.nav-dropdown-sm .dropdown-toggle[data-toggle="dropdown-submenu"]::after {
margin-left: .25rem;
border-top: 0.35em solid;
border-right: 0.35em solid transparent;
border-left: 0.35em solid transparent;
border-bottom: 0; }
.nav-dropdown-sm .dropdown-toggle[data-toggle="dropdown-submenu"][aria-expanded="true"]::after {
border-top: 0;
border-right: 0.35em solid transparent;
border-left: 0.35em solid transparent;
border-bottom: 0.35em solid; }
.nav-dropdown-sm .dropdown-menu {
margin: 0;
padding: 0;
position: relative;
top: 0;
left: 0;
width: 100%;
border: 0;
float: none;
border-radius: 0;
background: none; }
.nav-dropdown-sm .dropdown-submenu {
left: 100%;
margin-left: 0.125rem;
margin-top: -1.25rem;
top: 0; }
.navbar-toggleable-sm .nav-dropdown .dropdown-menu {
position: absolute; }
.navbar-toggleable-sm .nav-dropdown .dropdown-submenu {
left: 100%;
margin-left: 0.125rem;
margin-top: -1.25rem;
top: 0; }
.navbar-toggleable-sm.opened .nav-dropdown .dropdown-menu {
position: relative; }
.navbar-toggleable-sm.opened .nav-dropdown .dropdown-submenu {
left: 0;
margin-left: 00rem;
margin-top: 0rem;
top: 0; }
.is-builder .nav-dropdown.collapsing {
transition: none !important; }

View File

@ -0,0 +1,8 @@
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(b,c,a){b!=Array.prototype&&b!=Object.prototype&&(b[c]=a.value)};$jscomp.getGlobal=function(b){return"undefined"!=typeof window&&window===b?b:"undefined"!=typeof global&&null!=global?global:b};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.polyfill=function(b,c,a,d){if(c){a=$jscomp.global;b=b.split(".");for(d=0;d<b.length-1;d++){var f=b[d];f in a||(a[f]={});a=a[f]}b=b[b.length-1];d=a[b];c=c(d);c!=d&&null!=c&&$jscomp.defineProperty(a,b,{configurable:!0,writable:!0,value:c})}};
$jscomp.polyfill("Array.from",function(b){return b?b:function(b,a,d){a=null!=a?a:function(a){return a};var f=[],e="undefined"!=typeof Symbol&&Symbol.iterator&&b[Symbol.iterator];if("function"==typeof e){b=e.call(b);for(var c=0;!(e=b.next()).done;)f.push(a.call(d,e.value,c++))}else for(e=b.length,c=0;c<e;c++)f.push(a.call(d,b[c],c));return f}},"es6","es3");
(function(){function b(a){"resize"===a.type&&(document.body.classList.remove("navbar-dropdown-open"),document.querySelector(".navbar-dropdown").querySelector(".navbar-collapse").classList.remove("show"),document.querySelector(".navbar-dropdown").classList.remove("opened"),Array.from(document.querySelector(".navbar-dropdown").querySelectorAll('.navbar-toggler[aria-expanded="true"]')).forEach(function(a){var b=a.querySelector(a.getAttribute("data-target"));b&&(b.classList.remove("in"),b.setAttribute("aria-expanded",
"false"),a.setAttribute("aria-expanded","false"))}));var b=document.documentElement.scrollTop;Array.from(document.querySelectorAll(".navbar-dropdown")).forEach(function(a){a.matches(".navbar-fixed-top")&&(a.matches(".transparent")&&!a.classList.contains("opened")&&(0<b?a.classList.remove("bg-color"):a.classList.add("bg-color")),0<b?a.classList.add("navbar-short"):a.classList.remove("navbar-short"))})}var c;["scroll","resize"].forEach(function(a){document.addEventListener(a,function(a){clearTimeout(c);
c=setTimeout(function(){b(a)},10)})});["show.bs.collapse","hide.bs.collapse"].forEach(function(a){document.addEventListener(a,function(b){if(b=b.target.closest(".navbar-dropdown"))"show.bs.collapse"===a?(document.body.classList.add("navbar-dropdown-open"),b.classList.add("opened")):(document.body.classList.remove("navbar-dropdown-open"),b.classList.remove("opened"),window.dispatchEvent(new Event("scroll.bs.navbar-dropdown.data-api")),b.dispatchEvent(new Event("collapse.bs.navbar-dropdown")))})});
document.querySelector("html").classList.contains("is-builder")||document.addEventListener("click",function(a){a=a.target;if(!a.classList.contains("nav-link")&&!a.parentNode.classList.contains("nav-link")){var b=document.querySelector("#navbarSupportedContent"),c=document.querySelector(".navbar-dropdown"),e=b.classList.contains("show"),g=a.closest(".nav-item a:not(.dropdown-toggle)");c=c.classList.contains("collapsed");(window.matchMedia("(max-width: 991px)").matches||c)&&(e&&!a.closest(".navbar-collapse")||
g)&&new bootstrap.Collapse(b)}});document.addEventListener("collapse.bs.nav-dropdown",function(a){(a=a.relatedTarget.closest(".navbar-dropdown"))&&(a=a.querySelector('.navbar-toggler[aria-expanded="true"]'))&&a.dispatchEvent(new Event("click"))});document.querySelectorAll(".nav-link.dropdown-toggle").forEach(function(a){a.addEventListener("click",function(a){a.preventDefault();a.target.parentNode.classList.toggle("open")})})})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

View File

@ -0,0 +1 @@
{"+GQdJ/FkTHHhtIo5SJpbjw==":"logo-96x43.png","sNnwj6dliedFMQPUjtqZKA==":"logo-1-96x43.png","FshdhnAXWIc3Sivjcd0w7w==":"logo-2-96x43.png","o6j4Z3r46yoaBgNIONzS4A==":"logo-3-96x43.png","gakqOFXqLTkOgBY/bPwdWw==":"logo-4-96x43.png","erPZweO4Ec5GB5uI98xycQ==":"ecosostenibilita-1000x500.jpeg","XL8v257g6FjjbOw+0BL9KA==":"air-pollution-1679x944.jpg","BOA7b8UnSidGomEe8QOuuQ==":"the%20expansion%20of%20cloud%20applications%20has%20added%20to...-766x476.png"}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
.jarallax {
position: relative;
z-index: 0;
}
.jarallax > .jarallax-img {
position: absolute;
object-fit: cover;
/* support for plugin https://github.com/bfred-it/object-fit-images */
font-family: 'object-fit: cover;';
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}

View File

@ -0,0 +1,45 @@
/*
Name : Just Another Parallax [Jarallax]
Version : 1.12.7
Author : nK <https://nkdev.info>
GitHub : https://github.com/nk-o/jarallax
*/
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,d,b){a!=Array.prototype&&a!=Object.prototype&&(a[d]=b.value)};$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.polyfill=function(a,d,b,e){if(d){b=$jscomp.global;a=a.split(".");for(e=0;e<a.length-1;e++){var g=a[e];g in b||(b[g]={});b=b[g]}a=a[a.length-1];e=b[a];d=d(e);d!=e&&null!=d&&$jscomp.defineProperty(b,a,{configurable:!0,writable:!0,value:d})}};
$jscomp.polyfill("Array.from",function(a){return a?a:function(a,b,e){b=null!=b?b:function(a){return a};var d=[],l="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];if("function"==typeof l){a=l.call(a);for(var q=0;!(l=a.next()).done;)d.push(b.call(e,l.value,q++))}else for(l=a.length,q=0;q<l;q++)d.push(b.call(e,a[q],q));return d}},"es6","es3");$jscomp.arrayIteratorImpl=function(a){var d=0;return function(){return d<a.length?{done:!1,value:a[d++]}:{done:!0}}};$jscomp.arrayIterator=function(a){return{next:$jscomp.arrayIteratorImpl(a)}};
$jscomp.SYMBOL_PREFIX="jscomp_symbol_";$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.SymbolClass=function(a,d){this.$jscomp$symbol$id_=a;$jscomp.defineProperty(this,"description",{configurable:!0,writable:!0,value:d})};$jscomp.SymbolClass.prototype.toString=function(){return this.$jscomp$symbol$id_};
$jscomp.Symbol=function(){function a(b){if(this instanceof a)throw new TypeError("Symbol is not a constructor");return new $jscomp.SymbolClass($jscomp.SYMBOL_PREFIX+(b||"")+"_"+d++,b)}var d=0;return a}();
$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.iterator;a||(a=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("Symbol.iterator"));"function"!=typeof Array.prototype[a]&&$jscomp.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return $jscomp.iteratorPrototype($jscomp.arrayIteratorImpl(this))}});$jscomp.initSymbolIterator=function(){}};
$jscomp.initSymbolAsyncIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.asyncIterator;a||(a=$jscomp.global.Symbol.asyncIterator=$jscomp.global.Symbol("Symbol.asyncIterator"));$jscomp.initSymbolAsyncIterator=function(){}};$jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[$jscomp.global.Symbol.iterator]=function(){return this};return a};
$jscomp.iteratorFromArray=function(a,d){$jscomp.initSymbolIterator();a instanceof String&&(a+="");var b=0,e={next:function(){if(b<a.length){var g=b++;return{value:d(g,a[g]),done:!1}}e.next=function(){return{done:!0,value:void 0}};return e.next()}};e[Symbol.iterator]=function(){return e};return e};$jscomp.polyfill("Array.prototype.keys",function(a){return a?a:function(){return $jscomp.iteratorFromArray(this,function(a){return a})}},"es6","es3");
(function(a){function d(e){if(b[e])return b[e].exports;var g=b[e]={i:e,l:!1,exports:{}};a[e].call(g.exports,g,g.exports,d);g.l=!0;return g.exports}var b={};d.m=a;d.c=b;d.d=function(a,b,l){d.o(a,b)||Object.defineProperty(a,b,{enumerable:!0,get:l})};d.r=function(a){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(a,Symbol.toStringTag,{value:"Module"});Object.defineProperty(a,"__esModule",{value:!0})};d.t=function(a,b){b&1&&(a=d(a));if(b&8||b&4&&"object"===typeof a&&a&&a.__esModule)return a;
var g=Object.create(null);d.r(g);Object.defineProperty(g,"default",{enumerable:!0,value:a});if(b&2&&"string"!=typeof a)for(var e in a)d.d(g,e,function(b){return a[b]}.bind(null,e));return g};d.n=function(a){var b=a&&a.__esModule?function(){return a["default"]}:function(){return a};d.d(b,"a",b);return b};d.o=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};d.p="";return d(d.s=10)})([,,function(a,d){a.exports=function(a){"complete"===document.readyState||"interactive"===document.readyState?
a.call():document.attachEvent?document.attachEvent("onreadystatechange",function(){"interactive"===document.readyState&&a.call()}):document.addEventListener&&document.addEventListener("DOMContentLoaded",a)}},function(a,d,b){d=b(4);a.exports="undefined"!==typeof window?window:"undefined"!==typeof d?d:"undefined"!==typeof self?self:{}},function(a,d){function b(a){"@babel/helpers - typeof";b="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&
"function"===typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a};return b(a)}d=function(){return this}();try{d=d||(new Function("return this"))()}catch(e){"object"===("undefined"===typeof window?"undefined":b(window))&&(d=window)}a.exports=d},,,,,,function(a,d,b){a.exports=b(11)},function(a,d,b){function e(a){"@babel/helpers - typeof";e="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"===typeof Symbol&&
a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a};return e(a)}b.r(d);a=b(2);a=b.n(a);var g=b(3);b.n(g);var l=b(12),q=g.window.jarallax;g.window.jarallax=l["default"];g.window.jarallax.noConflict=function(){g.window.jarallax=q;return this};if("undefined"!==typeof g.jQuery){b=function(){for(var a=arguments.length,b=Array(a),d=0;d<a;d++)b[d]=arguments[d];Array.prototype.unshift.call(b,this);a=l["default"].apply(g.window,b);return"object"!==e(a)?a:this};b.constructor=l["default"].constructor;
var v=g.jQuery.fn.jarallax;g.jQuery.fn.jarallax=b;g.jQuery.fn.jarallax.noConflict=function(){g.jQuery.fn.jarallax=v;return this}}a()(function(){Object(l["default"])(document.querySelectorAll("[data-jarallax]"))})},function(a,d,b){function e(a,f){var h=Array.isArray(a)?a:void 0;if(!h)if(h=a&&("undefined"!==typeof Symbol&&a[Symbol.iterator]||a["@@iterator"]),null==h)h=void 0;else{var c=[],b=!0,d=!1,m;try{for(h=h.call(a);!(b=(m=h.next()).done)&&(c.push(m.value),!f||c.length!==f);b=!0);}catch(B){d=!0;
var e=B}finally{try{if(!b&&null!=h["return"])h["return"]()}finally{if(d)throw e;}}h=c}if(!(m=h))a:{if(a){if("string"===typeof a){m=g(a,f);break a}m=Object.prototype.toString.call(a).slice(8,-1);"Object"===m&&a.constructor&&(m=a.constructor.name);if("Map"===m||"Set"===m){m=Array.from(a);break a}if("Arguments"===m||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(m)){m=g(a,f);break a}}m=void 0}if(!(a=m))throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
return a}function g(a,f){if(null==f||f>a.length)f=a.length;for(var h=0,c=Array(f);h<f;h++)c[h]=a[h];return c}function l(a){"@babel/helpers - typeof";l="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"===typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a};return l(a)}function q(a,f){for(var h=0;h<f.length;h++){var c=f[h];c.enumerable=c.enumerable||!1;c.configurable=!0;"value"in c&&(c.writable=!0);Object.defineProperty(a,
c.key,c)}}function v(a,f,h){f&&q(a.prototype,f);h&&q(a,h);return a}function u(){C?(!t&&document.body&&(t=document.createElement("div"),t.style.cssText="position: fixed; top: -9999px; left: 0; height: 100vh; width: 0;",document.body.appendChild(t)),k=(t?t.clientHeight:0)||p.window.innerHeight||document.documentElement.clientHeight):k=p.window.innerHeight||document.documentElement.clientHeight}function A(a){for(var f=[];null!==a.parentElement;)a=a.parentElement,1===a.nodeType&&f.push(a);return f}function x(){r.length&&
(r.forEach(function(a,f){var h=a.instance;a=a.oldData;var c=h.$item.getBoundingClientRect();c={width:c.width,height:c.height,top:c.top,bottom:c.bottom,wndW:p.window.innerWidth,wndH:k};var b=!a||a.wndW!==c.wndW||a.wndH!==c.wndH||a.width!==c.width||a.height!==c.height;a=b||!a||a.top!==c.top||a.bottom!==c.bottom;r[f].oldData=c;if(b)h.onResize();if(a)h.onScroll()}),p.window.requestAnimationFrame(x))}b.r(d);a=b(2);a=b.n(a);var p=b(3);b.n(p);var n=p.window.navigator,D=-1<n.userAgent.indexOf("MSIE ")||-1<
n.userAgent.indexOf("Trident/")||-1<n.userAgent.indexOf("Edge/"),C=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(n.userAgent),w=function(){for(var a=["transform","WebkitTransform","MozTransform"],f=document.createElement("div"),h=0;h<a.length;h+=1)if(f&&void 0!==f.style[a[h]])return a[h];return!1}(),t,k;u();p.window.addEventListener("resize",u);p.window.addEventListener("orientationchange",u);p.window.addEventListener("load",u);a()(function(){u({type:"dom-loaded"})});var r=
[],y=0,z=function(){function a(f,h){if(!(this instanceof a))throw new TypeError("Cannot call a class as a function");var c=this;c.instanceID=y;y+=1;c.$item=f;c.defaults={type:"scroll",speed:.5,imgSrc:null,imgElement:".jarallax-img",imgSize:"cover",imgPosition:"50% 50%",imgRepeat:"no-repeat",keepImg:!1,elementInViewport:null,zIndex:-100,disableParallax:!1,disableVideo:!1,videoSrc:null,videoStartTime:0,videoEndTime:0,videoVolume:0,videoLoop:!0,videoPlayOnlyVisible:!0,videoLazyLoading:!0,onScroll:null,
onInit:null,onDestroy:null,onCoverImage:null};var b=c.$item.dataset||{},d={};Object.keys(b).forEach(function(a){var f=a.substr(0,1).toLowerCase()+a.substr(1);f&&"undefined"!==typeof c.defaults[f]&&(d[f]=b[a])});c.options=c.extend({},c.defaults,d,h);c.pureOptions=c.extend({},c.options);Object.keys(c.options).forEach(function(a){"true"===c.options[a]?c.options[a]=!0:"false"===c.options[a]&&(c.options[a]=!1)});c.options.speed=Math.min(2,Math.max(-1,parseFloat(c.options.speed)));"string"===typeof c.options.disableParallax&&
(c.options.disableParallax=new RegExp(c.options.disableParallax));if(c.options.disableParallax instanceof RegExp){var g=c.options.disableParallax;c.options.disableParallax=function(){return g.test(n.userAgent)}}"function"!==typeof c.options.disableParallax&&(c.options.disableParallax=function(){return!1});"string"===typeof c.options.disableVideo&&(c.options.disableVideo=new RegExp(c.options.disableVideo));if(c.options.disableVideo instanceof RegExp){var m=c.options.disableVideo;c.options.disableVideo=
function(){return m.test(n.userAgent)}}"function"!==typeof c.options.disableVideo&&(c.options.disableVideo=function(){return!1});(f=c.options.elementInViewport)&&"object"===l(f)&&"undefined"!==typeof f.length&&(f=e(f,1)[0]);f instanceof Element||(f=null);c.options.elementInViewport=f;c.image={src:c.options.imgSrc||null,$container:null,useImgTag:!1,position:/iPad|iPhone|iPod|Android/.test(n.userAgent)?"absolute":"fixed"};c.initImg()&&c.canInitParallax()&&c.init()}v(a,[{key:"css",value:function(a,b){if("string"===
typeof b)return p.window.getComputedStyle(a).getPropertyValue(b);b.transform&&w&&(b[w]=b.transform);Object.keys(b).forEach(function(c){a.style[c]=b[c]});return a}},{key:"extend",value:function(a){for(var b=arguments.length,c=Array(1<b?b-1:0),f=1;f<b;f++)c[f-1]=arguments[f];a=a||{};Object.keys(c).forEach(function(b){c[b]&&Object.keys(c[b]).forEach(function(f){a[f]=c[b][f]})});return a}},{key:"getWindowData",value:function(){return{width:p.window.innerWidth||document.documentElement.clientWidth,height:k,
y:document.documentElement.scrollTop}}},{key:"initImg",value:function(){var a=this.options.imgElement;a&&"string"===typeof a&&(a=this.$item.querySelector(a));a instanceof Element||(this.options.imgSrc?(a=new Image,a.src=this.options.imgSrc):a=null);a&&(this.options.keepImg?this.image.$item=a.cloneNode(!0):(this.image.$item=a,this.image.$itemParent=a.parentNode),this.image.useImgTag=!0);if(this.image.$item)return!0;null===this.image.src&&(this.image.src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
this.image.bgImage=this.css(this.$item,"background-image"));return!(!this.image.bgImage||"none"===this.image.bgImage)}},{key:"canInitParallax",value:function(){return w&&!this.options.disableParallax()}},{key:"init",value:function(){var a={position:"absolute",top:0,left:0,width:"100%",height:"100%",overflow:"hidden"},b={pointerEvents:"none",transformStyle:"preserve-3d",backfaceVisibility:"hidden",willChange:"transform,opacity"};if(!this.options.keepImg){var c=this.$item.getAttribute("style");c&&this.$item.setAttribute("data-jarallax-original-styles",
c);this.image.useImgTag&&(c=this.image.$item.getAttribute("style"))&&this.image.$item.setAttribute("data-jarallax-original-styles",c)}"static"===this.css(this.$item,"position")&&this.css(this.$item,{position:"relative"});"auto"===this.css(this.$item,"z-index")&&this.css(this.$item,{zIndex:0});this.image.$container=document.createElement("div");this.css(this.image.$container,a);this.css(this.image.$container,{"z-index":this.options.zIndex});D&&this.css(this.image.$container,{opacity:.9999});this.image.$container.setAttribute("id",
"jarallax-container-".concat(this.instanceID));this.$item.appendChild(this.image.$container);this.image.useImgTag?b=this.extend({"object-fit":this.options.imgSize,"object-position":this.options.imgPosition,"font-family":"object-fit: ".concat(this.options.imgSize,"; object-position: ").concat(this.options.imgPosition,";"),"max-width":"none"},a,b):(this.image.$item=document.createElement("div"),this.image.src&&(b=this.extend({"background-position":this.options.imgPosition,"background-size":this.options.imgSize,
"background-repeat":this.options.imgRepeat,"background-image":this.image.bgImage||'url("'.concat(this.image.src,'")')},a,b)));if("opacity"===this.options.type||"scale"===this.options.type||"scale-opacity"===this.options.type||1===this.options.speed)this.image.position="absolute";"fixed"===this.image.position&&(a=A(this.$item).filter(function(a){a=p.window.getComputedStyle(a);var c=a["-webkit-transform"]||a["-moz-transform"]||a.transform,b=/(auto|scroll)/;return c&&"none"!==c||b.test(a.overflow+a["overflow-y"]+
a["overflow-x"])}),this.image.position=a.length?"absolute":"fixed");b.position=this.image.position;this.css(this.image.$item,b);this.image.$container.appendChild(this.image.$item);this.onResize();this.onScroll(!0);this.options.onInit&&this.options.onInit.call(this);"none"!==this.css(this.$item,"background-image")&&this.css(this.$item,{"background-image":"none"});this.addToParallaxList()}},{key:"addToParallaxList",value:function(){r.push({instance:this});1===r.length&&p.window.requestAnimationFrame(x)}},
{key:"removeFromParallaxList",value:function(){var a=this;r.forEach(function(b,c){b.instance.instanceID===a.instanceID&&r.splice(c,1)})}},{key:"destroy",value:function(){this.removeFromParallaxList();var a=this.$item.getAttribute("data-jarallax-original-styles");this.$item.removeAttribute("data-jarallax-original-styles");a?this.$item.setAttribute("style",a):this.$item.removeAttribute("style");if(this.image.useImgTag){var b=this.image.$item.getAttribute("data-jarallax-original-styles");this.image.$item.removeAttribute("data-jarallax-original-styles");
b?this.image.$item.setAttribute("style",a):this.image.$item.removeAttribute("style");this.image.$itemParent&&this.image.$itemParent.appendChild(this.image.$item)}this.$clipStyles&&this.$clipStyles.parentNode.removeChild(this.$clipStyles);this.image.$container&&this.image.$container.parentNode.removeChild(this.image.$container);this.options.onDestroy&&this.options.onDestroy.call(this);delete this.$item.jarallax}},{key:"clipContainer",value:function(){if("fixed"===this.image.position){var a=this.image.$container.getBoundingClientRect(),
b=a.width;a=a.height;this.$clipStyles||(this.$clipStyles=document.createElement("style"),this.$clipStyles.setAttribute("type","text/css"),this.$clipStyles.setAttribute("id","jarallax-clip-".concat(this.instanceID)),(document.head||document.getElementsByTagName("head")[0]).appendChild(this.$clipStyles));b="#jarallax-container-".concat(this.instanceID," {\n clip: rect(0 ").concat(b,"px ").concat(a,"px 0);\n clip: rect(0, ").concat(b,"px, ").concat(a,"px, 0);\n -webkit-clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);\n clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);\n }");
this.$clipStyles.styleSheet?this.$clipStyles.styleSheet.cssText=b:this.$clipStyles.innerHTML=b}}},{key:"coverImage",value:function(){var a=this.image.$container.getBoundingClientRect(),b=a.height,c=this.options.speed,d="scroll"===this.options.type||"scroll-opacity"===this.options.type,e=0,g=b;d&&(0>c?(e=c*Math.max(b,k),k<b&&(e-=c*(b-k))):e=c*(b+k),g=1<c?Math.abs(e-k):0>c?e/c+Math.abs(e):g+(k-b)*(1-c),e/=2);this.parallaxScrollDistance=e;b=d?(k-g)/2:(b-g)/2;this.css(this.image.$item,{height:"".concat(g,
"px"),marginTop:"".concat(b,"px"),left:"fixed"===this.image.position?"".concat(a.left,"px"):"0",width:"".concat(a.width,"px")});this.options.onCoverImage&&this.options.onCoverImage.call(this);return{image:{height:g,marginTop:b},container:a}}},{key:"isVisible",value:function(){return this.isElementInViewport||!1}},{key:"onScroll",value:function(a){var b=this.$item.getBoundingClientRect(),c=b.top,d=b.height,f={},e=b;this.options.elementInViewport&&(e=this.options.elementInViewport.getBoundingClientRect());
this.isElementInViewport=0<=e.bottom&&0<=e.right&&e.top<=k&&e.left<=p.window.innerWidth;if(a||this.isElementInViewport){a=Math.max(0,c);e=Math.max(0,d+c);var g=Math.max(0,-c),m=Math.max(0,c+d-k),l=Math.max(0,d-(c+d-k)),q=Math.max(0,-c+k-d),r=1-(k-c)/(k+d)*2,n=1;d<k?n=1-(g||m)/d:e<=k?n=e/k:l<=k&&(n=l/k);if("opacity"===this.options.type||"scale-opacity"===this.options.type||"scroll-opacity"===this.options.type)f.transform="translate3d(0,0,0)",f.opacity=n;if("scale"===this.options.type||"scale-opacity"===
this.options.type)d=1,d=0>this.options.speed?d-this.options.speed*n:d+this.options.speed*(1-n),f.transform="scale(".concat(d,") translate3d(0,0,0)");if("scroll"===this.options.type||"scroll-opacity"===this.options.type)d=this.parallaxScrollDistance*r,"absolute"===this.image.position&&(d-=c),f.transform="translate3d(0,".concat(d,"px,0)");this.css(this.image.$item,f);this.options.onScroll&&this.options.onScroll.call(this,{section:b,beforeTop:a,beforeTopEnd:e,afterTop:g,beforeBottom:m,beforeBottomEnd:l,
afterBottom:q,visiblePercent:n,fromViewportCenter:r})}}},{key:"onResize",value:function(){this.coverImage();this.clipContainer()}}]);return a}();b=function(a,b){if("object"===("undefined"===typeof HTMLElement?"undefined":l(HTMLElement))?a instanceof HTMLElement:a&&"object"===l(a)&&null!==a&&1===a.nodeType&&"string"===typeof a.nodeName)a=[a];for(var d=a.length,c=0,e,f=arguments.length,g=Array(2<f?f-2:0),k=2;k<f;k++)g[k-2]=arguments[k];for(c;c<d;c+=1)if("object"===l(b)||"undefined"===typeof b?a[c].jarallax||
(a[c].jarallax=new z(a[c],b)):a[c].jarallax&&(e=a[c].jarallax[b].apply(a[c].jarallax,g)),"undefined"!==typeof e)return e;return a};b.constructor=z;d["default"]=b}]);

View File

@ -0,0 +1,15 @@
(function(){function C(){if(!D&&document.body){D=!0;var a=document.body,b=document.documentElement,d=window.innerHeight,c=a.scrollHeight;l=0<=document.compatMode.indexOf("CSS")?b:a;m=a;f.keyboardSupport&&window.addEventListener("keydown",M,!1);if(top!=self)v=!0;else if(ca&&c>d&&(a.offsetHeight<=d||b.offsetHeight<=d)){var e=document.createElement("div");e.style.cssText="position:absolute; z-index:-10000; top:0; left:0; right:0; height:"+l.scrollHeight+"px";document.body.appendChild(e);var g;w=function(){g||
(g=setTimeout(function(){e.style.height="0";e.style.height=l.scrollHeight+"px";g=null},500))};setTimeout(w,10);window.addEventListener("resize",w,!1);z=new da(w);z.observe(a,{attributes:!0,childList:!0,characterData:!1});l.offsetHeight<=d&&(d=document.createElement("div"),d.style.clear="both",a.appendChild(d))}f.fixedBackground||(a.style.backgroundAttachment="scroll",b.style.backgroundAttachment="scroll")}}function N(a,b,d){ea(b,d);if(1!=f.accelerationMax){var c=Date.now()-E;c<f.accelerationDelta&&
(c=(1+50/c)/2,1<c&&(c=Math.min(c,f.accelerationMax),b*=c,d*=c));E=Date.now()}t.push({x:b,y:d,lastX:0>b?.99:-.99,lastY:0>d?.99:-.99,start:Date.now()});if(!F){c=O();var e=a===c||a===document.body;null==a.$scrollBehavior&&fa(a)&&(a.$scrollBehavior=a.style.scrollBehavior,a.style.scrollBehavior="auto");var g=function(c){c=Date.now();for(var k=0,l=0,h=0;h<t.length;h++){var n=t[h],p=c-n.start,m=p>=f.animationTime,q=m?1:p/f.animationTime;f.pulseAlgorithm&&(p=q,1<=p?q=1:0>=p?q=0:(1==f.pulseNormalize&&(f.pulseNormalize/=
P(1)),q=P(p)));p=n.x*q-n.lastX>>0;q=n.y*q-n.lastY>>0;k+=p;l+=q;n.lastX+=p;n.lastY+=q;m&&(t.splice(h,1),h--)}e?window.scrollBy(k,l):(k&&(a.scrollLeft+=k),l&&(a.scrollTop+=l));b||d||(t=[]);t.length?Q(g,a,1E3/f.frameRate+1):(F=!1,null!=a.$scrollBehavior&&(a.style.scrollBehavior=a.$scrollBehavior,a.$scrollBehavior=null))};Q(g,a,0);F=!0}}function R(a){D||C();var b=a.target;if(a.defaultPrevented||a.ctrlKey||r(m,"embed")||r(b,"embed")&&/\.pdf/i.test(b.src)||r(m,"object")||b.shadowRoot)return!0;var d=-a.wheelDeltaX||
a.deltaX||0,c=-a.wheelDeltaY||a.deltaY||0;ha&&(a.wheelDeltaX&&x(a.wheelDeltaX,120)&&(d=a.wheelDeltaX/Math.abs(a.wheelDeltaX)*-120),a.wheelDeltaY&&x(a.wheelDeltaY,120)&&(c=a.wheelDeltaY/Math.abs(a.wheelDeltaY)*-120));d||c||(c=-a.wheelDelta||0);1===a.deltaMode&&(d*=40,c*=40);b=S(b);if(!b)return v&&G?(Object.defineProperty(a,"target",{value:window.frameElement}),parent.wheel(a)):!0;if(ia(c))return!0;1.2<Math.abs(d)&&(d*=f.stepSize/120);1.2<Math.abs(c)&&(c*=f.stepSize/120);N(b,d,c);a.preventDefault();
T()}function M(a){var b=a.target,d=a.ctrlKey||a.altKey||a.metaKey||a.shiftKey&&a.keyCode!==g.spacebar;document.body.contains(m)||(m=document.activeElement);var c=/^(textarea|select|embed|object)$/i,e=/^(button|submit|radio|checkbox|file|color|image)$/i;if(!(c=a.defaultPrevented||c.test(b.nodeName)||r(b,"input")&&!e.test(b.type)||r(m,"video"))){c=a.target;var h=!1;if(-1!=document.URL.indexOf("www.youtube.com/watch")){do if(h=c.classList&&c.classList.contains("html5-video-controls"))break;while(c=c.parentNode)
}c=h}if(c||b.isContentEditable||d||(r(b,"button")||r(b,"input")&&e.test(b.type))&&a.keyCode===g.spacebar||r(b,"input")&&"radio"==b.type&&ja[a.keyCode])return!0;c=b=0;d=S(m);if(!d)return v&&G?parent.keydown(a):!0;e=d.clientHeight;d==document.body&&(e=window.innerHeight);switch(a.keyCode){case g.up:c=-f.arrowScroll;break;case g.down:c=f.arrowScroll;break;case g.spacebar:c=a.shiftKey?1:-1;c=-c*e*.9;break;case g.pageup:c=.9*-e;break;case g.pagedown:c=.9*e;break;case g.home:d==document.body&&document.scrollingElement&&
(d=document.scrollingElement);c=-d.scrollTop;break;case g.end:e=d.scrollHeight-d.scrollTop-e;c=0<e?e+10:0;break;case g.left:b=-f.arrowScroll;break;case g.right:b=f.arrowScroll;break;default:return!0}N(d,b,c);a.preventDefault();T()}function U(a){m=a.target}function T(){clearTimeout(V);V=setInterval(function(){W=H=A={}},1E3)}function I(a,b,d){d=d?W:H;for(var c=a.length;c--;)d[J(a[c])]=b;return b}function S(a){var b=[],d=document.body,c=l.scrollHeight;do{var e=H[J(a)];if(e)return I(b,e);b.push(a);if(c===
a.scrollHeight){if(e=X(l)&&X(d)||Y(l),v&&l.clientHeight+10<l.scrollHeight||!v&&e)return I(b,O())}else if(a.clientHeight+10<a.scrollHeight&&Y(a))return I(b,a)}while(a=a.parentElement)}function X(a){return"hidden"!==getComputedStyle(a,"").getPropertyValue("overflow-y")}function Y(a){a=getComputedStyle(a,"").getPropertyValue("overflow-y");return"scroll"===a||"auto"===a}function fa(a){var b=J(a);null==A[b]&&(a=getComputedStyle(a,"")["scroll-behavior"],A[b]="smooth"==a);return A[b]}function r(a,b){return a&&
(a.nodeName||"").toLowerCase()===b.toLowerCase()}function ea(a,b){a=0<a?1:-1;b=0<b?1:-1;if(B.x!==a||B.y!==b)B.x=a,B.y=b,t=[],E=0}function ia(a){if(a){h.length||(h=[a,a,a]);a=Math.abs(a);h.push(a);h.shift();clearTimeout(Z);Z=setTimeout(function(){try{localStorage.SS_deltaBuffer=h.join(",")}catch(d){}},1E3);var b=120<a&&K(a);b=!K(120)&&!K(100)&&!b;return 50>a?!0:b}}function x(a,b){return Math.floor(a/b)==a/b}function K(a){return x(h[0],a)&&x(h[1],a)&&x(h[2],a)}function P(a){a*=f.pulseScale;if(1>a)var b=
a-(1-Math.exp(-a));else b=Math.exp(-1),a=1-Math.exp(-(a-1)),b+=a*(1-b);return b*f.pulseNormalize}function y(a){for(var b in a)aa.hasOwnProperty(b)&&(f[b]=a[b])}var aa={frameRate:150,animationTime:400,stepSize:100,pulseAlgorithm:!0,pulseScale:4,pulseNormalize:1,accelerationDelta:50,accelerationMax:3,keyboardSupport:!0,arrowScroll:50,fixedBackground:!0,excluded:""},f=aa,v=!1,B={x:0,y:0},D=!1,l=document.documentElement,m,z,w,h=[],Z,ha=/^Mac/.test(navigator.platform),g={left:37,up:38,right:39,down:40,
spacebar:32,pageup:33,pagedown:34,end:35,home:36},ja={37:1,38:1,39:1,40:1},t=[],F=!1,E=Date.now(),J=function(){var a=0;return function(b){return b.uniqueID||(b.uniqueID=a++)}}(),W={},H={},V,A={};if(window.localStorage&&localStorage.SS_deltaBuffer)try{h=localStorage.SS_deltaBuffer.split(",")}catch(a){}var Q=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(a,b,d){window.setTimeout(a,d||1E3/60)}}(),da=window.MutationObserver||
window.WebKitMutationObserver||window.MozMutationObserver,O=function(){var a=document.scrollingElement;return function(){if(!a){var b=document.createElement("div");b.style.cssText="height:10000px;width:1px;";document.body.appendChild(b);var d=document.body.scrollTop;window.scrollBy(0,3);a=document.body.scrollTop!=d?document.body:document.documentElement;window.scrollBy(0,-3);document.body.removeChild(b)}return a}}(),k=window.navigator.userAgent,u=/Edge/.test(k),G=/chrome/i.test(k)&&!u;u=/safari/i.test(k)&&
!u;var ka=/mobile/i.test(k),la=/Windows NT 6.1/i.test(k)&&/rv:11/i.test(k),ca=u&&(/Version\/8/i.test(k)||/Version\/9/i.test(k));k=(G||u||la)&&!ka;var ba=!1;try{window.addEventListener("test",null,Object.defineProperty({},"passive",{get:function(){ba=!0}}))}catch(a){}u=ba?{passive:!1}:!1;var L="onwheel"in document.createElement("div")?"wheel":"mousewheel";L&&k&&(window.addEventListener(L,R,u||!1),window.addEventListener("mousedown",U,!1),window.addEventListener("load",C,!1));y.destroy=function(){z&&
z.disconnect();window.removeEventListener(L,R,!1);window.removeEventListener("mousedown",U,!1);window.removeEventListener("keydown",M,!1);window.removeEventListener("resize",w,!1);window.removeEventListener("load",C,!1)};window.SmoothScrollOptions&&y(window.SmoothScrollOptions);"function"===typeof define&&define.amd?define(function(){return y}):"object"==typeof exports?module.exports=y:window.SmoothScroll=y})();

View File

@ -0,0 +1,934 @@
@charset "UTF-8";
@font-face {
font-family: 'Socicon';
src: url('../fonts/socicon.eot');
src: url('../fonts/socicon.eot?#iefix') format('embedded-opentype'),
url('../fonts/socicon.woff2') format('woff2'),
url('../fonts/socicon.ttf') format('truetype'),
url('../fonts/socicon.woff') format('woff'),
url('../fonts/socicon.svg#socicon') format('svg');
font-weight: normal;
font-style: normal;
font-display: swap;
}
[data-icon]:before {
font-family: "socicon" !important;
content: attr(data-icon);
font-style: normal !important;
font-weight: normal !important;
font-variant: normal !important;
text-transform: none !important;
speak: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
[class^="socicon-"], [class*=" socicon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'Socicon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.socicon-eitaa:before {
content: "\e97c";
}
.socicon-soroush:before {
content: "\e97d";
}
.socicon-bale:before {
content: "\e97e";
}
.socicon-zazzle:before {
content: "\e97b";
}
.socicon-society6:before {
content: "\e97a";
}
.socicon-redbubble:before {
content: "\e979";
}
.socicon-avvo:before {
content: "\e978";
}
.socicon-stitcher:before {
content: "\e977";
}
.socicon-googlehangouts:before {
content: "\e974";
}
.socicon-dlive:before {
content: "\e975";
}
.socicon-vsco:before {
content: "\e976";
}
.socicon-flipboard:before {
content: "\e973";
}
.socicon-ubuntu:before {
content: "\e958";
}
.socicon-artstation:before {
content: "\e959";
}
.socicon-invision:before {
content: "\e95a";
}
.socicon-torial:before {
content: "\e95b";
}
.socicon-collectorz:before {
content: "\e95c";
}
.socicon-seenthis:before {
content: "\e95d";
}
.socicon-googleplaymusic:before {
content: "\e95e";
}
.socicon-debian:before {
content: "\e95f";
}
.socicon-filmfreeway:before {
content: "\e960";
}
.socicon-gnome:before {
content: "\e961";
}
.socicon-itchio:before {
content: "\e962";
}
.socicon-jamendo:before {
content: "\e963";
}
.socicon-mix:before {
content: "\e964";
}
.socicon-sharepoint:before {
content: "\e965";
}
.socicon-tinder:before {
content: "\e966";
}
.socicon-windguru:before {
content: "\e967";
}
.socicon-cdbaby:before {
content: "\e968";
}
.socicon-elementaryos:before {
content: "\e969";
}
.socicon-stage32:before {
content: "\e96a";
}
.socicon-tiktok:before {
content: "\e96b";
}
.socicon-gitter:before {
content: "\e96c";
}
.socicon-letterboxd:before {
content: "\e96d";
}
.socicon-threema:before {
content: "\e96e";
}
.socicon-splice:before {
content: "\e96f";
}
.socicon-metapop:before {
content: "\e970";
}
.socicon-naver:before {
content: "\e971";
}
.socicon-remote:before {
content: "\e972";
}
.socicon-internet:before {
content: "\e957";
}
.socicon-moddb:before {
content: "\e94b";
}
.socicon-indiedb:before {
content: "\e94c";
}
.socicon-traxsource:before {
content: "\e94d";
}
.socicon-gamefor:before {
content: "\e94e";
}
.socicon-pixiv:before {
content: "\e94f";
}
.socicon-myanimelist:before {
content: "\e950";
}
.socicon-blackberry:before {
content: "\e951";
}
.socicon-wickr:before {
content: "\e952";
}
.socicon-spip:before {
content: "\e953";
}
.socicon-napster:before {
content: "\e954";
}
.socicon-beatport:before {
content: "\e955";
}
.socicon-hackerone:before {
content: "\e956";
}
.socicon-hackernews:before {
content: "\e946";
}
.socicon-smashwords:before {
content: "\e947";
}
.socicon-kobo:before {
content: "\e948";
}
.socicon-bookbub:before {
content: "\e949";
}
.socicon-mailru:before {
content: "\e94a";
}
.socicon-gitlab:before {
content: "\e945";
}
.socicon-instructables:before {
content: "\e944";
}
.socicon-portfolio:before {
content: "\e943";
}
.socicon-codered:before {
content: "\e940";
}
.socicon-origin:before {
content: "\e941";
}
.socicon-nextdoor:before {
content: "\e942";
}
.socicon-udemy:before {
content: "\e93f";
}
.socicon-livemaster:before {
content: "\e93e";
}
.socicon-crunchbase:before {
content: "\e93b";
}
.socicon-homefy:before {
content: "\e93c";
}
.socicon-calendly:before {
content: "\e93d";
}
.socicon-realtor:before {
content: "\e90f";
}
.socicon-tidal:before {
content: "\e910";
}
.socicon-qobuz:before {
content: "\e911";
}
.socicon-natgeo:before {
content: "\e912";
}
.socicon-mastodon:before {
content: "\e913";
}
.socicon-unsplash:before {
content: "\e914";
}
.socicon-homeadvisor:before {
content: "\e915";
}
.socicon-angieslist:before {
content: "\e916";
}
.socicon-codepen:before {
content: "\e917";
}
.socicon-slack:before {
content: "\e918";
}
.socicon-openaigym:before {
content: "\e919";
}
.socicon-logmein:before {
content: "\e91a";
}
.socicon-fiverr:before {
content: "\e91b";
}
.socicon-gotomeeting:before {
content: "\e91c";
}
.socicon-aliexpress:before {
content: "\e91d";
}
.socicon-guru:before {
content: "\e91e";
}
.socicon-appstore:before {
content: "\e91f";
}
.socicon-homes:before {
content: "\e920";
}
.socicon-zoom:before {
content: "\e921";
}
.socicon-alibaba:before {
content: "\e922";
}
.socicon-craigslist:before {
content: "\e923";
}
.socicon-wix:before {
content: "\e924";
}
.socicon-redfin:before {
content: "\e925";
}
.socicon-googlecalendar:before {
content: "\e926";
}
.socicon-shopify:before {
content: "\e927";
}
.socicon-freelancer:before {
content: "\e928";
}
.socicon-seedrs:before {
content: "\e929";
}
.socicon-bing:before {
content: "\e92a";
}
.socicon-doodle:before {
content: "\e92b";
}
.socicon-bonanza:before {
content: "\e92c";
}
.socicon-squarespace:before {
content: "\e92d";
}
.socicon-toptal:before {
content: "\e92e";
}
.socicon-gust:before {
content: "\e92f";
}
.socicon-ask:before {
content: "\e930";
}
.socicon-trulia:before {
content: "\e931";
}
.socicon-loomly:before {
content: "\e932";
}
.socicon-ghost:before {
content: "\e933";
}
.socicon-upwork:before {
content: "\e934";
}
.socicon-fundable:before {
content: "\e935";
}
.socicon-booking:before {
content: "\e936";
}
.socicon-googlemaps:before {
content: "\e937";
}
.socicon-zillow:before {
content: "\e938";
}
.socicon-niconico:before {
content: "\e939";
}
.socicon-toneden:before {
content: "\e93a";
}
.socicon-augment:before {
content: "\e908";
}
.socicon-bitbucket:before {
content: "\e909";
}
.socicon-fyuse:before {
content: "\e90a";
}
.socicon-yt-gaming:before {
content: "\e90b";
}
.socicon-sketchfab:before {
content: "\e90c";
}
.socicon-mobcrush:before {
content: "\e90d";
}
.socicon-microsoft:before {
content: "\e90e";
}
.socicon-pandora:before {
content: "\e907";
}
.socicon-messenger:before {
content: "\e906";
}
.socicon-gamewisp:before {
content: "\e905";
}
.socicon-bloglovin:before {
content: "\e904";
}
.socicon-tunein:before {
content: "\e903";
}
.socicon-gamejolt:before {
content: "\e901";
}
.socicon-trello:before {
content: "\e902";
}
.socicon-spreadshirt:before {
content: "\e900";
}
.socicon-500px:before {
content: "\e000";
}
.socicon-8tracks:before {
content: "\e001";
}
.socicon-airbnb:before {
content: "\e002";
}
.socicon-alliance:before {
content: "\e003";
}
.socicon-amazon:before {
content: "\e004";
}
.socicon-amplement:before {
content: "\e005";
}
.socicon-android:before {
content: "\e006";
}
.socicon-angellist:before {
content: "\e007";
}
.socicon-apple:before {
content: "\e008";
}
.socicon-appnet:before {
content: "\e009";
}
.socicon-baidu:before {
content: "\e00a";
}
.socicon-bandcamp:before {
content: "\e00b";
}
.socicon-battlenet:before {
content: "\e00c";
}
.socicon-mixer:before {
content: "\e00d";
}
.socicon-bebee:before {
content: "\e00e";
}
.socicon-bebo:before {
content: "\e00f";
}
.socicon-behance:before {
content: "\e010";
}
.socicon-blizzard:before {
content: "\e011";
}
.socicon-blogger:before {
content: "\e012";
}
.socicon-buffer:before {
content: "\e013";
}
.socicon-chrome:before {
content: "\e014";
}
.socicon-coderwall:before {
content: "\e015";
}
.socicon-curse:before {
content: "\e016";
}
.socicon-dailymotion:before {
content: "\e017";
}
.socicon-deezer:before {
content: "\e018";
}
.socicon-delicious:before {
content: "\e019";
}
.socicon-deviantart:before {
content: "\e01a";
}
.socicon-diablo:before {
content: "\e01b";
}
.socicon-digg:before {
content: "\e01c";
}
.socicon-discord:before {
content: "\e01d";
}
.socicon-disqus:before {
content: "\e01e";
}
.socicon-douban:before {
content: "\e01f";
}
.socicon-draugiem:before {
content: "\e020";
}
.socicon-dribbble:before {
content: "\e021";
}
.socicon-drupal:before {
content: "\e022";
}
.socicon-ebay:before {
content: "\e023";
}
.socicon-ello:before {
content: "\e024";
}
.socicon-endomodo:before {
content: "\e025";
}
.socicon-envato:before {
content: "\e026";
}
.socicon-etsy:before {
content: "\e027";
}
.socicon-facebook:before {
content: "\e028";
}
.socicon-feedburner:before {
content: "\e029";
}
.socicon-filmweb:before {
content: "\e02a";
}
.socicon-firefox:before {
content: "\e02b";
}
.socicon-flattr:before {
content: "\e02c";
}
.socicon-flickr:before {
content: "\e02d";
}
.socicon-formulr:before {
content: "\e02e";
}
.socicon-forrst:before {
content: "\e02f";
}
.socicon-foursquare:before {
content: "\e030";
}
.socicon-friendfeed:before {
content: "\e031";
}
.socicon-github:before {
content: "\e032";
}
.socicon-goodreads:before {
content: "\e033";
}
.socicon-google:before {
content: "\e034";
}
.socicon-googlescholar:before {
content: "\e035";
}
.socicon-googlegroups:before {
content: "\e036";
}
.socicon-googlephotos:before {
content: "\e037";
}
.socicon-googleplus:before {
content: "\e038";
}
.socicon-grooveshark:before {
content: "\e039";
}
.socicon-hackerrank:before {
content: "\e03a";
}
.socicon-hearthstone:before {
content: "\e03b";
}
.socicon-hellocoton:before {
content: "\e03c";
}
.socicon-heroes:before {
content: "\e03d";
}
.socicon-smashcast:before {
content: "\e03e";
}
.socicon-horde:before {
content: "\e03f";
}
.socicon-houzz:before {
content: "\e040";
}
.socicon-icq:before {
content: "\e041";
}
.socicon-identica:before {
content: "\e042";
}
.socicon-imdb:before {
content: "\e043";
}
.socicon-instagram:before {
content: "\e044";
}
.socicon-issuu:before {
content: "\e045";
}
.socicon-istock:before {
content: "\e046";
}
.socicon-itunes:before {
content: "\e047";
}
.socicon-keybase:before {
content: "\e048";
}
.socicon-lanyrd:before {
content: "\e049";
}
.socicon-lastfm:before {
content: "\e04a";
}
.socicon-line:before {
content: "\e04b";
}
.socicon-linkedin:before {
content: "\e04c";
}
.socicon-livejournal:before {
content: "\e04d";
}
.socicon-lyft:before {
content: "\e04e";
}
.socicon-macos:before {
content: "\e04f";
}
.socicon-mail:before {
content: "\e050";
}
.socicon-medium:before {
content: "\e051";
}
.socicon-meetup:before {
content: "\e052";
}
.socicon-mixcloud:before {
content: "\e053";
}
.socicon-modelmayhem:before {
content: "\e054";
}
.socicon-mumble:before {
content: "\e055";
}
.socicon-myspace:before {
content: "\e056";
}
.socicon-newsvine:before {
content: "\e057";
}
.socicon-nintendo:before {
content: "\e058";
}
.socicon-npm:before {
content: "\e059";
}
.socicon-odnoklassniki:before {
content: "\e05a";
}
.socicon-openid:before {
content: "\e05b";
}
.socicon-opera:before {
content: "\e05c";
}
.socicon-outlook:before {
content: "\e05d";
}
.socicon-overwatch:before {
content: "\e05e";
}
.socicon-patreon:before {
content: "\e05f";
}
.socicon-paypal:before {
content: "\e060";
}
.socicon-periscope:before {
content: "\e061";
}
.socicon-persona:before {
content: "\e062";
}
.socicon-pinterest:before {
content: "\e063";
}
.socicon-play:before {
content: "\e064";
}
.socicon-player:before {
content: "\e065";
}
.socicon-playstation:before {
content: "\e066";
}
.socicon-pocket:before {
content: "\e067";
}
.socicon-qq:before {
content: "\e068";
}
.socicon-quora:before {
content: "\e069";
}
.socicon-raidcall:before {
content: "\e06a";
}
.socicon-ravelry:before {
content: "\e06b";
}
.socicon-reddit:before {
content: "\e06c";
}
.socicon-renren:before {
content: "\e06d";
}
.socicon-researchgate:before {
content: "\e06e";
}
.socicon-residentadvisor:before {
content: "\e06f";
}
.socicon-reverbnation:before {
content: "\e070";
}
.socicon-rss:before {
content: "\e071";
}
.socicon-sharethis:before {
content: "\e072";
}
.socicon-skype:before {
content: "\e073";
}
.socicon-slideshare:before {
content: "\e074";
}
.socicon-smugmug:before {
content: "\e075";
}
.socicon-snapchat:before {
content: "\e076";
}
.socicon-songkick:before {
content: "\e077";
}
.socicon-soundcloud:before {
content: "\e078";
}
.socicon-spotify:before {
content: "\e079";
}
.socicon-stackexchange:before {
content: "\e07a";
}
.socicon-stackoverflow:before {
content: "\e07b";
}
.socicon-starcraft:before {
content: "\e07c";
}
.socicon-stayfriends:before {
content: "\e07d";
}
.socicon-steam:before {
content: "\e07e";
}
.socicon-storehouse:before {
content: "\e07f";
}
.socicon-strava:before {
content: "\e080";
}
.socicon-streamjar:before {
content: "\e081";
}
.socicon-stumbleupon:before {
content: "\e082";
}
.socicon-swarm:before {
content: "\e083";
}
.socicon-teamspeak:before {
content: "\e084";
}
.socicon-teamviewer:before {
content: "\e085";
}
.socicon-technorati:before {
content: "\e086";
}
.socicon-telegram:before {
content: "\e087";
}
.socicon-tripadvisor:before {
content: "\e088";
}
.socicon-tripit:before {
content: "\e089";
}
.socicon-triplej:before {
content: "\e08a";
}
.socicon-tumblr:before {
content: "\e08b";
}
.socicon-twitch:before {
content: "\e08c";
}
.socicon-twitter:before {
content: "\e08d";
}
.socicon-uber:before {
content: "\e08e";
}
.socicon-ventrilo:before {
content: "\e08f";
}
.socicon-viadeo:before {
content: "\e090";
}
.socicon-viber:before {
content: "\e091";
}
.socicon-viewbug:before {
content: "\e092";
}
.socicon-vimeo:before {
content: "\e093";
}
.socicon-vine:before {
content: "\e094";
}
.socicon-vkontakte:before {
content: "\e095";
}
.socicon-warcraft:before {
content: "\e096";
}
.socicon-wechat:before {
content: "\e097";
}
.socicon-weibo:before {
content: "\e098";
}
.socicon-whatsapp:before {
content: "\e099";
}
.socicon-wikipedia:before {
content: "\e09a";
}
.socicon-windows:before {
content: "\e09b";
}
.socicon-wordpress:before {
content: "\e09c";
}
.socicon-wykop:before {
content: "\e09d";
}
.socicon-xbox:before {
content: "\e09e";
}
.socicon-xing:before {
content: "\e09f";
}
.socicon-yahoo:before {
content: "\e0a0";
}
.socicon-yammer:before {
content: "\e0a1";
}
.socicon-yandex:before {
content: "\e0a2";
}
.socicon-yelp:before {
content: "\e0a3";
}
.socicon-younow:before {
content: "\e0a4";
}
.socicon-youtube:before {
content: "\e0a5";
}
.socicon-zapier:before {
content: "\e0a6";
}
.socicon-zerply:before {
content: "\e0a7";
}
.socicon-zomato:before {
content: "\e0a8";
}
.socicon-zynga:before {
content: "\e0a9";
}

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,965 @@
@charset "UTF-8";
section {
background-color: #ffffff;
}
body {
font-style: normal;
line-height: 1.5;
font-weight: 400;
color: #232323;
position: relative;
}
button {
background-color: transparent;
border-color: transparent;
}
.embla__button,
.carousel-control {
background-color: #edefea !important;
opacity: 0.8 !important;
color: #464845 !important;
border-color: #edefea !important;
}
.carousel .close,
.modalWindow .close {
background-color: #edefea !important;
color: #464845 !important;
border-color: #edefea !important;
opacity: 0.8 !important;
}
.carousel .close:hover,
.modalWindow .close:hover {
opacity: 1 !important;
}
.carousel-indicators li {
background-color: #edefea !important;
border: 2px solid #464845 !important;
}
.carousel-indicators li:hover,
.carousel-indicators li:active {
opacity: 0.8 !important;
}
.embla__button:hover,
.carousel-control:hover {
background-color: #edefea !important;
opacity: 1 !important;
}
.modalWindow-video-container {
height: 80%;
}
section,
.container,
.container-fluid {
position: relative;
word-wrap: break-word;
}
a.mbr-iconfont:hover {
text-decoration: none;
}
.article .lead p,
.article .lead ul,
.article .lead ol,
.article .lead pre,
.article .lead blockquote {
margin-bottom: 0;
}
a {
font-style: normal;
font-weight: 400;
cursor: pointer;
}
a, a:hover {
text-decoration: none;
}
.mbr-section-title {
font-style: normal;
line-height: 1.3;
}
.mbr-section-subtitle {
line-height: 1.3;
}
.mbr-text {
font-style: normal;
line-height: 1.7;
}
h1,
h2,
h3,
h4,
h5,
h6,
.display-1,
.display-2,
.display-4,
.display-5,
.display-7,
span,
p,
a {
line-height: 1;
word-break: break-word;
word-wrap: break-word;
font-weight: 400;
}
b,
strong {
font-weight: bold;
}
input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, input:-webkit-autofill:active {
transition-delay: 9999s;
-webkit-transition-property: background-color, color;
transition-property: background-color, color;
}
textarea[type=hidden] {
display: none;
}
section {
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;
}
section .mbr-background-video,
section .mbr-background-video-preview {
position: absolute;
bottom: 0;
left: 0;
right: 0;
top: 0;
}
.hidden {
visibility: hidden;
}
.mbr-z-index20 {
z-index: 20;
}
/*! Base colors */
.mbr-white {
color: #ffffff;
}
.mbr-black {
color: #111111;
}
.mbr-bg-white {
background-color: #ffffff;
}
.mbr-bg-black {
background-color: #000000;
}
/*! Text-aligns */
.align-left {
text-align: left;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
/*! Font-weight */
.mbr-light {
font-weight: 300;
}
.mbr-regular {
font-weight: 400;
}
.mbr-semibold {
font-weight: 500;
}
.mbr-bold {
font-weight: 700;
}
/*! Media */
.media-content {
flex-basis: 100%;
}
.media-container-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-content: center;
align-items: start;
}
.media-container-row .media-size-item {
width: 400px;
}
.media-container-column {
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: center;
align-content: center;
align-items: stretch;
}
.media-container-column > * {
width: 100%;
}
@media (min-width: 992px) {
.media-container-row {
flex-wrap: nowrap;
}
}
figure {
margin-bottom: 0;
overflow: hidden;
}
figure[mbr-media-size] {
transition: width 0.1s;
}
img,
iframe {
display: block;
width: 100%;
}
.card {
background-color: transparent;
border: none;
}
.card-box {
width: 100%;
}
.card-img {
text-align: center;
flex-shrink: 0;
-webkit-flex-shrink: 0;
}
.media {
max-width: 100%;
margin: 0 auto;
}
.mbr-figure {
align-self: center;
}
.media-container > div {
max-width: 100%;
}
.mbr-figure img,
.card-img img {
width: 100%;
}
@media (max-width: 991px) {
.media-size-item {
width: auto !important;
}
.media {
width: auto;
}
.mbr-figure {
width: 100% !important;
}
}
/*! Buttons */
.mbr-section-btn {
margin-left: -0.6rem;
margin-right: -0.6rem;
font-size: 0;
}
.btn {
font-weight: 600;
border-width: 1px;
font-style: normal;
margin: 0.6rem 0.6rem;
white-space: normal;
transition: all 0.2s ease-in-out;
display: inline-flex;
align-items: center;
justify-content: center;
word-break: break-word;
}
.btn-sm {
font-weight: 600;
letter-spacing: 0px;
transition: all 0.3s ease-in-out;
}
.btn-md {
font-weight: 600;
letter-spacing: 0px;
transition: all 0.3s ease-in-out;
}
.btn-lg {
font-weight: 600;
letter-spacing: 0px;
transition: all 0.3s ease-in-out;
}
.btn-form {
margin: 0;
}
.btn-form:hover {
cursor: pointer;
}
nav .mbr-section-btn {
margin-left: 0rem;
margin-right: 0rem;
}
/*! Btn icon margin */
.btn .mbr-iconfont,
.btn.btn-sm .mbr-iconfont {
order: 1;
cursor: pointer;
margin-left: 0.5rem;
vertical-align: sub;
}
.btn.btn-md .mbr-iconfont,
.btn.btn-md .mbr-iconfont {
margin-left: 0.8rem;
}
.mbr-regular {
font-weight: 400;
}
.mbr-semibold {
font-weight: 500;
}
.mbr-bold {
font-weight: 700;
}
[type=submit] {
-webkit-appearance: none;
}
/*! Full-screen */
.mbr-fullscreen .mbr-overlay {
min-height: 100vh;
}
.mbr-fullscreen {
display: flex;
display: -moz-flex;
display: -ms-flex;
display: -o-flex;
align-items: center;
min-height: 100vh;
padding-top: 3rem;
padding-bottom: 3rem;
}
/*! Map */
.map {
height: 25rem;
position: relative;
}
.map iframe {
width: 100%;
height: 100%;
}
/*! Scroll to top arrow */
.mbr-arrow-up {
bottom: 25px;
right: 90px;
position: fixed;
text-align: right;
z-index: 5000;
color: #ffffff;
font-size: 22px;
}
.mbr-arrow-up a {
background: rgba(0, 0, 0, 0.2);
border-radius: 50%;
color: #fff;
display: inline-block;
height: 60px;
width: 60px;
border: 2px solid #fff;
outline-style: none !important;
position: relative;
text-decoration: none;
transition: all 0.3s ease-in-out;
cursor: pointer;
text-align: center;
}
.mbr-arrow-up a:hover {
background-color: rgba(0, 0, 0, 0.4);
}
.mbr-arrow-up a i {
line-height: 60px;
}
.mbr-arrow-up-icon {
display: block;
color: #fff;
}
.mbr-arrow-up-icon::before {
content: "";
display: inline-block;
font-family: serif;
font-size: 22px;
line-height: 1;
font-style: normal;
position: relative;
top: 6px;
left: -4px;
transform: rotate(-90deg);
}
/*! Arrow Down */
.mbr-arrow {
position: absolute;
bottom: 45px;
left: 50%;
width: 60px;
height: 60px;
cursor: pointer;
background-color: rgba(80, 80, 80, 0.5);
border-radius: 50%;
transform: translateX(-50%);
}
@media (max-width: 767px) {
.mbr-arrow {
display: none;
}
}
.mbr-arrow > a {
display: inline-block;
text-decoration: none;
outline-style: none;
animation: arrowdown 1.7s ease-in-out infinite;
color: #ffffff;
}
.mbr-arrow > a > i {
position: absolute;
top: -2px;
left: 15px;
font-size: 2rem;
}
#scrollToTop a i::before {
content: "";
position: absolute;
display: block;
border-bottom: 2.5px solid #fff;
border-left: 2.5px solid #fff;
width: 27.8%;
height: 27.8%;
left: 50%;
top: 51%;
transform: translateY(-30%) translateX(-50%) rotate(135deg);
}
@keyframes arrowdown {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-5px);
}
100% {
transform: translateY(0px);
}
}
@media (max-width: 500px) {
.mbr-arrow-up {
left: 0;
right: 0;
text-align: center;
}
}
/*Gradients animation*/
@keyframes gradient-animation {
from {
background-position: 0% 100%;
animation-timing-function: ease-in-out;
}
to {
background-position: 100% 0%;
animation-timing-function: ease-in-out;
}
}
.bg-gradient {
background-size: 200% 200%;
animation: gradient-animation 5s infinite alternate;
-webkit-animation: gradient-animation 5s infinite alternate;
}
.menu .navbar-brand {
display: -webkit-flex;
}
.menu .navbar-brand span {
display: flex;
display: -webkit-flex;
}
.menu .navbar-brand .navbar-caption-wrap {
display: -webkit-flex;
}
.menu .navbar-brand .navbar-logo img {
display: -webkit-flex;
width: auto;
}
@media (min-width: 768px) and (max-width: 991px) {
.menu .navbar-toggleable-sm .navbar-nav {
display: -ms-flexbox;
}
}
@media (max-width: 991px) {
.menu .navbar-collapse {
max-height: 93.5vh;
}
.menu .navbar-collapse.show {
overflow: auto;
}
}
@media (min-width: 992px) {
.menu .navbar-nav.nav-dropdown {
display: -webkit-flex;
}
.menu .navbar-toggleable-sm .navbar-collapse {
display: -webkit-flex !important;
}
.menu .collapsed .navbar-collapse {
max-height: 93.5vh;
}
.menu .collapsed .navbar-collapse.show {
overflow: auto;
}
}
@media (max-width: 767px) {
.menu .navbar-collapse {
max-height: 80vh;
}
}
.nav-link .mbr-iconfont {
margin-right: 0.5rem;
}
.navbar {
display: -webkit-flex;
-webkit-flex-wrap: wrap;
-webkit-align-items: center;
-webkit-justify-content: space-between;
}
.navbar-collapse {
-webkit-flex-basis: 100%;
-webkit-flex-grow: 1;
-webkit-align-items: center;
}
.nav-dropdown .link {
padding: 0.667em 1.667em !important;
margin: 0 !important;
}
.nav {
display: -webkit-flex;
-webkit-flex-wrap: wrap;
}
.row {
display: -webkit-flex;
-webkit-flex-wrap: wrap;
}
.justify-content-center {
-webkit-justify-content: center;
}
.form-inline {
display: -webkit-flex;
}
.card-wrapper {
-webkit-flex: 1;
}
.carousel-control {
z-index: 10;
display: -webkit-flex;
}
.carousel-controls {
display: -webkit-flex;
}
.media {
display: -webkit-flex;
}
.form-group:focus {
outline: none;
}
.jq-selectbox__select {
padding: 7px 0;
position: relative;
}
.jq-selectbox__dropdown {
overflow: hidden;
border-radius: 10px;
position: absolute;
top: 100%;
left: 0 !important;
width: 100% !important;
}
.jq-selectbox__trigger-arrow {
right: 0;
transform: translateY(-50%);
}
.jq-selectbox li {
padding: 1.07em 0.5em;
}
input[type=range] {
padding-left: 0 !important;
padding-right: 0 !important;
}
.modal-dialog,
.modal-content {
height: 100%;
}
.modal-dialog .carousel-inner {
height: calc(100vh - 1.75rem);
}
@media (max-width: 575px) {
.modal-dialog .carousel-inner {
height: calc(100vh - 1rem);
}
}
.carousel-item {
text-align: center;
}
.carousel-item img {
margin: auto;
}
.navbar-toggler {
align-self: flex-start;
padding: 0.25rem 0.75rem;
font-size: 1.25rem;
line-height: 1;
background: transparent;
border: 1px solid transparent;
border-radius: 0.25rem;
}
.navbar-toggler:focus,
.navbar-toggler:hover {
text-decoration: none;
box-shadow: none;
}
.navbar-toggler-icon {
display: inline-block;
width: 1.5em;
height: 1.5em;
vertical-align: middle;
content: "";
background: no-repeat center center;
background-size: 100% 100%;
}
.navbar-toggler-left {
position: absolute;
left: 1rem;
}
.navbar-toggler-right {
position: absolute;
right: 1rem;
}
.card-img {
width: auto;
}
.menu .navbar.collapsed:not(.beta-menu) {
flex-direction: column;
}
.carousel-item.active,
.carousel-item-next,
.carousel-item-prev {
display: flex;
}
.note-air-layout .dropup .dropdown-menu,
.note-air-layout .navbar-fixed-bottom .dropdown .dropdown-menu {
bottom: initial !important;
}
html,
body {
height: auto;
min-height: 100vh;
}
.dropup .dropdown-toggle::after {
display: none;
}
.form-asterisk {
font-family: initial;
position: absolute;
top: -2px;
font-weight: normal;
}
.form-control-label {
position: relative;
cursor: pointer;
margin-bottom: 0.357em;
padding: 0;
}
.alert {
color: #ffffff;
border-radius: 0;
border: 0;
font-size: 1.1rem;
line-height: 1.5;
margin-bottom: 1.875rem;
padding: 1.25rem;
position: relative;
text-align: center;
}
.alert.alert-form::after {
background-color: inherit;
bottom: -7px;
content: "";
display: block;
height: 14px;
left: 50%;
margin-left: -7px;
position: absolute;
transform: rotate(45deg);
width: 14px;
}
.form-control {
background-color: #ffffff;
background-clip: border-box;
color: #232323;
line-height: 1rem !important;
height: auto;
padding: 1.2rem 2rem;
transition: border-color 0.25s ease 0s;
border: 1px solid transparent !important;
border-radius: 4px;
box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 1px 0px, rgba(0, 0, 0, 0.07) 0px 1px 3px 0px, rgba(0, 0, 0, 0.03) 0px 0px 0px 1px;
}
.form-active .form-control:invalid {
border-color: red;
}
.row > * {
padding-right: 1rem;
padding-left: 1rem;
}
form .row {
margin-left: -0.6rem;
margin-right: -0.6rem;
}
form .row [class*=col] {
padding-left: 0.6rem;
padding-right: 0.6rem;
}
form .mbr-section-btn {
padding-left: 0.6rem;
padding-right: 0.6rem;
}
form .form-check-input {
margin-top: 0.5;
}
textarea.form-control {
line-height: 1.5rem !important;
}
.form-group {
margin-bottom: 1.2rem;
}
.form-control,
form .btn {
min-height: 48px;
}
.gdpr-block label span.textGDPR input[name=gdpr] {
top: 7px;
}
.form-control:focus {
box-shadow: none;
}
:focus {
outline: none;
}
.mbr-overlay {
background-color: #000;
bottom: 0;
left: 0;
opacity: 0.5;
position: absolute;
right: 0;
top: 0;
z-index: 0;
pointer-events: none;
}
blockquote {
font-style: italic;
padding: 3rem;
font-size: 1.09rem;
position: relative;
border-left: 3px solid;
}
ul,
ol,
pre,
blockquote {
margin-bottom: 2.3125rem;
}
.mt-4 {
margin-top: 2rem !important;
}
.mb-4 {
margin-bottom: 2rem !important;
}
.container,
.container-fluid {
padding-left: 16px;
padding-right: 16px;
}
.row {
margin-left: -16px;
margin-right: -16px;
}
.row > [class*=col] {
padding-left: 16px;
padding-right: 16px;
}
@media (min-width: 992px) {
.container-fluid {
padding-left: 32px;
padding-right: 32px;
}
}
@media (max-width: 991px) {
.mbr-container {
padding-left: 16px;
padding-right: 16px;
}
}
.app-video-wrapper > img {
opacity: 1;
}
.app-video-wrapper {
background: transparent;
}
.item {
position: relative;
}
.dropdown-menu .dropdown-menu {
left: 100%;
}
.dropdown-item + .dropdown-menu {
display: none;
}
.dropdown-item:hover + .dropdown-menu,
.dropdown-menu:hover {
display: block;
}
@media (min-aspect-ratio: 16/9) {
.mbr-video-foreground {
height: 300% !important;
top: -100% !important;
}
}
@media (max-aspect-ratio: 16/9) {
.mbr-video-foreground {
width: 300% !important;
left: -100% !important;
}
}.engine {
position: absolute;
text-indent: -2400px;
text-align: center;
padding: 0;
top: 0;
left: -2400px;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,70 @@
/*
yt-player. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}};$jscomp.arrayIterator=function(a){return{next:$jscomp.arrayIteratorImpl(a)}};$jscomp.makeIterator=function(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):$jscomp.arrayIterator(a)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;
$jscomp.objectCreate=$jscomp.ASSUME_ES5||"function"==typeof Object.create?Object.create:function(a){var b=function(){};b.prototype=a;return new b};$jscomp.underscoreProtoCanBeSet=function(){var a={a:!0},b={};try{return b.__proto__=a,b.a}catch(c){}return!1};$jscomp.setPrototypeOf="function"==typeof Object.setPrototypeOf?Object.setPrototypeOf:$jscomp.underscoreProtoCanBeSet()?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null;
$jscomp.inherits=function(a,b){a.prototype=$jscomp.objectCreate(b.prototype);a.prototype.constructor=a;if($jscomp.setPrototypeOf){var c=$jscomp.setPrototypeOf;c(a,b)}else for(c in b)if("prototype"!=c)if(Object.defineProperties){var d=Object.getOwnPropertyDescriptor(b,c);d&&Object.defineProperty(a,c,d)}else a[c]=b[c];a.superClass_=b.prototype};$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)};$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in c||(c[e]={});c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};$jscomp.FORCE_POLYFILL_PROMISE=!1;
$jscomp.polyfill("Promise",function(a){function b(){this.batch_=null}function c(a){return a instanceof e?a:new e(function(b,c){b(a)})}if(a&&!$jscomp.FORCE_POLYFILL_PROMISE)return a;b.prototype.asyncExecute=function(a){if(null==this.batch_){this.batch_=[];var b=this;this.asyncExecuteFunction(function(){b.executeBatch_()})}this.batch_.push(a)};var d=$jscomp.global.setTimeout;b.prototype.asyncExecuteFunction=function(a){d(a,0)};b.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var a=
this.batch_;this.batch_=[];for(var b=0;b<a.length;++b){var c=a[b];a[b]=null;try{c()}catch(l){this.asyncThrow_(l)}}}this.batch_=null};b.prototype.asyncThrow_=function(a){this.asyncExecuteFunction(function(){throw a;})};var e=function(a){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];var b=this.createResolveAndReject_();try{a(b.resolve,b.reject)}catch(h){b.reject(h)}};e.prototype.createResolveAndReject_=function(){function a(a){return function(d){c||(c=!0,a.call(b,d))}}var b=this,c=!1;
return{resolve:a(this.resolveTo_),reject:a(this.reject_)}};e.prototype.resolveTo_=function(a){if(a===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(a instanceof e)this.settleSameAsPromise_(a);else{a:switch(typeof a){case "object":var b=null!=a;break a;case "function":b=!0;break a;default:b=!1}b?this.resolveToNonPromiseObj_(a):this.fulfill_(a)}};e.prototype.resolveToNonPromiseObj_=function(a){var b=void 0;try{b=a.then}catch(h){this.reject_(h);return}"function"==typeof b?
this.settleSameAsThenable_(b,a):this.fulfill_(a)};e.prototype.reject_=function(a){this.settle_(2,a)};e.prototype.fulfill_=function(a){this.settle_(1,a)};e.prototype.settle_=function(a,b){if(0!=this.state_)throw Error("Cannot settle("+a+", "+b+"): Promise already settled in state"+this.state_);this.state_=a;this.result_=b;this.executeOnSettledCallbacks_()};e.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var a=0;a<this.onSettledCallbacks_.length;++a)f.asyncExecute(this.onSettledCallbacks_[a]);
this.onSettledCallbacks_=null}};var f=new b;e.prototype.settleSameAsPromise_=function(a){var b=this.createResolveAndReject_();a.callWhenSettled_(b.resolve,b.reject)};e.prototype.settleSameAsThenable_=function(a,b){var c=this.createResolveAndReject_();try{a.call(b,c.resolve,c.reject)}catch(l){c.reject(l)}};e.prototype.then=function(a,b){function c(a,b){return"function"==typeof a?function(b){try{d(a(b))}catch(m){f(m)}}:b}var d,f,g=new e(function(a,b){d=a;f=b});this.callWhenSettled_(c(a,d),c(b,f));return g};
e.prototype.catch=function(a){return this.then(void 0,a)};e.prototype.callWhenSettled_=function(a,b){function c(){switch(d.state_){case 1:a(d.result_);break;case 2:b(d.result_);break;default:throw Error("Unexpected state: "+d.state_);}}var d=this;null==this.onSettledCallbacks_?f.asyncExecute(c):this.onSettledCallbacks_.push(c)};e.resolve=c;e.reject=function(a){return new e(function(b,c){c(a)})};e.race=function(a){return new e(function(b,d){for(var e=$jscomp.makeIterator(a),f=e.next();!f.done;f=e.next())c(f.value).callWhenSettled_(b,
d)})};e.all=function(a){var b=$jscomp.makeIterator(a),d=b.next();return d.done?c([]):new e(function(a,e){function f(b){return function(c){g[b]=c;h--;0==h&&a(g)}}var g=[],h=0;do g.push(void 0),h++,c(d.value).callWhenSettled_(f(g.length-1),e),d=b.next();while(!d.done)})};return e},"es6","es3");$jscomp.owns=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};
$jscomp.polyfill("Object.entries",function(a){return a?a:function(a){var b=[],d;for(d in a)$jscomp.owns(a,d)&&b.push([d,a[d]]);return b}},"es8","es3");$jscomp.assign="function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(d)for(var e in d)$jscomp.owns(d,e)&&(a[e]=d[e])}return a};$jscomp.polyfill("Object.assign",function(a){return a||$jscomp.assign},"es6","es3");
$jscomp.findInternal=function(a,b,c){a instanceof String&&(a=String(a));for(var d=a.length,e=0;e<d;e++){var f=a[e];if(b.call(c,f,e,a))return{i:e,v:f}}return{i:-1,v:void 0}};$jscomp.polyfill("Array.prototype.find",function(a){return a?a:function(a,c){return $jscomp.findInternal(this,a,c).v}},"es6","es3");$jscomp.SYMBOL_PREFIX="jscomp_symbol_";$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};
$jscomp.SymbolClass=function(a,b){this.$jscomp$symbol$id_=a;$jscomp.defineProperty(this,"description",{configurable:!0,writable:!0,value:b})};$jscomp.SymbolClass.prototype.toString=function(){return this.$jscomp$symbol$id_};$jscomp.Symbol=function(){function a(c){if(this instanceof a)throw new TypeError("Symbol is not a constructor");return new $jscomp.SymbolClass($jscomp.SYMBOL_PREFIX+(c||"")+"_"+b++,c)}var b=0;return a}();
$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.iterator;a||(a=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("Symbol.iterator"));"function"!=typeof Array.prototype[a]&&$jscomp.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return $jscomp.iteratorPrototype($jscomp.arrayIteratorImpl(this))}});$jscomp.initSymbolIterator=function(){}};
$jscomp.initSymbolAsyncIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.asyncIterator;a||(a=$jscomp.global.Symbol.asyncIterator=$jscomp.global.Symbol("Symbol.asyncIterator"));$jscomp.initSymbolAsyncIterator=function(){}};$jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[$jscomp.global.Symbol.iterator]=function(){return this};return a};
$jscomp.iteratorFromArray=function(a,b){$jscomp.initSymbolIterator();a instanceof String&&(a+="");var c=0,d={next:function(){if(c<a.length){var e=c++;return{value:b(e,a[e]),done:!1}}d.next=function(){return{done:!0,value:void 0}};return d.next()}};d[Symbol.iterator]=function(){return d};return d};$jscomp.polyfill("Array.prototype.entries",function(a){return a?a:function(){return $jscomp.iteratorFromArray(this,function(a,c){return[a,c]})}},"es6","es3");
$jscomp.polyfill("Array.from",function(a){return a?a:function(a,c,d){c=null!=c?c:function(a){return a};var b=[],f="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];if("function"==typeof f){a=f.call(a);for(var g=0;!(f=a.next()).done;)b.push(c.call(d,f.value,g++))}else for(f=a.length,g=0;g<f;g++)b.push(c.call(d,a[g],g));return b}},"es6","es3");$jscomp.polyfill("Object.is",function(a){return a?a:function(a,c){return a===c?0!==a||1/a===1/c:a!==a&&c!==c}},"es6","es3");
$jscomp.polyfill("Array.prototype.includes",function(a){return a?a:function(a,c){var b=this;b instanceof String&&(b=String(b));var e=b.length;c=c||0;for(0>c&&(c=Math.max(c+e,0));c<e;c++){var f=b[c];if(f===a||Object.is(f,a))return!0}return!1}},"es7","es3");
$jscomp.checkStringArgs=function(a,b,c){if(null==a)throw new TypeError("The 'this' value for String.prototype."+c+" must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype."+c+" must not be a regular expression");return a+""};$jscomp.polyfill("String.prototype.includes",function(a){return a?a:function(a,c){return-1!==$jscomp.checkStringArgs(this,a,"includes").indexOf(a,c||0)}},"es6","es3");var EventEmitter=function(){this.events={}};
EventEmitter.prototype.on=function(a,b){"object"!==typeof this.events[a]&&(this.events[a]=[]);this.events[a].push(b)};EventEmitter.prototype.removeListener=function(a,b){"object"===typeof this.events[a]&&(b=this.indexOf(this.events[a],b),-1<b&&this.events[a].splice(b,1))};EventEmitter.prototype.emit=function(a){var b,c=[].slice.call(arguments,1);if("object"===typeof this.events[a]){var d=this.events[a].slice();var e=d.length;for(b=0;b<e;b++)d[b].apply(this,c)}};
EventEmitter.prototype.once=function(a,b){this.on(a,function d(){this.removeListener(a,d);b.apply(this,arguments)})};
var loadScript=function(a,b,c){return new Promise(function(d,e){var f=document.createElement("script");f.async=!0;f.src=a;for(var g=$jscomp.makeIterator(Object.entries(b||{})),k=g.next();!k.done;k=g.next()){var h=$jscomp.makeIterator(k.value);k=h.next().value;h=h.next().value;f.setAttribute(k,h)}f.onload=function(){f.onerror=f.onload=null;d(f)};f.onerror=function(){f.onerror=f.onload=null;e(Error("Failed to load "+a))};(c||document.head||document.getElementsByTagName("head")[0]).appendChild(f)})},
YOUTUBE_IFRAME_API_SRC="https://www.youtube.com/iframe_api",YOUTUBE_STATES={"-1":"unstarted",0:"ended",1:"playing",2:"paused",3:"buffering",5:"cued"},YOUTUBE_ERROR={INVALID_PARAM:2,HTML5_ERROR:5,NOT_FOUND:100,UNPLAYABLE_1:101,UNPLAYABLE_2:150},loadIframeAPICallbacks=[],C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0=function(a,b){EventEmitter.call(this);var c=this;a="string"===typeof a?
document.querySelector(a):a;this._id=a.id?a.id:a.id="ytplayer-"+Math.random().toString(16).slice(2,8);this._opts=Object.assign({width:640,height:360,autoplay:!1,captions:void 0,controls:!0,keyboard:!0,fullscreen:!0,annotations:!0,modestBranding:!1,related:!0,timeupdateFrequency:1E3,playsInline:!0,start:0},b);this.videoId=null;this.destroyed=!1;this._api=null;this._autoplay=!1;this._player=null;this._ready=!1;this._queue=[];this.replayInterval=[];this._interval=null;this._startInterval=this._startInterval.bind(this);
this._stopInterval=this._stopInterval.bind(this);this.on("playing",this._startInterval);this.on("unstarted",this._stopInterval);this.on("ended",this._stopInterval);this.on("paused",this._stopInterval);this.on("buffering",this._stopInterval);this._loadIframeAPI(function(a,b){if(a)return c._destroy(Error("YouTube Iframe API failed to load"));c._api=b;c.videoId&&c.load(c.videoId,c._autoplay,c._start)})};
$jscomp.inherits(C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0,EventEmitter);C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.indexOf=function(a,b){for(var c=0,d=a.length,e=-1,f=!1;c<d&&!f;)a[c]===b&&(e=c,f=!0),c++;return e};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.load=function(a,b,c){b=void 0===b?!1:b;c=void 0===c?0:c;this.destroyed||(this._startOptimizeDisplayEvent(),this._optimizeDisplayHandler("center, center"),this.videoId=a,this._autoplay=b,this._start=c,this._api&&(this._player?this._ready&&(b?this._player.loadVideoById(a,c):this._player.cueVideoById(a,c)):this._createPlayer(a)))};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.play=function(){this._ready?this._player.playVideo():this._queueCommand("play")};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.replayFrom=function(a){var b=this;!this.replayInterval.find(function(a){return a.iframeParent===b._player.i.parentNode})&&a&&this.replayInterval.push({iframeParent:this._player.i.parentNode,interval:setInterval(function(){if(b._player.getCurrentTime()>=b._player.getDuration()-Number(a)){b.seek(0);for(var c=$jscomp.makeIterator(b.replayInterval.entries()),
d=c.next();!d.done;d=c.next()){d=$jscomp.makeIterator(d.value);var e=d.next().value;d.next();Object.hasOwnProperty.call(b.replayInterval,e)&&(clearInterval(b.replayInterval[e].interval),b.replayInterval.splice(e,1))}}},1E3*Number(a))})};C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.pause=function(){this._ready?this._player.pauseVideo():this._queueCommand("pause")};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.stop=function(){this._ready?this._player.stopVideo():this._queueCommand("stop")};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.seek=function(a){this._ready?this._player.seekTo(a,!0):this._queueCommand("seek",a)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._optimizeDisplayHandler=function(a){if(this._player){var b=this._player.i;a=a.split(",");if(b){var c;if(c=b.parentElement){var d=window.getComputedStyle(c);var e=c.clientHeight+parseFloat(d.marginTop,10)+parseFloat(d.marginBottom,10)+parseFloat(d.borderTopWidth,10)+parseFloat(d.borderBottomWidth,10);c=c.clientWidth+parseFloat(d.marginLeft,
10)+parseFloat(d.marginRight,10)+parseFloat(d.borderLeftWidth,10)+parseFloat(d.borderRightWidth,10);e+=80;b.style.width=c+"px";b.style.height=Math.ceil(parseFloat(b.style.width,10)/1.7)+"px";b.style.marginTop=Math.ceil(-((parseFloat(b.style.height,10)-e)/2))+"px";b.style.marginLeft=0;if(d=parseFloat(b.style.height,10)<e)b.style.height=e+"px",b.style.width=Math.ceil(1.7*parseFloat(b.style.height,10))+"px",b.style.marginTop=0,b.style.marginLeft=Math.ceil(-((parseFloat(b.style.width,10)-c)/2))+"px";
for(var f in a)if(a.hasOwnProperty(f))switch(a[f].replace(/ /g,"")){case "top":b.style.marginTop=d?-((parseFloat(b.style.height,10)-e)/2)+"px":0;break;case "bottom":b.style.marginTop=d?0:-(parseFloat(b.style.height,10)-e)+"px";break;case "left":b.style.marginLeft=0;break;case "right":b.style.marginLeft=d?-(parseFloat(b.style.width,10)-c):"0px";break;default:parseFloat(b.style.width,10)>c&&(b.style.marginLeft=-((parseFloat(b.style.width,10)-c)/2)+"px")}}}}};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.stopResize=function(){window.removeEventListener("resize",this._resizeListener);this._resizeListener=null};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.stopReplay=function(a){for(var b=$jscomp.makeIterator(this.replayInterval.entries()),c=b.next();!c.done;c=b.next()){c=$jscomp.makeIterator(c.value);var d=c.next().value;c.next();Object.hasOwnProperty.call(this.replayInterval,d)&&a===this.replayInterval[d].iframeParent&&(clearInterval(this.replayInterval[d].interval),this.replayInterval.splice(d,
1))}};C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.setVolume=function(a){this._ready?this._player.setVolume(a):this._queueCommand("setVolume",a)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.loadPlaylist=function(){this._ready?this._player.loadPlaylist(this.videoId):this._queueCommand("loadPlaylist",this.videoId)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.setLoop=function(a){this._ready?this._player.setLoop(a):this._queueCommand("setLoop",a)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.getVolume=function(){return this._ready&&this._player.getVolume()||0};C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.mute=function(){this._ready?this._player.mute():this._queueCommand("mute")};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.unMute=function(){this._ready?this._player.unMute():this._queueCommand("unMute")};C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.isMuted=function(){return this._ready&&this._player.isMuted()||!1};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.setSize=function(a,b){this._ready?this._player.setSize(a,b):this._queueCommand("setSize",a,b)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.setPlaybackRate=function(a){this._ready?this._player.setPlaybackRate(a):this._queueCommand("setPlaybackRate",a)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.setPlaybackQuality=function(a){this._ready?this._player.setPlaybackQuality(a):this._queueCommand("setPlaybackQuality",a)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.getPlaybackRate=function(){return this._ready&&this._player.getPlaybackRate()||1};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.getAvailablePlaybackRates=function(){return this._ready&&this._player.getAvailablePlaybackRates()||[1]};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.getDuration=function(){return this._ready&&this._player.getDuration()||0};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.getProgress=function(){return this._ready&&this._player.getVideoLoadedFraction()||0};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.getState=function(){return this._ready&&YOUTUBE_STATES[this._player.getPlayerState()]||"unstarted"};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.getCurrentTime=function(){return this._ready&&this._player.getCurrentTime()||0};C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype.destroy=function(){this._destroy()};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._destroy=function(a){this.destroyed||(this.destroyed=!0,this._player&&(this._player.stopVideo&&this._player.stopVideo(),this._player.destroy()),this._player=this._api=this._opts=this._id=this.videoId=null,this._ready=!1,this._queue=null,this._stopInterval(),this.removeListener("playing",this._startInterval),this.removeListener("paused",
this._stopInterval),this.removeListener("buffering",this._stopInterval),this.removeListener("unstarted",this._stopInterval),this.removeListener("ended",this._stopInterval),a&&this.emit("error",a))};C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._queueCommand=function(a,b){for(var c=[],d=1;d<arguments.length;++d)c[d-1]=arguments[d];this.destroyed||this._queue.push([a,c])};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._flushQueue=function(){for(;this._queue.length;){var a=this._queue.shift();this[a[0]].apply(this,a[1])}};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._loadIframeAPI=function(a){if(window.YT&&"function"===typeof window.YT.Player)return a(null,window.YT);loadIframeAPICallbacks.push(a);Array.from(document.getElementsByTagName("script")).some(function(a){return a.src===YOUTUBE_IFRAME_API_SRC})||loadScript(YOUTUBE_IFRAME_API_SRC).catch(function(a){for(;loadIframeAPICallbacks.length;)loadIframeAPICallbacks.shift()(a)});
var b=window.onYouTubeIframeAPIReady;window.onYouTubeIframeAPIReady=function(){for("function"===typeof b&&b();loadIframeAPICallbacks.length;)loadIframeAPICallbacks.shift()(null,window.YT)}};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._createPlayer=function(a){var b=this;if(!this.destroyed){var c=this._opts;this._player=new this._api.Player(this._id,{width:c.width,height:c.height,videoId:a,host:c.host,playerVars:{autoplay:c.autoplay?1:0,mute:c.mute?1:0,hl:null!=c.captions&&!1!==c.captions?c.captions:void 0,cc_lang_pref:null!=c.captions&&!1!==c.captions?c.captions:
void 0,controls:c.controls?2:0,enablejsapi:1,allowfullscreen:!0,iv_load_policy:c.annotations?1:3,modestbranding:c.modestBranding?1:0,origin:"*",rel:c.related?1:0,mode:"transparent",showinfo:0,html5:1,version:3,playerapiid:"iframe_YTP_1624972482514"},events:{onReady:function(){return b._onReady(a)},onStateChange:function(a){return b._onStateChange(a)},onPlaybackQualityChange:function(a){return b._onPlaybackQualityChange(a)},onPlaybackRateChange:function(a){return b._onPlaybackRateChange(a)},onError:function(a){return b._onError(a)}}})}};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._onReady=function(a){this.destroyed||(this._ready=!0,this.load(this.videoId,this._autoplay,this._start),this._flushQueue())};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._onStateChange=function(a){if(!this.destroyed){var b=YOUTUBE_STATES[a.data];if(b)["paused","buffering","ended"].includes(b)&&this._onTimeupdate(),this.emit(b),["unstarted","playing","cued"].includes(b)&&this._onTimeupdate();else throw Error("Unrecognized state change: "+a);}};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._onPlaybackQualityChange=function(a){this.destroyed||this.emit("playbackQualityChange",a.data)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._onPlaybackRateChange=function(a){this.destroyed||this.emit("playbackRateChange",a.data)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._onError=function(a){if(!this.destroyed&&(a=a.data,a!==YOUTUBE_ERROR.HTML5_ERROR)){if(a===YOUTUBE_ERROR.UNPLAYABLE_1||a===YOUTUBE_ERROR.UNPLAYABLE_2||a===YOUTUBE_ERROR.NOT_FOUND||a===YOUTUBE_ERROR.INVALID_PARAM)return this.emit("unplayable",this.videoId);this._destroy(Error("YouTube Player Error. Unknown error code: "+a))}};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._startOptimizeDisplayEvent=function(){var a=this;this._resizeListener||(this._resizeListener=function(){return a._optimizeDisplayHandler("center, center")},window.addEventListener("resize",this._resizeListener))};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._onTimeupdate=function(){this.emit("timeupdate",this.getCurrentTime())};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._startInterval=function(){var a=this;this._interval=setInterval(function(){return a._onTimeupdate()},this._opts.timeupdateFrequency)};
C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0.prototype._stopInterval=function(){clearInterval(this._interval);this._interval=null};YouTubePlayer=C_$hudson$workspace$Mobirise_Windows_release_web$Release$release$win_ia32_unpacked$resources$_app_asar$web$app$themes$startm5$plugins$ytplayer$index$classdecl$var0;

View File

@ -17,8 +17,8 @@
<% } %>
<div class="input-box">
<span class="details">Username</span>
<input type="text" name="usernameOrEmail" placeholder="Enter your email" required>
<span class="details">Username or Email</span>
<input type="text" name="usernameOrEmail" placeholder="Enter your Username or Email" required>
</div>
<div class="button">

View File

@ -20,16 +20,49 @@
<nav>
<a href="/inusers">In-House Users</a>
<a href="#">Users</a>
<a href="#">Sensors</a>
<a href="/sensors">Sensors</a>
<a href="/locations">Locations</a>
<a href="/apilog">Api Logs</a>
<a href="/logout">Logout</a>
</nav>
<main>
<h2>Welcome to the Home Page, <%= username %>!</h2>
</main>
<section>
<div class="health-container">
<h4>Server Uptime</h4>
<ul>
<li><strong>Uptime:</strong> <span id="serverUptime"><%= systemHealth && systemHealth.serverStatus ? (systemHealth.serverStatus.uptime / 60).toFixed(2) : 'N/A' %> minutes</span></li>
</ul>
</div>
<div class="health-container">
<h4>Database Status</h4>
<ul>
<li><strong>Status:</strong> <%= systemHealth && systemHealth.databaseStatus ? (systemHealth.databaseStatus.connected ? 'Connected' : 'Disconnected') : 'N/A' %></li>
</ul>
</div>
<div class="health-container">
<h4>CPU Usage</h4>
<ul>
<li><strong>Usage:</strong> <span id="cpuUsage"><%= systemHealth && systemHealth.resourceUtilization ? systemHealth.resourceUtilization.cpuUsage.toFixed(2) : 'N/A' %> %</span></li>
</ul>
</div>
<div class="health-container">
<h4>Memory Usage</h4>
<ul>
<li><strong>Usage:</strong> <%= systemHealth && systemHealth.resourceUtilization ? (systemHealth.resourceUtilization.memoryUsage.rss / (1024 * 1024)).toFixed(2) : 'N/A' %> MB</li>
</ul>
</div>
</section>
<footer>
Any Issue faced, Please contact the administrator at 11111111 or ecosaverAdmin@gmail.com
</footer>
</body>
</html>

186
Sean/views/index.ejs Normal file
View File

@ -0,0 +1,186 @@
<!DOCTYPE html>
<html >
<head>
<!-- Site made with Mobirise Website Builder v5.9.13, https://mobirise.com -->
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="generator" content="Mobirise v5.9.13, mobirise.com">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="description" content="">
<title>Home</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-grid.min.css">
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-reboot.min.css">
<link rel="stylesheet" href="assets/parallax/jarallax.css">
<link rel="stylesheet" href="assets/animatecss/animate.css">
<link rel="stylesheet" href="assets/dropdown/css/style.css">
<link rel="stylesheet" href="assets/socicon/css/styles.css">
<link rel="stylesheet" href="assets/theme/css/style.css">
<link rel="preload" href="https://fonts.googleapis.com/css?family=Inter+Tight:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter+Tight:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap"></noscript>
<link rel="preload" as="style" href="assets/mobirise/css/mbr-additional.css?v=b1g2Yh"><link rel="stylesheet" href="assets/mobirise/css/mbr-additional.css?v=b1g2Yh" type="text/css">
</head>
<body>
<section data-bs-version="5.1" class="menu menu5 cid-u2mrL2wdLO" once="menu" id="menu05-1i">
<nav class="navbar navbar-dropdown navbar-fixed-top navbar-expand-lg">
<div class="container">
<div class="navbar-brand">
<span class="navbar-caption-wrap"><a class="navbar-caption text-black display-4" href="https://mobiri.se">EcoSaver Management</a></span>
</div>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-bs-toggle="collapse" data-target="#navbarSupportedContent" data-bs-target="#navbarSupportedContent" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<div class="hamburger">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav nav-dropdown" data-app-modern-menu="true">
<li class="nav-item">
<a class="nav-link link text-black display-4" href="index.html#header01-7">About</a>
</li>
<li class="nav-item">
<a class="nav-link link text-black display-4" href="index.html#features04-w" aria-expanded="false">Services</a>
</li>
<li class="nav-item">
<a class="nav-link link text-black display-4" href="index.html#contacts02-9">Contacts</a>
</li>
</ul>
<div class="navbar-buttons mbr-section-btn"><a class="btn btn-primary display-4" href="http://localhost:3000/login">Login</a></div>
</div>
</div>
</nav>
</section>
<section data-bs-version="5.1" class="header5 cid-tJS6uM4N87" id="header05-1">
<div class="topbg"></div>
<div class="align-center container">
<div class="row justify-content-center">
<div class="col-md-12 col-lg-9">
<h1 class="mbr-section-title mbr-fonts-style mb-4 display-1"><strong>WELCOME TO ECOSAVER MANAGEMENT</strong></h1>
<p class="mbr-text mbr-fonts-style mb-4 display-7">Here at ECOSAVER MANAGEMENT, we are tasked to secure and protect ECOSAVER to provide<br>consumers with the best experience</p>
</div>
</div>
<div class="row mt-5 justify-content-center">
<div class="col-12 col-lg-12">
<img src="assets/images/ecosostenibilita-1000x500.jpeg" alt="Mobirise Website Builder" title="">
</div>
</div>
</div>
</section>
<section data-bs-version="5.1" class="features4 cid-tMlEXTHLbS" id="features04-w">
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-12 content-head">
<div class="mbr-section-head mb-5">
<h4 class="mbr-section-title mbr-fonts-style align-center mb-0 display-2"><strong>Our Services</strong></h4>
<h5 class="mbr-section-subtitle mbr-fonts-style align-center mb-0 mt-4 display-7">At EcoSaver, we are committed to empowering individuals and businesses to embrace a greener, more sustainable lifestyle. We are designed to make eco-friendly choices accessible and effortless, ensuring a positive impact on both the environment and your bottom line.</h5>
</div>
</div>
</div>
<div class="row">
<div class="item features-image col-12 col-md-6 col-lg-6 active">
<div class="item-wrapper">
<div class="item-img">
<img src="assets/images/air-pollution-1679x944.jpg" alt="Mobirise Website Builder" data-slide-to="1" data-bs-slide-to="1">
</div>
<div class="item-content">
<h5 class="item-title mbr-fonts-style display-5"><strong>Ecosaver</strong></h5>
<p class="mbr-text mbr-fonts-style display-7">EcoSaver is proud to offer a cutting-edge Air Quality Service, providing real-time air quality data for Singapore. It allows anyone to access crucial information about the air they breathe, empowering individuals and communities to make informed decisions for their well-being.</p>
<div class="mbr-section-btn item-footer"><a href="" class="btn item-btn btn-primary display-7">Learn more</a></div>
</div>
</div>
</div><div class="item features-image col-12 col-md-6 col-lg-6">
<div class="item-wrapper">
<div class="item-img">
<img src="assets/images/the-expansion-of-cloud-applications-has-added-to...-766x476.png" alt="Mobirise Website Builder" data-slide-to="0" data-bs-slide-to="0">
</div>
<div class="item-content">
<h5 class="item-title mbr-fonts-style display-5"><strong>Ecosaver Management</strong></h5>
<p class="mbr-text mbr-fonts-style display-7">
EcoSaver Management is unwaveringly dedicated to delivering paramount security within the EcoSaver system. Our paramount objective is to establish and maintain a secure environment, ensuring the safety of every individual who engages with the EcoSaver platform.</p>
<div class="mbr-section-btn item-footer"><a href="" class="btn item-btn btn-primary display-7">Learn more</a></div>
</div>
</div>
</div>
</div>
</div>
</section>
<section data-bs-version="5.1" class="header1 cid-tJS9vXDdRK" id="header01-7">
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-md-12 col-lg-7 image-wrapper">
<img class="w-100" src="assets/images/gallery01.jpg" alt="Mobirise Website Builder">
</div>
<div class="col-12 col-lg col-md-12">
<div class="text-wrapper align-left">
<h1 class="mbr-section-title mbr-fonts-style mb-4 display-2"><strong>About us</strong></h1>
<p class="mbr-text mbr-fonts-style mb-4 display-7">
EcoSaver is made by 4 student of Temasek Polytechnic studying cyber security and digital forensics for their major project</p>
</div>
</div>
</div>
</div>
</section>
<section data-bs-version="5.1" class="contacts2 map1 cid-tLdYHD757A mbr-parallax-background" id="contacts02-9">
<div class="mbr-overlay" style="opacity: 0.5; background-color: rgb(0, 0, 0);"></div>
<div class="container-fluid">
<div class="mbr-section-head mb-5">
<h3 class="mbr-section-title mbr-fonts-style align-center mb-0 display-2">
<strong>Contacts</strong>
</h3>
</div>
<div class="row justify-content-center mt-4">
<div class="card col-12 col-md-5">
<div class="card-wrapper">
<div class="text-wrapper">
<h5 class="cardTitle mbr-fonts-style mb-2 display-5">
<strong>Get in touch</strong></h5>
<ul class="list mbr-fonts-style display-7">
<li class="mbr-text item-wrap"><span style="font-size: 1.4rem;">Phone: 6666 6666</span></li>
<li class="mbr-text item-wrap">Email: ecosaverx@gmail.com</li><li class="mbr-text item-wrap"><br></li>
<li class="mbr-text item-wrap">Address: </li><li class="mbr-text item-wrap">21 Tampines Ave 1, Singapore 529757</li><li class="mbr-text item-wrap"><span style="font-size: 1.4rem;">Working hours:</span><br></li><li class="mbr-text item-wrap">8:30AM - 6:00PM</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
<section data-bs-version="5.1" class="footer1 programm5 cid-tJS9NNcTLZ" once="footers" id="footer03-8">
<div class="container">
<div class="row">
<div class="row-links mb-4">
<ul class="header-menu">
<li class="header-menu-item mbr-fonts-style display-5"><a href="index.html#header01-7" class="text-danger">About</a></li><li class="header-menu-item mbr-fonts-style display-5"><a href="index.html#features04-w" class="text-danger text-primary">Services</a></li><li class="header-menu-item mbr-fonts-style display-5"><a href="index.html#contacts02-9" class="text-danger">Contacts</a></li></ul>
</div>
<div class="col-12">
</div>
<div class="col-12 mt-5">
<p class="mbr-fonts-style copyright display-7">
© Copyright 2030 Ecosaver - All Rights Reserved
</p>
</div>
</div>
</div>
</section><section class="display-7" style="padding: 0;align-items: center;justify-content: center;flex-wrap: wrap; align-content: center;display: flex;position: relative;height: 4rem;"><a href="https://mobiri.se/3136804" style="flex: 1 1;height: 4rem;position: absolute;width: 100%;z-index: 1;"><img alt="" style="height: 4rem;" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="></a><p style="margin: 0;text-align: center;" class="display-7">&#8204;</p><a style="z-index:1" href="https://mobirise.com/builder/ai-website-builder.html">AI Website Builder</a></section><script src="assets/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="assets/parallax/jarallax.js"></script> <script src="assets/smoothscroll/smooth-scroll.js"></script> <script src="assets/ytplayer/index.js"></script> <script src="assets/dropdown/js/navbar-dropdown.js"></script> <script src="assets/theme/js/script.js"></script>
<input name="animation" type="hidden">
</body>
</html>

View File

@ -173,21 +173,19 @@
</div>
<script nonce="<%= nonce %>">
<script >
const allUsers = <%- JSON.stringify(allUsers) %>;
const currentUsername = '<%= currentUsername %>';
const nonce = "<%= nonce %>"
console.log('Nonce:', nonce);
</script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js" nonce="<%= nonce %>"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js" nonce="<%= nonce %>" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.4/xlsx.full.min.js" nonce="<%= nonce %>"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js" nonce="<%= nonce %>"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.2.1/exceljs.min.js" nonce="<%= nonce %>"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" nonce="<%= nonce %>"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.js" nonce="<%= nonce %>"></script>
<script src="inusers.js" nonce="<%= nonce %>"></script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.4/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.2.1/exceljs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.js"></script>
<script src="inusers.js"></script>
</body>

180
Sean/views/location.js Normal file
View File

@ -0,0 +1,180 @@
$(document).ready(function () {
$('#allLocationLink').on('click', function () {
$('#locationContainer').show();
$('#createLocationForm').hide();
$('#updateLocationForm').hide();
$('#deleteLocationForm').hide();
});
$('#addLocationLink').on('click', function () {
$('#locationContainer').hide();
$('#createLocationForm').show();
$('#updateLocationForm').hide();
$('#deleteLocationForm').hide();
});
$('#updateLocationLink').on('click', function () {
$('#locationContainer').hide();
$('#createLocationForm').hide();
$('#updateLocationForm').show();
$('#deleteLocationForm').hide();
});
$('#deleteLocationLink').on('click', function () {
$('#locationContainer').hide();
$('#createLocationForm').hide();
$('#updateLocationForm').show();
$('#deleteLocationForm').show();
});
});
let locationArray = [];
function populateTableAndArray(data) {
const tableBody = document.getElementById("locationTableBody");
// Clear existing rows and array
tableBody.innerHTML = "";
locationArray.length = 0;
// Loop through the data and create table rows
data.forEach(location => {
const row = document.createElement("tr");
row.innerHTML = `
<td>${location.id}</td>
<td>${location.location}</td>
<td>${location.description}</td>
`;
tableBody.appendChild(row);
// Push location data to the array
locationArray.push(location);
});
}
populateTableAndArray(locationsData);
populateLocationDropdown();
$('#locationForm').on('submit', function (e) {
e.preventDefault();
const location= $('#location').val();
const user = req.session.jobTitle
const description= $('#description').val();
const csrf_token = $('#userForm input[name="csrf_token"]').val();
fetch('/location/new', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: location,
added_by: user,
description: description,
csrf_token: csrf_token
}),
})
.then(response => {
if (response.ok) {
// Status 201 indicates successful creation
return response.json();
} else {
return response.json().then(data => {
throw new Error(data.message || `HTTP error! Status: ${response.status}`);
});
}
})
.then(data => {
console.log(`Location added successfully. Message: ${data.message}`);
alert('Location added successfully!');
resetFormFields();
})
.catch(error => {
console.error('Location not added successfully', error);
// Handle error as needed
});
});
function populateLocationDropdown() {
// Clear existing options
$('#locationDropdown').empty();
// Populate the dropdown with options from locationArray
locationArray.forEach(location => {
$('#locationDropdown').append(`<option value="${location.id}">${location.name}</option>`);
});
}
$('#updateForm').on('submit', function (e) {
e.preventDefault();
const selectedLocationId = $('#locationDropdown').val();
const location= $('#location').val();
const user = req.session.jobTitle
const description=$('#description').val();
const csrf_token = $('#userForm input[name="csrf_token"]').val();
fetch('/location/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id:selectedLocationId,
name: location,
added_by: user,
description: description,
csrf_token: csrf_token
}),
})
.then(response => {
if (response.ok) {
// Status 201 indicates successful creation
return response.json();
} else {
return response.json().then(data => {
throw new Error(data.message || `HTTP error! Status: ${response.status}`);
});
}
})
.then(data => {
console.log(`Location uppdated successfully. Message: ${data.message}`);
alert('Location updated successfully!');
resetFormFields();
})
.catch(error => {
console.error('Location not updated successfully', error);
// Handle error as needed
});
});
$('#deleteForm').on('submit', function (e) {
e.preventDefault();
const selectedLocationId = $('#locationDropdown').val();
const csrf_token = $('#userForm input[name="csrf_token"]').val();
fetch('/location/delete', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id:selectedLocationId,
csrf_token: csrf_token
}),
})
.then(response => {
if (response.ok) {
// Status 201 indicates successful creation
return response.json();
} else {
return response.json().then(data => {
throw new Error(data.message || `HTTP error! Status: ${response.status}`);
});
}
})
.then(data => {
console.log(`Location deleted successfully. Message: ${data.message}`);
alert('Location deleted successfully!');
resetFormFields();
})
.catch(error => {
console.error('Location not deleted successfully', error);
// Handle error as needed
});
});

120
Sean/views/locations.ejs Normal file
View File

@ -0,0 +1,120 @@
<!-- views/location.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap">
</head>
<body>
<header>
<h1>ECOSAVER MANAGEMENT</h1>
</header>
<nav>
<a href="#" id="allLocationLink">All Locations</a>
<a href="#" id="addLocationLink">Add Locations</a>
<a href="#" id="updateLocationLink">Update Locations</a>
<a href="#">Delete Locations</a>
<a href="/home" id="homeLink">Home</a>
</nav>
<main>
<h2>Welcome to the Location Page</h2>
</main>
<div id="locationContainer">
<table class="nice-table">
<thead>
<tr>
<th>ID</th>
<th>Location</th>
<th>Descriptions</th>
</tr>
</thead>
<tbody id="locationTableBody"></tbody>
</table>
</div>
<div id="createLocationForm" class="location-creation-container custom-location-form" style="display: none;">
<h3>Add Location</h3>
<div class="content">
<form action="/location/new" id="locationForm" method="post">
<div class="Location-details">
<div class="input-box">
<span class="details">Location Name</span>
<input type="text" name="location" id="location" placeholder="Enter Location name" required>
</div>
<div class="input-box">
<span class="details">Description</span>
<input type="text" name="description" id="description" placeholder="Enter the description here" required>
</div>
</div>
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<div class="button">
<input type="submit" value="submit">
</div>
</form>
</div>
</div>
<div id="updateLocationForm" class="location-update-container" style="display: none;">
<h3>Update Location</h3>
<div class="content">
<form action="/location/update" id="updateForm" method="put">
<div class="Location-details">
<div class="input-box">
<span class="details">Location to Update</span>
<select name="location" id="locationDropdown" required>
</select>
</div>
<div class="input-box">
<span class="details">Location Name</span>
<input type="text" name="location" id="location" placeholder="Enter Location name" required>
</div>
<div class="input-box">
<span class="details">Description</span>
<input type="text" name="description" id="description" placeholder="Enter the description here" required>
</div>
</div>
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<div class="button">
<input type="submit" value="submit">
</div>
</form>
</div>
</div>
<div id="deleteLocationForm" class="location-delete-container" style="display: none;">
<h3>Delete Location</h3>
<div class="content">
<form action="/location/delete" id="deleteForm" method="delete">
<div class="Location-details">
<div class="input-box">
<span class="details">Location to Delete</span>
<select name="location" id="locationDropdown" required>
</select>
</div>
</div>
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<div class="button">
<input type="submit" value="submit">
</div>
</form>
</div>
</div>
<footer>
Any Issue faced, Please contact the administrator at 11111111 or ecosaverAdmin@gmail.com
</footer>
<script>
const locationsData = <%- JSON.stringify(locationsData) %>;
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.3.3/purify.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="location.js"></script>
</body>
</html>

225
Sean/views/sensor.js Normal file
View File

@ -0,0 +1,225 @@
$(document).ready(function () {
$('#allSensorLink').on('click', function () {
$('#sensorContainer').show();
$('#createSensorForm').hide();
$('#additional-text4').hide();
$('#updateSensorForm').hide();
$('#deleteSensorForm').hide();
});
$('#addSensorLink').on('click', function () {
$('#sensorContainer').hide();
$('#createSensorForm').show();
$('#additional-text4').show();
$('#updateSensorForm').hide();
$('#deleteSensorForm').hide();
});
$('#updateSensorLink').on('click', function () {
$('#sensorContainer').hide();
$('#createSensorForm').show();
$('#additional-text4').show();
$('#updateSensorForm').show();
$('#deleteSensorForm').hide();
});
$('#deleteSensorLink').on('click', function () {
$('#sensorContainer').hide();
$('#createSensorForm').show();
$('#additional-text4').show();
$('#updateSensorForm').hide();
$('#deleteSensorForm').show();
});
});
function populateTableAndArray(data, locationsArray) {
const tableBody = document.getElementById("sensorTableBody");
// Clear existing rows and array
tableBody.innerHTML = "";
sensorArray.length = 0;
// Loop through the data and create table rows
data.forEach(sensor => {
const location = locationsArray.find(loc => loc.id === sensor.location);
const row = document.createElement("tr");
row.innerHTML = `
<td>${sensor.id}</td>
<td>${sensor.sensorname}</td>
<td>${sensor.added_by}</td>
<td>${sensor.description}</td>
<td>${location ? location.name : 'Unknown Location'}</td>
`;
tableBody.appendChild(row);
// Push sensor data to the array
sensorArray.push(sensor);
});
}
// Assuming locationsArray is defined elsewhere in your code
populateTableAndArray(sensorData);
console.log(sensorArray);
function populateLocationDropdown() {
const locationDropdown = document.getElementById('locationDropdown');
// Clear existing options
locationDropdown.innerHTML = '';
// Add a default option
const defaultOption = document.createElement('option');
defaultOption.text = 'Select a Location';
defaultOption.value = '';
locationDropdown.add(defaultOption);
// Add locations as options
locationsArray.forEach(location => {
const option = document.createElement('option');
option.text = location.location;
option.value = location.id;
locationDropdown.add(option);
});
}
populateLocationDropdown();
function populateSensorDropdown() {
const sensorDropdown = document.getElementById('sensorDropdown');
// Clear existing options
sensorDropdown.innerHTML = '';
// Add a default option
const defaultOption = document.createElement('option');
defaultOption.text = 'Select a Sensor';
defaultOption.value = '';
sensorDropdown.add(defaultOption);
// Add locations as options
sensorArray.forEach(location => {
const option = document.createElement('option');
option.text = sensor.sensorname;
option.value = sensor.id;
sensorDropdown.add(option);
});
}
populateSensorDropdown();
$('#sensorForm').on('submit', function (e) {
e.preventDefault();
const sensor = $('#sensor').val();
const user = req.session.jobTitle;
const macAddress = $('#macAddress').val();
const description = $('#description').val();
const location = $('#location').val();
const csrf_token = $('#userForm input[name="csrf_token"]').val();
fetch('sensor/new', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
sensorname: sensor,
added_by: user,
mac_address: macAddress,
description: description,
location: location,
csrf_token: csrf_token
}),
})
.then(response => {
if (response.ok) {
// Status 201 indicates successful creation
return response.json();
} else {
return response.json().then(data => {
throw new Error(data.message || `HTTP error! Status: ${response.status}`);
});
}
})
.then(data => {
console.log(`Sensor added successfully. Message: ${data.message}`);
alert('Sensor added successfully!');
resetFormFields();
})
.catch(error => {
console.error('Location not added successfully', error);
// Handle error as needed
});
});
$('#updatesensorForm').on('submit', function (e) {
e.preventDefault();
const id = $('#sensorDropdown').val();
const sensorname = $('#sensorname').val();
const user = req.session.jobTitle;
const macAddress = $('#macAddress').val();
const description = $('#description').val();
const location = $('#locationDropdown').val();
const csrf_token = $('#userForm input[name="csrf_token"]').val();
fetch('sensor/update', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: id,
sensorname: sensorname,
added_by: user,
mac_address: macAddress,
description: description,
location: location,
csrf_token: csrf_token
}),
})
.then(response => {
if (response.ok) {
// Status 201 indicates successful creation
return response.json();
} else {
return response.json().then(data => {
throw new Error(data.message || `HTTP error! Status: ${response.status}`);
});
}
})
.then(data => {
console.log(`Sensor updated successfully. Message: ${data.message}`);
alert('Sensor updated successfully!');
resetFormFields();
})
.catch(error => {
console.error('Sensor not updated successfully', error);
// Handle error as needed
});
});
$('#deleteForm').on('submit', function (e) {
e.preventDefault();
const selectedSensorId = $('#sensorDropdown').val();
const csrf_token = $('#userForm input[name="csrf_token"]').val();
fetch('/sensor/delete', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id:selectedSensorId,
csrf_token: csrf_token
}),
})
.then(response => {
if (response.ok) {
// Status 201 indicates successful creation
return response.json();
} else {
return response.json().then(data => {
throw new Error(data.message || `HTTP error! Status: ${response.status}`);
});
}
})
.then(data => {
console.log(`Sensor deleted successfully. Message: ${data.message}`);
alert('Sensor deleted successfully!');
resetFormFields();
})
.catch(error => {
console.error('Sensor not deleted successfully', error);
// Handle error as needed
});
});

153
Sean/views/sensors.ejs Normal file
View File

@ -0,0 +1,153 @@
<!-- views/sensor.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap">
</head>
<body>
<header>
<h1>ECOSAVER MANAGEMENT</h1>
</header>
<nav>
<a href="#" id="allSensorLink">All Sensor</a>
<a href="#" id="addSensorLink">Add Sensor</a>
<a href="#" id="updateSensorLink">Update Sensor</a>
<a href="#" id="deleteSensorLink">Delete Sensor</a>
<a href="/home" id="homeLink">Home</a>
</nav>
<main>
<h2>Welcome to the Sensor Page</h2>
</main>
<div id="sensorContainer">
<table class="nice-table">
<thead>
<tr>
<th>ID</th>
<th>Sensor Name</th>
<th>Added By</th>
<th>Mac Address</th>
<th>Description</th>
<th>Location</th>
</tr>
</thead>
<tbody id="sensorTableBody"></tbody>
</table>
</div>
<div id="createSensorForm" class="sensor-creation-container" style="display: none;">
<h3>Add Sensor</h3>
<div class="content">
<form action="/api/v0/sensor/new" id="sensorForm" method="post">
<div class="Sensor-details">
<div class="input-box">
<span class="details">Sensor Name</span>
<input type="text" name="sensor" id="sensor" placeholder="Enter Sensor name" required>
</div>
<div class="input-box">
<span class="details">Mac Address</span>
<input type="text" name="macAddress" id="macAddress" placeholder="Enter the Mac Address" required>
</div>
<div class="input-box">
<span class="details">Description</span>
<input type="text" name="description" id="description" placeholder="Enter the description here" required>
</div>
<div class="input-box">
<span class="details">Location</span>
<select name="location" id="locationDropdown" required>
</select>
</div>
</div>
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<div class="button">
<input type="submit" value="Submit">
</div>
</form>
</div>
</div>
<div id="additional-text4" style="display: none;">
<div class="condition">
<span>Conditions for creating a Sensor:</span>
<ul>
<li class="error">Please Remember to fill all inputs.</li>
<li class="error">Please ensure all inputs are correct before adding</li>
</ul>
</div>
</div>
<div id="updateSensorForm" class="sensor-update-container" style="display: none;">
<h3>Add Location</h3>
<div class="content">
<form action="/sensor/update" id="updateForm" method="put">
<div class="Location-details">
<div class="input-box">
<span class="details">Sensor to Update</span>
<select name="Sensor" id="sensorDropdown" required>
</select>
</div>
<div class="input-box">
<span class="details">Sensor Name</span>
<input type="text" name="sensorname" id="sensorname" placeholder="Enter Sensor name" required>
</div>
<div class="input-box">
<span class="details">Mac Address</span>
<input type="text" name="macAddress" id="macAddress" placeholder="Enter the Mac Address" required>
</div>
<div class="input-box">
<span class="details">Description</span>
<input type="text" name="description" id="description" placeholder="Enter the description here" required>
</div>
</div>
<div class="input-box">
<span class="details">Location</span>
<select name="location" id="locationDropdown" required>
</select>
</div>
</div>
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<div class="button">
<input type="submit" value="submit">
</div>
</form>
</div>
</div>
<div id="deleteSensorForm" class="sensor-delete-container" style="display: none;">
<h3>Delete Location</h3>
<div class="content">
<form action="/sensor/delete" id="deleteForm" method="delete">
<div class="Location-details">
<div class="input-box">
<span class="details">Sensor to Delete</span>
<select name="sensor" id="sensorDropdown" required>
</select>
</div>
</div>
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<div class="button">
<input type="submit" value="submit">
</div>
</form>
</div>
</div>
<footer>
Any Issue faced, Please contact the administrator at 11111111 or ecosaverAdmin@gmail.com
</footer>
</body>
<script>
const locationsArray = <%-JSON.stringify(locationsData) %>;
const sensorArray = <%- JSON.stringify(sensorData) %>;
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.3.3/purify.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="sensor.js"></script>
</html>

View File

@ -556,4 +556,120 @@ footer {
font-weight: bold; /* Make condition labels bold */
}
.sensor-creation-container {
max-width: 600px;
margin: 10px auto;
background-color: #f9f9f9;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
margin-left: 20px; /* Adjust the value as needed */
}
.input-box {
margin-bottom: 15px;
}
.input-box input,
.input-box select {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
.button input {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
.button input:hover {
background-color: #45a049;
}
#additional-text4 {
width: 30%; /* Adjust the width as needed */
margin-left: 500px; /* Push it to the right */
margin-top: -450px; /* Adjust the negative margin to move it up */
padding: 10px; /* Add padding for spacing */
background-color: #f4f4f4; /* Add background color if desired */
border-radius: 5px; /* Add border-radius for rounded corners */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* Add box shadow for a subtle effect */
border: 1px solid #ddd; /* Add a 1px solid border with light gray color */
font-family: 'Open Sans', sans-serif;
}
#additional-text4 p {
font-size: 16px; /* Adjust font size as needed */
line-height: 1.6; /* Adjust line height for better readability */
}
#additional-text4 .condition {
margin-bottom: 10px; /* Add space between conditions */
}
#additional-text4 .condition span {
font-weight: bold; /* Make condition labels bold */
}
#additional-text4 .condition.error {
color: red; /* Change text color for error conditions */
}
#additional-text4 .condition.success {
color: green; /* Change text color for success conditions */
}
.custom-location-form {
max-width: 600px;
margin: 10px auto 20px; /* Adjust the top and bottom margins */
background-color: #f9f9f9;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
margin-left: 20px; /* Adjust the left margin as needed */
margin-top: 0px; /* Adjust the top margin to move it up */
}
/* Add any other specific styles for this form */
.custom-location-form h3 {
color: #333; /* Customize the heading color */
}
section {
margin-top: 20px;
display: flex; /* Use flexbox to arrange items in a row */
justify-content: space-between; /* Distribute items evenly along the main axis */
}
.health-container {
width: 200px; /* Set a fixed width for square shape */
height: 200px; /* Set a fixed height for square shape */
background-color: #f0f0f0; /* Background color */
border: 1px solid #ddd;
padding: 10px;
border-radius: 10px; /* Rounded corners */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Box shadow for depth */
transition: transform 0.3s ease-in-out; /* Add a subtle transition effect */
}
.health-container:hover {
transform: scale(1.05); /* Scale up on hover */
}
.health-container h4 {
text-align: center;
color: #333; /* Heading color */
}
.health-container ul {
list-style: none;
margin: 0;
padding: 0;
text-align: center;
}
.health-container li {
margin: 5px 0;
color: #555; /* Text color */
}

27
api.MD
View File

@ -84,13 +84,13 @@ curl localhost/api/v0/sensor-data/new -H "Content-Type: application/json" -X POS
//put
curl localhost/api/v0/sensor-data/update -H "Content-Type: application/json" -X PUT -d '{"id": "1", "id_sensor": "1" , "id_location": "3" , "sensordata": {
"psi": "500",
"humidity": "11%",
"o3": "326ppm",
"no2": "445ppm",
"so2": "511ppm",
"co": "16ppm",
"temperature": "25C",
"windspeed": "2km/h",
"humidity": "11",
"o3": "326",
"no2": "445",
"so2": "511",
"co": "16",
"temperature": "25",
"windspeed": "2",
}}'
//delete
@ -160,10 +160,15 @@ Hour = 1 or wtv
curl 'http://localhost/api/v0/sensor-data/data?year=2023&month=1&week=1&day=1&sensorid=1&locationid=1'
//get specific data
http://localhost/api/v0/sensor-data/filter?windspeed=highest&limit=1
http://localhost/api/v0/sensor-data/data?psi=highest
//avg
http://localhost/api/v0/sensor-data/data?avg=temperature
//sum
http://localhost/api/v0/sensor-data/data?sum=temperature
//pagination
http://localhost/api/v0/sensor-data/data?week=1&sensorid=1&locationid=1&page=2&pagesize=10
@ -172,4 +177,4 @@ http://localhost/api/v0/sensor-data/data?week=1&sensorid=1&locationid=1&page=2&p
curl localhost/api/v0/user/register -H "Content-Type: application/json" -X POST -d '{"username": "testuser123", "password": "thisisthesystemuserpasswordnoob", "email": "testuser123@ecosaver.com", "address": "Nanyang Polytechnic 180 Ang Mo Kio Avenue 8 Singapore 569830", "phone": "12345678"}'
curl localhost:3000/api/v0/token/new -H "Content-Type: application/json" -X POST -d '{"userid": "5", "permission": "canRead"}'
curl localhost/api/v0/token/new -H "Content-Type: application/json" -X POST -d '{"userid": "5", "permission": "canRead"}'

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,17 +43,10 @@ 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');
@ -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();
@ -15,14 +16,39 @@ const router = express.Router();
//'{"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,55 +102,59 @@
</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>

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>
@ -104,4 +113,3 @@
</div>
</div>
</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" -->
@ -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

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') %>

View File

@ -1,4 +0,0 @@
moment = require('moment')
//current time
console.log(moment().format('hh:mm:ss a'))

193
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"axios": "^1.6.5",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
@ -29,8 +30,10 @@
"mysql2": "^3.7.1",
"node-fetch": "^3.3.2",
"nodemailer": "^6.9.8",
"os": "^0.1.2",
"otp-generator": "^4.0.1",
"otplib": "^12.0.1",
"pidusage": "^3.0.2",
"qrcode": "^1.5.3",
"sanitize-html": "^2.11.0",
"sequelize": "^6.35.2",
@ -40,32 +43,7 @@
"validator": "^13.11.0"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
},
"helmet": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz",
"integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==",
"extraneous": true
},
"htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"extraneous": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
"nodemon": "^3.0.3"
}
},
"node_modules/@babel/runtime": {
@ -442,6 +420,11 @@
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
@ -450,6 +433,16 @@
"node": ">= 4.0.0"
}
},
"node_modules/axios": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
"dependencies": {
"follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -731,6 +724,17 @@
"color-support": "bin.js"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
@ -1026,6 +1030,14 @@
"node": ">= 0.4"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@ -1693,6 +1705,25 @@
"node": ">=8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@ -1719,6 +1750,19 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@ -1971,55 +2015,9 @@
}
},
"node_modules/help-me": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz",
"integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==",
"dependencies": {
"glob": "^8.0.0",
"readable-stream": "^3.6.0"
}
},
"node_modules/help-me/node_modules/glob": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^5.0.1",
"once": "^1.3.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/help-me/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/help-me/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="
},
"node_modules/htmlparser2": {
"version": "8.0.2",
@ -2568,17 +2566,25 @@
"node": "*"
}
},
"node_modules/moment-timezone/node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}
},
"node_modules/mqtt": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.3.4.tgz",
"integrity": "sha512-nyhr2bnFtyiv68jV3yfR6eQtGcGs/jr2l3ETKXYc0amttsasXa1KgvETHRNRjfeDt/yc68IqoEjFzKkHpoQUPQ==",
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.3.5.tgz",
"integrity": "sha512-xd7qt/LEM721U6yHQcqjlaAKXL1Fsqf/MXq6C2WPi/6OXK2jdSzL1eZ7ZUgjea7IY2yFLRWD5LNdT1mL0arPoA==",
"dependencies": {
"@types/readable-stream": "^4.0.5",
"@types/ws": "^8.5.9",
"commist": "^3.2.0",
"concat-stream": "^2.0.0",
"debug": "^4.3.4",
"help-me": "^4.2.0",
"help-me": "^5.0.0",
"lru-cache": "^10.0.1",
"minimist": "^1.2.8",
"mqtt": "^5.2.0",
@ -2892,6 +2898,11 @@
"wrappy": "1"
}
},
"node_modules/os": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/os/-/os-0.1.2.tgz",
"integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ=="
},
"node_modules/otp-generator": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/otp-generator/-/otp-generator-4.0.1.tgz",
@ -3035,6 +3046,17 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pidusage": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz",
"integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==",
"dependencies": {
"safe-buffer": "^5.2.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
@ -3100,6 +3122,11 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
@ -3508,6 +3535,14 @@
"node": ">= 10.0.0"
}
},
"node_modules/sequelize/node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",

View File

@ -17,6 +17,7 @@
},
"homepage": "https://github.com/Newtbot/MP#readme",
"dependencies": {
"axios": "^1.6.5",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
@ -37,8 +38,10 @@
"mysql2": "^3.7.1",
"node-fetch": "^3.3.2",
"nodemailer": "^6.9.8",
"os": "^0.1.2",
"otp-generator": "^4.0.1",
"otplib": "^12.0.1",
"pidusage": "^3.0.2",
"qrcode": "^1.5.3",
"sanitize-html": "^2.11.0",
"sequelize": "^6.35.2",
@ -48,6 +51,6 @@
"validator": "^13.11.0"
},
"devDependencies": {
"nodemon": "^3.0.2"
"nodemon": "^3.0.3"
}
}