Compare commits
4 Commits
5a8e65a471
...
651f870415
Author | SHA1 | Date | |
---|---|---|---|
651f870415 | |||
407248710f | |||
677d1d92d2 | |||
dd32138229 |
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
*.sqlite
|
||||
.env
|
57
index.js
57
index.js
@ -1,55 +1,32 @@
|
||||
const { launchWeb } = require('./web.js');
|
||||
const { EventEmitter } = require('events');
|
||||
const Sequelize = require('sequelize');
|
||||
import { launchWeb } from './web.js';
|
||||
import { EventEmitter } from 'events';
|
||||
import { loadDatabase } from './utils/database.js';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
const sequelize = new Sequelize("database", "user", "password", {
|
||||
host: "localhost",
|
||||
dialect: "sqlite",
|
||||
logging: false,
|
||||
storage: "database.sqlite",
|
||||
});
|
||||
|
||||
const contactsDB = sequelize.define("contacts", {
|
||||
phone: {
|
||||
type: Sequelize.STRING,
|
||||
primaryKey: true,
|
||||
},
|
||||
firstName: {
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
lastName: {
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
called: {
|
||||
type: Sequelize.INTEGER,// 0: not called - 1: called - 2: ongoing call (- 3: no response ?)
|
||||
defaultValue: false,
|
||||
},
|
||||
vote: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
}
|
||||
});
|
||||
|
||||
contactsDB.sync();
|
||||
|
||||
global.database = {};
|
||||
global.database.contacts = contactsDB
|
||||
loadDatabase();
|
||||
|
||||
const submitEvent = new EventEmitter();
|
||||
launchWeb(submitEvent);
|
||||
await launchWeb(submitEvent);
|
||||
|
||||
submitEvent.on('call', async (call) => {
|
||||
submitEvent.on("call", async (call) => {
|
||||
let vote;
|
||||
if(call.vote) {
|
||||
if (call.vote) {
|
||||
console.log(`${call.phone} va voter`);
|
||||
vote = 1;
|
||||
} else {
|
||||
console.log(`${call.phone} ne va pas voter`);
|
||||
vote = 0;
|
||||
}
|
||||
await global.database.contacts.update({ vote: vote }, { where: { phone: call.phone } })
|
||||
await global.database.contacts.update(
|
||||
{ vote: vote, called: 1 },
|
||||
{ where: { phone: call.phone } },
|
||||
);
|
||||
});
|
||||
submitEvent.on('add', (request) => {
|
||||
submitEvent.on("add", (request) => {
|
||||
console.log(request);
|
||||
});
|
||||
|
||||
global.events = {
|
||||
submitEvent: submitEvent,
|
||||
}
|
||||
|
@ -6,11 +6,16 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "Ninjdai",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"sequelize": "^6.35.1",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"sqlite3": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,4 @@ async function getRandomUser() {
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { getRandomUser };
|
||||
export { getRandomUser };
|
||||
|
@ -1,7 +1,9 @@
|
||||
function generateCallHTML(phoneNumber, name, callcount) {
|
||||
import { navbar } from '../../utils/navbar.js';
|
||||
|
||||
function generateCallHTML(phoneNumber, name, callcount, session) {
|
||||
const head = `<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><link rel="stylesheet" type="text/css" href="/assets/css/form.css" /><title>AutoCallMenu</title></head>`;
|
||||
const form = `
|
||||
<form action="/inputs/call" method="post">
|
||||
<form action="/api/contacts/call" method="post">
|
||||
<input type="hidden" name="phone" value="${phoneNumber}">
|
||||
<label class="switch">
|
||||
<input type="checkbox" name="vote">
|
||||
@ -12,8 +14,8 @@ function generateCallHTML(phoneNumber, name, callcount) {
|
||||
<button class="submit" type="submit">CONFIRMER</button>
|
||||
</form>
|
||||
`;
|
||||
const body = `<body><h1>Bienvenue :3</h1><h2>Appel: ${name} - ${phoneNumber}</h2><p>${callcount} appels restants</p>${form}</body>`;
|
||||
const body = `<body>${navbar(session)}<h1>Bienvenue :3</h1><h2>Appel: ${name} - ${phoneNumber}</h2><p>${callcount} appels restants</p>${form}</body>`;
|
||||
return `<!DOCTYPE html><html>${head}${body}</html>`;
|
||||
}
|
||||
|
||||
module.exports = { generateCallHTML };
|
||||
export { generateCallHTML };
|
||||
|
8
src/html/pages/api/add.js
Normal file
8
src/html/pages/api/add.js
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
path: "/api/contacts/add",
|
||||
requiresLogin: true,
|
||||
type: "post",
|
||||
async execute(request, response) {
|
||||
global.events.submitEvent.emit("add", request.body);
|
||||
},
|
||||
}
|
12
src/html/pages/api/call.js
Normal file
12
src/html/pages/api/call.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { permissionBits } from '../../../../utils/permissions.js';
|
||||
|
||||
export default {
|
||||
path: "/api/contacts/call",
|
||||
requiresLogin: true,
|
||||
permissions: permissionBits.CALL,
|
||||
type: "post",
|
||||
async execute(request, response) {
|
||||
global.events.submitEvent.emit("call", request.body);
|
||||
response.redirect("/calls");
|
||||
},
|
||||
}
|
39
src/html/pages/api/users.js
Normal file
39
src/html/pages/api/users.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { permissionBits } from "../../../../utils/permissions.js";
|
||||
|
||||
export default {
|
||||
path: "/api/users(/*)?",
|
||||
requiresLogin: true,
|
||||
permissions: permissionBits.ADMIN,
|
||||
type: "post",
|
||||
async execute(request, response) {
|
||||
const username = request.originalUrl.split("/")[3];
|
||||
const operation = request.originalUrl.split("/")[4];
|
||||
const args = request.body;
|
||||
switch (operation) {
|
||||
case "create":
|
||||
if (
|
||||
await global.database.users.findOne({
|
||||
where: { username: username },
|
||||
})
|
||||
) return response.redirect("/dashboard/users/create?error=User already exists");
|
||||
console.log(`Creating user ${username}`);
|
||||
|
||||
let permissions = Number(args.permissions);
|
||||
if (isNaN(permissions)) {
|
||||
permissions = 0;
|
||||
for (const permissionBit of args.permissions) {
|
||||
permissions += Number(permissionBit);
|
||||
}
|
||||
}
|
||||
if(!(permissions & permissionBits.DEFAULT)) permissions ^= permissionBits.DEFAULT;
|
||||
await global.database.users.create({
|
||||
username: username,
|
||||
password: args.password,
|
||||
permissions: permissions,
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
14
src/html/pages/dashboard/index.js
Normal file
14
src/html/pages/dashboard/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { readFile } from 'fs/promises';
|
||||
import { navbar } from '../../../../utils/navbar.js';
|
||||
import { permissionBits } from '../../../../utils/permissions.js';
|
||||
|
||||
export default {
|
||||
path: "/dashboard",
|
||||
requiresLogin: true,
|
||||
permissions: permissionBits.DEFAULT,
|
||||
type: "get",
|
||||
async execute(request, response) {
|
||||
const res = await readFile(`${process.env.WWW}/dashboard/index.html`, "utf8")
|
||||
response.send(res.replaceAll("<NAVBAR>", navbar(request.session)));
|
||||
},
|
||||
}
|
11
src/html/pages/dashboard/users.js
Normal file
11
src/html/pages/dashboard/users.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { permissionBits } from '../../../../utils/permissions.js';
|
||||
|
||||
export default {
|
||||
path: "/dashboard/users",
|
||||
requiresLogin: true,
|
||||
permissions: permissionBits.ADMIN,
|
||||
type: "get",
|
||||
async execute(request, response) {
|
||||
response.redirect("/dashboard/users/list");
|
||||
},
|
||||
}
|
21
src/html/pages/dashboard/users_create.js
Normal file
21
src/html/pages/dashboard/users_create.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { readFile } from "fs/promises";
|
||||
import { navbar } from "../../../../utils/navbar.js";
|
||||
import { permissionBits } from "../../../../utils/permissions.js";
|
||||
|
||||
export default {
|
||||
path: "/dashboard/users/create",
|
||||
requiresLogin: true,
|
||||
permissions: permissionBits.ADMIN,
|
||||
type: "get",
|
||||
async execute(request, response, args) {
|
||||
let res = await readFile(
|
||||
`${process.env.WWW}/dashboard/users/create.html`,
|
||||
"utf8",
|
||||
);
|
||||
if(args?.error) res += `
|
||||
<script type="text/javascript">
|
||||
alert("Erreur: ${args.error.replaceAll('%20', ' ')}");
|
||||
</script>`;
|
||||
response.send(res.replaceAll("<NAVBAR>", navbar(request.session)));
|
||||
},
|
||||
};
|
38
src/html/pages/root/call.js
Normal file
38
src/html/pages/root/call.js
Normal file
@ -0,0 +1,38 @@
|
||||
import { generateCallHTML } from "../../../../src/html/callHTML.js";
|
||||
import { getRandomUser } from '../../../../src/database/getUser.js';
|
||||
import { navbar } from '../../../../utils/navbar.js';
|
||||
import { permissionBits } from "../../../../utils/permissions.js";
|
||||
|
||||
export default {
|
||||
path: "/calls",
|
||||
requiresLogin: true,
|
||||
permissions: permissionBits.CALL,
|
||||
type: "get",
|
||||
async execute(request, response) {
|
||||
const res = await generateCallResponse(request.session);
|
||||
response.send(res.replaceAll("<NAVBAR>", navbar(request.session)));
|
||||
},
|
||||
}
|
||||
|
||||
async function generateCallResponse(session) {
|
||||
const user = await getRandomUser();
|
||||
let callHTML;
|
||||
if (user) callHTML = generateCallHTML(user.phone, user.name, user.count, session);
|
||||
else callHTML = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/form.css" />
|
||||
<title>AutoCallMenu</title>
|
||||
</head>
|
||||
<body>
|
||||
<NAVBAR>
|
||||
<h1>Bienvenue :3</h1>
|
||||
<h2>0 appels restants !</h2>
|
||||
</body>
|
||||
</html>`;
|
||||
return callHTML;
|
||||
}
|
11
src/html/pages/root/index.js
Normal file
11
src/html/pages/root/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { readFile } from 'fs/promises';
|
||||
import { navbar } from '../../../../utils/navbar.js';
|
||||
|
||||
export default {
|
||||
path: "/",
|
||||
type: "get",
|
||||
async execute(request, response) {
|
||||
const res = await readFile(`${process.env.WWW}/index.html`, "utf8")
|
||||
response.send(res.replaceAll("<NAVBAR>", navbar(request.session)));
|
||||
},
|
||||
}
|
15
src/html/pages/root/login_get.js
Normal file
15
src/html/pages/root/login_get.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { readFile } from 'fs/promises';
|
||||
import { navbar } from '../../../../utils/navbar.js';
|
||||
|
||||
export default {
|
||||
path: "/login",
|
||||
type: "get",
|
||||
async execute(request, response, args) {
|
||||
if (request.session.user) return response.redirect("/");
|
||||
if(args?.fail) {
|
||||
console.log('Failed login attempt !')
|
||||
}
|
||||
const res = await readFile(`${process.env.WWW}/login.html`, "utf8");
|
||||
response.send(res.replaceAll("<NAVBAR>", navbar(request.session)));
|
||||
},
|
||||
}
|
18
src/html/pages/root/login_post.js
Normal file
18
src/html/pages/root/login_post.js
Normal file
@ -0,0 +1,18 @@
|
||||
export default {
|
||||
path: "/login",
|
||||
type: "post",
|
||||
async execute(request, response) {
|
||||
const { username, password } = request.body;
|
||||
console.log(request.body);
|
||||
global.database.users.findOne({ where: { username: username } }).then(function (user) {
|
||||
if (!user) {
|
||||
response.redirect('/login');
|
||||
} else if (!user.validPassword(password)) {
|
||||
response.redirect('/login');
|
||||
} else {
|
||||
request.session.user = user.dataValues;
|
||||
response.redirect('/');
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
9
src/html/pages/root/logout.js
Normal file
9
src/html/pages/root/logout.js
Normal file
@ -0,0 +1,9 @@
|
||||
export default {
|
||||
path: "/logout",
|
||||
type: "get",
|
||||
async execute(request, response) {
|
||||
request.session.destroy(() => {
|
||||
response.redirect("/login");
|
||||
});
|
||||
},
|
||||
}
|
69
utils/database.js
Normal file
69
utils/database.js
Normal file
@ -0,0 +1,69 @@
|
||||
import Sequelize from "sequelize";
|
||||
import bcrypt from 'bcrypt';
|
||||
import { permissionBits } from "./permissions.js";
|
||||
|
||||
function loadDatabase() {
|
||||
const sequelize = new Sequelize("database", "user", "password", {
|
||||
host: "localhost",
|
||||
dialect: "sqlite",
|
||||
logging: false,
|
||||
storage: "database.sqlite",
|
||||
});
|
||||
|
||||
const contactsDB = sequelize.define("contacts", {
|
||||
phone: {
|
||||
type: Sequelize.STRING,
|
||||
primaryKey: true,
|
||||
},
|
||||
firstName: {
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
lastName: {
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
called: {
|
||||
type: Sequelize.INTEGER, // 0: not called - 1: called - 2: ongoing call (- 3: no response ?)
|
||||
defaultValue: false,
|
||||
},
|
||||
vote: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
const userDB = sequelize.define("users", {
|
||||
username: {
|
||||
type: Sequelize.STRING,
|
||||
primaryKey: true,
|
||||
},
|
||||
password: {
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
permissions: {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: permissionBits.DEFAULT,
|
||||
},
|
||||
}, {
|
||||
hooks: {
|
||||
beforeCreate: async function(user) {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
user.password = await bcrypt.hash(user.password, salt);
|
||||
},
|
||||
},
|
||||
});
|
||||
userDB.prototype.validPassword = async function(password) {
|
||||
return await bcrypt.compare(password, this.password);
|
||||
}
|
||||
|
||||
|
||||
contactsDB.sync();
|
||||
userDB.sync();
|
||||
|
||||
global.database = {};
|
||||
global.database.contacts = contactsDB;
|
||||
global.database.users = userDB;
|
||||
//global.database.users.create({username: "admin", password: "admin", permissions: permissionBits.DEFAULT | permissionBits.CALL | permissionBits.ADMIN})
|
||||
//global.database.users.create({username: "test", password: "test", permissions: permissionBits.DEFAULT | permissionBits.CALL})
|
||||
}
|
||||
|
||||
export { loadDatabase };
|
29
utils/handler.js
Normal file
29
utils/handler.js
Normal file
@ -0,0 +1,29 @@
|
||||
import fs from 'fs';
|
||||
const pagesPath = "./src/html/pages/";
|
||||
|
||||
async function deployHandler() {
|
||||
global.handler = {
|
||||
get: {},
|
||||
post: {},
|
||||
};
|
||||
let numberOfPages = 0;
|
||||
const PagesCategories = fs
|
||||
.readdirSync(pagesPath)
|
||||
.filter((file) => !file.includes("."));
|
||||
for (const category of PagesCategories) {
|
||||
const pageFiles = fs
|
||||
.readdirSync(`${pagesPath}${category}`)
|
||||
.filter((file) => file.endsWith(".js"));
|
||||
for (const file of pageFiles) {
|
||||
const { default: page } = await import(`.${pagesPath}${category}/${file}`);
|
||||
global.handler[page.type][page.path] = page;
|
||||
console.log(
|
||||
`\x1b[32mChargement de POST: ${page.path} !\x1b[0m`,
|
||||
);
|
||||
numberOfPages++;
|
||||
console.log(numberOfPages + " pages chargées ! ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { deployHandler };
|
17
utils/navbar.js
Normal file
17
utils/navbar.js
Normal file
@ -0,0 +1,17 @@
|
||||
function navbar(session) {
|
||||
let logField;
|
||||
if(session.user) {
|
||||
logField = `<li style="float:right"><a class="active" href="/dashboard">${session.user.username}</a></li>`
|
||||
} else {
|
||||
logField = `<li style="float:right"><a class="active" href="/login">Login</a></li>`;
|
||||
}
|
||||
return `
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/navbar.css" />
|
||||
<ul>
|
||||
<li><a href="/">Accueil</a></li>
|
||||
<li><a href="/calls">Phoning</a></li>
|
||||
<li><a href="#nope">Nope</a></li>
|
||||
${logField}
|
||||
</ul>`;
|
||||
}
|
||||
export { navbar };
|
37
utils/permissions.js
Normal file
37
utils/permissions.js
Normal file
@ -0,0 +1,37 @@
|
||||
const permissionBits = {
|
||||
DEFAULT: 1, // 0001
|
||||
CALL: 2, // 0010
|
||||
UNUSED: 4, // 0100
|
||||
ADMIN: 8, // 1000
|
||||
}
|
||||
|
||||
function checkPermissions(userPermissionBits) {
|
||||
// Default if you have no permissions
|
||||
let permissions = {
|
||||
default: false,
|
||||
call: false,
|
||||
unused: false,
|
||||
admin: false,
|
||||
}
|
||||
|
||||
if(userPermissionBits & permissionBits.DEFAULT) {
|
||||
permissions.default = true;
|
||||
}
|
||||
if(userPermissionBits & permissionBits.CALL) {
|
||||
permissions.call = true;
|
||||
}
|
||||
if(userPermissionBits & permissionBits.UNUSED) {
|
||||
permissions.unused = true;
|
||||
}
|
||||
if(userPermissionBits & permissionBits.ADMIN) {
|
||||
permissions.admin = true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
let userPermissionBits = permissionBits.DEFAULT;
|
||||
userPermissionBits |= permissionBits.CALL; // add permission
|
||||
userPermissionBits ^= permissionBits.ADMIN; // toggle permission
|
||||
userPermissionBits &= (~permissionBits.CALL); // remove permission
|
||||
*/
|
||||
|
||||
export { permissionBits, checkPermissions };
|
98
web.js
98
web.js
@ -1,41 +1,85 @@
|
||||
const express = require('express');
|
||||
const { readFile } = require('fs').promises;
|
||||
const { generateCallHTML } = require('./src/html/callHTML.js');
|
||||
const { getRandomUser } = require('./src/database/getUser.js');
|
||||
import express from "express";
|
||||
import session from "express-session";
|
||||
import { deployHandler } from "./utils/handler.js";
|
||||
import favicon from "serve-favicon";
|
||||
|
||||
function launchWeb(submitEvent){
|
||||
async function launchWeb() {
|
||||
const app = express();
|
||||
|
||||
app.use(express.json()); // Used to parse JSON bodies
|
||||
app.use(express.urlencoded({ extended: false })); //Parse URL-encoded bodies
|
||||
app.use(
|
||||
session({
|
||||
secret: process.env.SECRET,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
}),
|
||||
);
|
||||
|
||||
app.use('/assets', express.static('./www/assets'))
|
||||
app.use("/assets", express.static(`${process.env.WWW}/assets`));
|
||||
app.use(favicon(`${process.env.WWW}/assets/images/favicon.ico`));
|
||||
|
||||
app.get('/', async (request, response) => {
|
||||
response.send(await readFile('./www/index.html', 'utf8'));
|
||||
});
|
||||
app.get('/call', async (request, response) => {
|
||||
response.send(await generateCallResponse());
|
||||
await deployHandler();
|
||||
|
||||
app.post("*", async (request, response) => {
|
||||
console.log("POST: " + request.originalUrl);
|
||||
let handled = global.handler.post[request.originalUrl];
|
||||
|
||||
if(!handled) for(const path of Object.keys(global.handler.post)) {
|
||||
if(new RegExp(path).test(request.originalUrl)) handled = global.handler.post[path];
|
||||
}
|
||||
|
||||
if (!handled) return console.log(request.originalUrl);
|
||||
if (handled.requiresLogin && !request.session.user) {
|
||||
return response.redirect("/login");
|
||||
}
|
||||
if (handled.permissions) {
|
||||
if((handled.permissions & request.session.user.permissions) == 0) return response.status(403).send("Vous n'avez pas la permission d'effectuer cette action !");
|
||||
}
|
||||
return await handled.execute(
|
||||
request,
|
||||
response,
|
||||
);
|
||||
});
|
||||
|
||||
app.post('/inputs/call', async (request, response) => {
|
||||
submitEvent.emit('call', request.body);
|
||||
response.send(await generateCallResponse());
|
||||
});
|
||||
app.post('/inputs/add', async (request, response) => {
|
||||
submitEvent.emit('add', request);
|
||||
app.get("*", async (request, response) => {
|
||||
const [path, args] = parseURL(request.originalUrl);
|
||||
//console.log(parseURL(request.originalUrl));
|
||||
console.log(`GET: ${path}${args ? "?" + args : ""}`);
|
||||
let handled = global.handler.get[path];
|
||||
|
||||
if(!handled) for(const path of Object.keys(global.handler.get)) {
|
||||
if(new RegExp(path).test(request.originalUrl)) handled = global.handler.get[path];
|
||||
}
|
||||
|
||||
if (!handled) return;
|
||||
if (handled.requiresLogin && !request.session.user) {
|
||||
return response.redirect("/login");
|
||||
}
|
||||
if (handled.permissions) {
|
||||
if((handled.permissions & request.session.user.permissions) == 0) return response.status(403).send("Vous n'avez pas la permission d'accéder cette page !");
|
||||
}
|
||||
return await handled.execute(request, response, args);
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => { console.log(`App available at http://localhost:${PORT}`) });
|
||||
};
|
||||
|
||||
async function generateCallResponse(){
|
||||
const user = await getRandomUser();
|
||||
let callHTML;
|
||||
if(user) callHTML = generateCallHTML( user.phone, user.name, user.count );
|
||||
else callHTML = `Plus d'utilisateur à appeler !`;
|
||||
return callHTML;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`App available at http://localhost:${PORT}`);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { launchWeb };
|
||||
function parseURL(URL) {
|
||||
const spURL = URL.split("?");
|
||||
const path = spURL[0];
|
||||
if(!spURL[1]) return [path, {}];
|
||||
|
||||
const args = {};
|
||||
for (let i = 0; i < spURL[1].split("=").length; i+=2) {
|
||||
const key = spURL[1].split("=")[i];
|
||||
const val = spURL[1].split("=")[i+1];
|
||||
args[key] = val;
|
||||
}
|
||||
return [path, args];
|
||||
}
|
||||
|
||||
export { launchWeb };
|
||||
|
121
www/assets/css/forms/login.css
Normal file
121
www/assets/css/forms/login.css
Normal file
@ -0,0 +1,121 @@
|
||||
|
||||
body {font-family: Arial, Helvetica, sans-serif;}
|
||||
|
||||
/* Full-width input fields */
|
||||
input[type=text], input[type=password] {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Set a style for all buttons */
|
||||
button {
|
||||
background-color: #04AA6D;
|
||||
color: white;
|
||||
padding: 14px 20px;
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Extra styles for the cancel button */
|
||||
.cancelbtn {
|
||||
width: auto;
|
||||
padding: 10px 18px;
|
||||
background-color: #f44336;
|
||||
}
|
||||
|
||||
/* Center the image and position the close button */
|
||||
.imgcontainer {
|
||||
text-align: center;
|
||||
margin: 24px 0 12px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
img.avatar {
|
||||
width: 40%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
span.psw {
|
||||
float: right;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
/* The Modal (background) */
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1; /* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
background-color: rgb(0,0,0); /* Fallback color */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
/* Modal Content/Box */
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */
|
||||
border: 1px solid #888;
|
||||
width: 80%; /* Could be more or less, depending on screen size */
|
||||
}
|
||||
|
||||
/* The Close Button (x) */
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
top: 0;
|
||||
color: #000;
|
||||
font-size: 35px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Add Zoom Animation */
|
||||
.animate {
|
||||
-webkit-animation: animatezoom 0.6s;
|
||||
animation: animatezoom 0.6s
|
||||
}
|
||||
|
||||
@-webkit-keyframes animatezoom {
|
||||
from {-webkit-transform: scale(0)}
|
||||
to {-webkit-transform: scale(1)}
|
||||
}
|
||||
|
||||
@keyframes animatezoom {
|
||||
from {transform: scale(0)}
|
||||
to {transform: scale(1)}
|
||||
}
|
||||
|
||||
/* Change styles for span and cancel button on extra small screens */
|
||||
@media screen and (max-width: 300px) {
|
||||
span.psw {
|
||||
display: block;
|
||||
float: none;
|
||||
}
|
||||
.cancelbtn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
68
www/assets/css/navbar.css
Normal file
68
www/assets/css/navbar.css
Normal file
@ -0,0 +1,68 @@
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
li a {
|
||||
display: block;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 14px 16px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
li a:hover:not(.active) {
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #04AA6D;
|
||||
}
|
||||
|
||||
.dropbtn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background-color: #f9f9f9;
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
color: black;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {background-color: #f1f1f1;}
|
||||
|
||||
.dropdown:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown:hover .dropbtn {
|
||||
background-color: #3e8e41;
|
||||
}
|
BIN
www/assets/images/favicon.ico
Normal file
BIN
www/assets/images/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 967 B |
15
www/dashboard/index.html
Normal file
15
www/dashboard/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>AutoCallMenu</title>
|
||||
</head>
|
||||
<body>
|
||||
<NAVBAR>
|
||||
<h1>Dashboard</h1>
|
||||
<p><a href='/calls'>Phoning</a></p>
|
||||
<p><a href='/dashboard/users/create'>Ajouter un utilisateur</a></p>
|
||||
<p><a href='/logout'>Déconnexion</a></p>
|
||||
</body>
|
||||
</html>
|
122
www/dashboard/users/create.html
Normal file
122
www/dashboard/users/create.html
Normal file
@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Add padding to containers */
|
||||
.container {
|
||||
padding: 16px;
|
||||
background-color: white;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 35px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Full-width input fields */
|
||||
input[type=text], input[type=password] {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
margin: 5px 0 22px 0;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
input[type=text]:focus, input[type=password]:focus {
|
||||
background-color: #ddd;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Overwrite default styles of hr */
|
||||
hr {
|
||||
border: 1px solid #f1f1f1;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
/* Set a style for the submit button */
|
||||
.registerbtn {
|
||||
background-color: #04AA6D;
|
||||
color: white;
|
||||
padding: 16px 20px;
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.registerbtn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Add a blue text color to links */
|
||||
a {
|
||||
color: dodgerblue;
|
||||
}
|
||||
|
||||
/* Set a grey background color and center the text of the "sign in" section */
|
||||
.signin {
|
||||
background-color: #f1f1f1;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function setAction(form) {
|
||||
form.action = `/api/users/${form.username.value}/create`;
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<NAVBAR>
|
||||
<form action="/api/users/:username/create" method="post" onsubmit="return setAction(this)">
|
||||
<div class="container">
|
||||
<h1>Création de compte</h1>
|
||||
<p>Formulaire de création de compte.</p>
|
||||
<hr>
|
||||
|
||||
<label for="username"><b>Nom d'utilisateur</b></label>
|
||||
<input type="text" placeholder="Entrez le nom d'utilisateur" name="username" id="username" required>
|
||||
|
||||
<label for="psw"><b>Mot de passe</b></label>
|
||||
<input type="password" placeholder="Mot de passe" name="password" id="psw" required>
|
||||
|
||||
<label for="psw-repeat"><b>Mot de passe (confirmation)</b></label>
|
||||
<input type="password" placeholder="Répétez le mot de passe" name="password-repeat" id="psw-repeat" required>
|
||||
<h4>Permissions</h4>
|
||||
<label class="container">Appels
|
||||
<input type="checkbox" name="permissions" value="2">
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
<!--<label class="container">Unused
|
||||
<input type="checkbox">
|
||||
<span class="checkmark"></span>
|
||||
</label>-->
|
||||
<label class="container">Admin
|
||||
<input type="checkbox" name="permissions" value="8">
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
<hr>
|
||||
|
||||
<button type="submit" class="registerbtn">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -7,7 +7,8 @@
|
||||
<title>AutoCallMenu</title>
|
||||
</head>
|
||||
<body>
|
||||
<NAVBAR>
|
||||
<h1>Bienvenue :3</h1>
|
||||
<a href="/call"><p>Accéder aux téléphones</p></a>
|
||||
<a href="/calls"><p>Accéder aux téléphones</p></a>
|
||||
</body>
|
||||
</html>
|
||||
|
31
www/login.html
Normal file
31
www/login.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title></title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/assets/css/forms/login.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<NAVBAR>
|
||||
<form class="modal-content animate" action="/login" method="post">
|
||||
<div class="container">
|
||||
<label for="uname"><b>Username</b></label>
|
||||
<input type="text" placeholder="Enter Username" name="username" required>
|
||||
|
||||
<label for="psw"><b>Password</b></label>
|
||||
<input type="password" placeholder="Enter Password" name="password" required>
|
||||
|
||||
<button type="submit">Login</button>
|
||||
<!--<label>
|
||||
<input type="checkbox" checked="checked" name="remember"> Remember me
|
||||
</label>-->
|
||||
</div>
|
||||
|
||||
<div class="container" style="background-color:#f1f1f1">
|
||||
<a href="/"><button type="button" class="cancelbtn">Cancel</button></a>
|
||||
<!--<span class="psw">Forgot <a href="#">password?</a></span>-->
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user