merged
@ -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')),
|
||||
},
|
||||
|
||||
},
|
||||
|
11
README.md
@ -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
|
||||
|
@ -13,4 +13,6 @@ let transporter = nodemailer.createTransport({
|
||||
pass: process.env.epass
|
||||
},
|
||||
});
|
||||
module.exports = { transporter };
|
||||
module.exports = { transporter };
|
||||
|
||||
|
||||
|
47
Sean/modules/otpUtils.js
Normal 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
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
13
Sean/modules/rateLimitMiddleware.js
Normal 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;
|
||||
|
||||
|
106
Sean/modules/validationMiddleware.js
Normal 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
|
||||
};
|
||||
|
||||
|
450
Sean/server.js
@ -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.use('/login', rateLimit);
|
||||
|
||||
|
||||
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.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,25 +199,41 @@ 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);
|
||||
res.status(500).send("Internal Server Error");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
app.get("/home", isAuthenticated, (req, res) => {
|
||||
// Render the home page with sensor data
|
||||
res.render("home", {
|
||||
username: req.session.username,
|
||||
});
|
||||
const getDatabaseStatus = async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
return { connected: true };
|
||||
} catch (error) {
|
||||
console.error('Database connection error:', error);
|
||||
return { connected: false, error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
@ -363,8 +298,8 @@ 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' });
|
||||
}
|
||||
|
||||
@ -787,9 +672,6 @@ app.delete('/api/deleteUser/:username', async (req, res) => {
|
||||
res.status(500).json({ success: false, error: 'Internal Server Error', details: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
app.get('/api/getLogs', async (req, res) => {
|
||||
try {
|
||||
@ -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
@ -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
@ -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
6
Sean/views/assets/bootstrap/css/bootstrap-grid.min.css
vendored
Normal file
7
Sean/views/assets/bootstrap/css/bootstrap-reboot.min.css
vendored
Normal 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}
|
6
Sean/views/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
6
Sean/views/assets/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
265
Sean/views/assets/dropdown/css/style.css
Normal 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; }
|
||||
|
||||
|
8
Sean/views/assets/dropdown/js/navbar-dropdown.js
Normal 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")})})})();
|
BIN
Sean/views/assets/images/air-pollution-1679x944.jpg
Normal file
After Width: | Height: | Size: 195 KiB |
BIN
Sean/views/assets/images/background1.jpg
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
Sean/views/assets/images/ecosostenibilita-1000x500.jpeg
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
Sean/views/assets/images/gallery01.jpg
Normal file
After Width: | Height: | Size: 306 KiB |
1
Sean/views/assets/images/hashes.json
Normal 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"}
|
BIN
Sean/views/assets/images/logo5.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
Sean/views/assets/images/shop3.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 221 KiB |
1625
Sean/views/assets/mobirise/css/mbr-additional.css
Normal file
15
Sean/views/assets/parallax/jarallax.css
Normal 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;
|
||||
}
|
45
Sean/views/assets/parallax/jarallax.js
Normal 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}]);
|
15
Sean/views/assets/smoothscroll/smooth-scroll.js
Normal 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})();
|
934
Sean/views/assets/socicon/css/styles.css
Normal 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";
|
||||
}
|
BIN
Sean/views/assets/socicon/fonts/socicon.eot
Normal file
307
Sean/views/assets/socicon/fonts/socicon.svg
Normal file
After Width: | Height: | Size: 315 KiB |
BIN
Sean/views/assets/socicon/fonts/socicon.ttf
Normal file
BIN
Sean/views/assets/socicon/fonts/socicon.woff
Normal file
BIN
Sean/views/assets/socicon/fonts/socicon.woff2
Normal file
965
Sean/views/assets/theme/css/style.css
Normal 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;
|
||||
}
|
67
Sean/views/assets/theme/js/script.js
Normal file
70
Sean/views/assets/ytplayer/index.js
Normal 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;
|
@ -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">
|
||||
|
@ -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
@ -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">‌</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>
|
||||
|
@ -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
@ -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
@ -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
@ -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
@ -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>
|
@ -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
@ -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"}'
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -107,4 +107,6 @@ const sensorModel = sequelize.define(
|
||||
}
|
||||
);
|
||||
|
||||
//sensorModel.belongsTo(locationModel);
|
||||
|
||||
module.exports = { sensorModel };
|
||||
|
@ -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,
|
||||
|
@ -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(() => {
|
||||
|
@ -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};
|
||||
|
11
consumerWebsite/functions/apilog.js
Normal 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 };
|
@ -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 };
|
||||
|
@ -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,
|
||||
};
|
108
consumerWebsite/functions/nodeMail.js
Normal 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 };
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
|
||||
};
|
@ -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,
|
||||
|
||||
};
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
*/
|
@ -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',
|
||||
|
@ -3812,6 +3812,7 @@
|
||||
|
||||
.card-text {
|
||||
color: #000000;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* edit profile */
|
||||
|
@ -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 */
|
||||
}
|
||||
}
|
||||
|
||||
|
61
consumerWebsite/public/css/data.css
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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 */
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
121
consumerWebsite/public/js/data.js
Normal 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");
|
||||
});
|
||||
|
||||
|
||||
});
|
@ -1,21 +1,18 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function updateAdditionalInfo(region) {
|
||||
const infoContainer = document.getElementById("additional-info");
|
||||
// Replace the following with actual data retrieval based on the region
|
||||
const aqi = "15";
|
||||
const temperature = "25°C";
|
||||
const humidity = "60%";
|
||||
const pm25 = "10";
|
||||
const pm10 = "20";
|
||||
const so2 = "5";
|
||||
const o3 = "35";
|
||||
const co = "0.5";
|
||||
const no2 = "15";
|
||||
// Replace the following with actual data retrieval based on the region
|
||||
const aqi = "15";
|
||||
const temperature = "25°C";
|
||||
const humidity = "60%";
|
||||
const so2 = "5";
|
||||
const o3 = "35";
|
||||
const co = "0.5";
|
||||
const no2 = "15";
|
||||
|
||||
infoContainer.innerHTML = `
|
||||
infoContainer.innerHTML = `
|
||||
<div class="additional-info-box">
|
||||
<h3>Additional Information - ${region}</h3>
|
||||
<button id="downloadCsvButton">Download CSV</button>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Air Quality Index:</span>
|
||||
<span class="info-value">${aqi}</span>
|
||||
@ -28,14 +25,6 @@ infoContainer.innerHTML = `
|
||||
<span class="info-label">Humidity:</span>
|
||||
<span class="info-value">${humidity}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">PM2.5:</span>
|
||||
<span class="info-value">${pm25}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">PM10:</span>
|
||||
<span class="info-value">${pm10}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">SO2:</span>
|
||||
<span class="info-value">${so2}</span>
|
||||
@ -54,23 +43,16 @@ infoContainer.innerHTML = `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
// Remove the 'active' class from all info-box elements
|
||||
const infoBoxes = document.querySelectorAll('.info-box');
|
||||
infoBoxes.forEach(box => box.classList.remove('active'));
|
||||
|
||||
// Add the 'active' class to the clicked info-box
|
||||
const clickedBox = document.getElementById(region.toLowerCase());
|
||||
clickedBox.classList.add('active');
|
||||
}
|
||||
|
||||
|
||||
|
||||
const defaultRegion = "North";
|
||||
const defaultBox = document.getElementById(defaultRegion.toLowerCase());
|
||||
defaultBox.classList.add('active');
|
||||
const defaultAqi = "--"; // Replace with actual data retrieval
|
||||
updateAdditionalInfo(defaultRegion, defaultAqi);
|
||||
|
||||
|
||||
// Event listeners for each region's info-box
|
||||
document.getElementById("north").addEventListener("click", function () {
|
||||
const northAqi = "--"; // Replace with actual data retrieval
|
||||
@ -117,3 +99,4 @@ document.getElementById("central").addEventListener("click", function () {
|
||||
updateAdditionalInfo("Central");
|
||||
});
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
37
consumerWebsite/routes/apilog.js
Normal 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;
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
21
consumerWebsite/routes/latestsensorData.js
Normal 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;
|
@ -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
|
||||
|
@ -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;
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
const { addToken } = require("../functions/api");
|
||||
|
||||
const { addToken, checkToken } = require("../functions/api");
|
||||
const { checkEmail, getUserByEmail } = require("../functions/user");
|
||||
const { sendTokenEmail } = require("../functions/nodeMail");
|
||||
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
@ -11,18 +12,43 @@ const router = express.Router();
|
||||
4) store the api key in database
|
||||
*/
|
||||
//token/new
|
||||
//curl localhost:3000/api/v0/token/new -H "Content-Type: application/json" -X POST -d
|
||||
//curl localhost:3000/api/v0/token/new -H "Content-Type: application/json" -X POST -d
|
||||
//'{"userid": "5", "permission": "canRead" ,}'
|
||||
router.post("/new", async (req, res, next) => {
|
||||
try {
|
||||
const token = await addToken(req.body.userid, req.body.permission , "2204-01-24 07:34:36" );
|
||||
res.json({token: token});
|
||||
//console.log(req.body);
|
||||
const Res = await checkEmail(req.body.email);
|
||||
if (!Res) {
|
||||
let error = new Error("Email not found");
|
||||
error.status = 400;
|
||||
return next(error);
|
||||
} else {
|
||||
let userid = await getUserByEmail(req.body.email);
|
||||
if (!userid) return false;
|
||||
|
||||
const tokenRes = await checkToken(userid.id);
|
||||
if (tokenRes.isKey !== "null" && tokenRes.isKey !== "isKey") {
|
||||
//allow user to create token
|
||||
const token = await addToken(
|
||||
userid.id,
|
||||
"canRead",
|
||||
"isKey",
|
||||
"2204-01-24 07:34:36"
|
||||
);
|
||||
if (!token) return false;
|
||||
sendTokenEmail(req.body.email, token);
|
||||
res.json({
|
||||
message: "Token generated successfully and sent to email",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//const token = await addToken(req.body.userid, "canRead" , "2204-01-24 07:34:36" );
|
||||
//res.json({token: token});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -77,11 +77,12 @@
|
||||
</div>
|
||||
<div class="container text-center">
|
||||
<br>
|
||||
<p>All Rights Reserved. © 2023 <a href="/">EcoSaver</a>
|
||||
<p>All Rights Reserved. © 2024 <a href="/">EcoSaver</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/search.js"></script>
|
||||
<script src="/js/search.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
19
consumerWebsite/views/checkemail.ejs
Normal 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>
|
@ -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 -->
|
||||
|
@ -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>
|
||||
|
@ -1,11 +1,60 @@
|
||||
<%- include('top') %>
|
||||
<script>
|
||||
//call socket.io
|
||||
app.socket.on("sensorData:new", function (data) {
|
||||
console.log("new data!!")
|
||||
console.log(data);
|
||||
<script type="text/javascript">
|
||||
function extractNumbers(str) {
|
||||
if (typeof str === 'number') return str;
|
||||
return str.match(/\d+/)[0];
|
||||
}
|
||||
|
||||
function calculateAverage(numbers) {
|
||||
if (numbers.length === 0) return 0
|
||||
const sum = numbers.reduce((acc, num) => acc + num, 0);
|
||||
return sum / numbers.length;
|
||||
}
|
||||
const values = {
|
||||
psi: [],
|
||||
humidity: [],
|
||||
temperature: [],
|
||||
windspeed: [],
|
||||
};
|
||||
|
||||
function parseRowToTemplace(row) {
|
||||
values.psi.unshift(extractNumbers(row.measurement.psi))
|
||||
values.humidity.unshift(extractNumbers(row.measurement.humidity))
|
||||
values.temperature.unshift(extractNumbers(row.measurement.temperature))
|
||||
values.windspeed.unshift(extractNumbers(row.measurement.windspeed))
|
||||
|
||||
return {
|
||||
average: {
|
||||
psi: parseInt(calculateAverage(values.psi)),
|
||||
humidity: parseInt(calculateAverage(values.humidity)),
|
||||
temperature: parseInt(calculateAverage(values.temperature)),
|
||||
windspeed: parseInt(calculateAverage(values.windspeed)),
|
||||
},
|
||||
latest: {
|
||||
psi: values.psi[0],
|
||||
humidity: values.humidity[0],
|
||||
temperature: values.temperature[0],
|
||||
windspeed: values.windspeed[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(async function () {
|
||||
|
||||
app.api.get('latest-sensor-data/data', function (error, data) {
|
||||
for (let row of data) {
|
||||
//console.log(row);
|
||||
$.scope.LatestSensorData.update(parseRowToTemplace(row));
|
||||
}
|
||||
});
|
||||
|
||||
//call socket.io to get live data
|
||||
app.socket.on("sensorData:new", function (data) {
|
||||
$.scope.LatestSensorData.update(parseRowToTemplace(data));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -53,56 +102,60 @@
|
||||
</header>
|
||||
<!-- Page Content -->
|
||||
<div class="container">
|
||||
<div class="services-bar">
|
||||
<h1 class="my-4">Services</h1>
|
||||
<!-- Services Section -->
|
||||
<div class="row">
|
||||
<div class="col-lg-3 mb-4">
|
||||
<div class="card">
|
||||
<h4 class="card-header">Air Quality Index</h4>
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text display-4">15 - 18 PSI</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
<div class="services-bar" jq-repeat="LatestSensorData">
|
||||
<h1 class="my-4">Services</h1>
|
||||
<!-- Services Section -->
|
||||
<div class="row">
|
||||
<div class="col-lg-3 mb-4">
|
||||
<div class="card">
|
||||
<h4 class="card-header">Air Quality Index</h4>
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text display-4"> Average: {{average.psi}} PSI</p>
|
||||
<p class="card-text display-4"> Latest: {{latest.psi}} PSI</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 mb-4">
|
||||
<div class="card">
|
||||
<h4 class="card-header">Humidity</h4>
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text display-4">70% - 75%</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 mb-4">
|
||||
<div class="card">
|
||||
<h4 class="card-header">Temperature</h4>
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text display-4">30° - 37°</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}}°</p>
|
||||
<p class="card-text display-4"> Latest: {{latest.temperature}}°</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 mb-4">
|
||||
<div class="card">
|
||||
<h4 class="card-header">Wind Speed</h4>
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text display-4"> Average: {{average.windspeed}} Km/h</p>
|
||||
<p class="card-text display-4"> Latest: {{latest.windspeed}} Km/h</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
<!-- About Section -->
|
||||
|
@ -38,7 +38,11 @@
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
|
||||
|
||||
<script src="js/learnmore.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
|
||||
<%- include('bot') %>
|
||||
|
@ -6,14 +6,14 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link rel="shortcut icon" type="images/logo.ico" href="images/logo.ico" />
|
||||
<link rel="shortcut icon" type="images/logo.ico" href="/images/logo.ico" />
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link rel="stylesheet" href="css/sp.css" />
|
||||
<link rel="stylesheet" href="/css/sp.css" />
|
||||
|
||||
|
||||
<!-- jQuery library -->
|
||||
@ -27,10 +27,10 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.js"></script>
|
||||
<!-- socket.io scriot -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.min.js"></script>
|
||||
|
||||
<!-- jquery app.js -->
|
||||
<script src="js/app.js"></script>
|
||||
<script src="/js/app.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
@ -44,25 +44,29 @@
|
||||
$(document).ready(function () {
|
||||
//check if user is logged in
|
||||
app.auth.isLoggedIn(function (error, data) {
|
||||
if (data) {
|
||||
$('#cl-logout-button').show('fast');
|
||||
$('#cl-profile-button').show('fast');
|
||||
$('#cl-login-button').hide('fast');
|
||||
if (!error) {
|
||||
$("#cl-logout-button").show("fast");
|
||||
$("#cl-viewdata-button").show("fast");
|
||||
$("#cl-api-button").show("fast");
|
||||
$("#cl-profile-button").show("fast");
|
||||
$("#cl-login-button").hide("fast");
|
||||
|
||||
} else {
|
||||
$('#cl-login-button').show('fast');
|
||||
|
||||
}
|
||||
$('body').show('fast')
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
||||
|
||||
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-light top-nav fixed-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="images/logo.png" alt="logo" />
|
||||
<img src="/images/logo.png" alt="logo" />
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
|
||||
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
@ -79,11 +83,16 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/contact">Contact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/api">API Doc</a>
|
||||
</li>
|
||||
<!-- profile button -->
|
||||
<div class="form-inline mt-2 mt-md-0">
|
||||
<a id="cl-viewdata-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/viewdata"
|
||||
style="display: none">
|
||||
<i class="fas fa-sign-out"></i>Data
|
||||
</a>
|
||||
<a id="cl-api-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/api"
|
||||
style="display: none">
|
||||
<i class="fas fa-sign-out"></i> API
|
||||
</a>
|
||||
<a id="cl-profile-button" class="btn btn-outline-info my-2 my-sm-0" href="/profile"
|
||||
style="display: none;">
|
||||
<i class="fas fa-sign-out"></i>
|
||||
@ -103,5 +112,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</nav>
|
@ -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">
|
||||
|
@ -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>© 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>
|
||||
|
||||
|
@ -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') %>
|
@ -4,7 +4,6 @@
|
||||
//app.auth.redirectIfLoggedIn();
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<section class="wrapper">
|
||||
<div class="form signup iot-card">
|
||||
<!--<div class="form signup card" -->
|
||||
@ -67,7 +66,7 @@
|
||||
confirm_password.onkeyup = validatePassword;
|
||||
|
||||
|
||||
|
||||
|
||||
const wrapper = document.querySelector(".wrapper"),
|
||||
signupHeader = document.querySelector(".signup header"),
|
||||
loginHeader = document.querySelector(".login header");
|
||||
@ -83,4 +82,4 @@
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
@ -9,7 +9,7 @@
|
||||
<meta http-equiv="cleartype" content="on" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="shortcut icon" type="images/logo.ico" href="images/logo.ico" />
|
||||
<link rel="shortcut icon" type="images/logo.ico" href="/images/logo.ico" />
|
||||
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
@ -17,8 +17,8 @@
|
||||
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="css/all.css" rel="stylesheet" />
|
||||
<link href="css/style.css" rel="stylesheet" />
|
||||
<link href="/css/all.css" rel="stylesheet" />
|
||||
<link href="/css/style.css" rel="stylesheet" />
|
||||
<!-- weird api page cdn -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
@ -42,13 +42,14 @@
|
||||
</script>
|
||||
<!-- socket.io scriot -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.min.js"></script>
|
||||
|
||||
<!-- fancy table cdn -->
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/jquery.fancytable/dist/fancyTable.min.js"></script> -->
|
||||
|
||||
<!-- jq-repeat -->
|
||||
<script src="js/jq-repeat.js"></script>
|
||||
<script src="/js/jq-repeat.js"></script>
|
||||
|
||||
<!-- jquery app.js -->
|
||||
<script src="js/app.js"></script>
|
||||
<!-- jquery public app.js -->
|
||||
<script src="/js/app.js"></script>
|
||||
</head>
|
||||
<!-- javascript function to check if user is auth -->
|
||||
<script>
|
||||
@ -57,13 +58,12 @@
|
||||
//check if user is logged in
|
||||
app.auth.isLoggedIn(function (error, data) {
|
||||
if (!error) {
|
||||
console.log(error);
|
||||
$.scope.getUsername.update(data);
|
||||
|
||||
if (location.pathname == "/profile") {
|
||||
$.scope.getUserDetails.update(data);
|
||||
}
|
||||
$("#cl-logout-button").show("fast");
|
||||
$("#cl-viewdata-button").show("fast");
|
||||
$("#cl-api-button").show("fast");
|
||||
$("#cl-profile-button").show("fast");
|
||||
$("#cl-login-button").hide("fast");
|
||||
@ -80,7 +80,7 @@
|
||||
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-light top-nav fixed-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="images/logo.png" alt="logo" />
|
||||
<img src="/images/logo.png" alt="logo" />
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
|
||||
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
@ -100,14 +100,13 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/contact">Contact</a>
|
||||
</li>
|
||||
<!--
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/api">API Doc</a>
|
||||
</li>
|
||||
|
||||
-->
|
||||
<!-- profile button -->
|
||||
<div class="form-inline mt-2 mt-md-0">
|
||||
<a id="cl-viewdata-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/viewdata"
|
||||
style="display: none">
|
||||
<i class="fas fa-sign-out"></i>Data
|
||||
</a>
|
||||
|
||||
<a id="cl-api-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/api"
|
||||
style="display: none">
|
||||
<i class="fas fa-sign-out"></i> API
|
||||
@ -133,4 +132,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
259
consumerWebsite/views/viewdata.ejs
Normal 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') %>
|
@ -1,4 +0,0 @@
|
||||
moment = require('moment')
|
||||
|
||||
//current time
|
||||
console.log(moment().format('hh:mm:ss a'))
|
193
package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|