Compare commits
28 Commits
6af45b3ee2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c191700fe7 | |||
| abb0c12559 | |||
| b41573dc3f | |||
| f5e0cf2c86 | |||
| 2049457158 | |||
| 8cc00eac05 | |||
| bc9d345fd3 | |||
|
|
8612f6cf09 | ||
| 16f6aa057a | |||
|
|
71c6792c3d | ||
| b7552e3d13 | |||
|
|
912b12c627 | ||
| 900419ca6d | |||
|
|
c9fb03b499 | ||
|
|
464ff4d8b4 | ||
|
|
29258346cd | ||
| 5275c61483 | |||
|
|
aab46e50cb | ||
| 4a68b5a724 | |||
| 88d414ac47 | |||
| 5b0d68d66f | |||
| 756f928ced | |||
| 48aa16962c | |||
| bdab795506 | |||
|
|
e51b39433f | ||
|
|
05ed416944 | ||
|
|
fd6c4de7f5 | ||
|
|
98dc96ad7c |
7
.env
7
.env
@@ -1,5 +1,8 @@
|
|||||||
DATABASE = mia_dev
|
DATABASE = mia_dev
|
||||||
DATABASE_HOST = db.thecoredev.fr
|
DATABASE_HOST = nas.thecoredev.fr
|
||||||
DATABASE_PORT = 3306
|
DATABASE_PORT = 3306
|
||||||
DATABASE_USER = mia
|
DATABASE_USER = mia
|
||||||
DATABASE_PASSWORD = CalomOk0t-ISvpw-
|
DATABASE_PASSWORD = CalomOk0t-ISvpw-
|
||||||
|
|
||||||
|
MASTER_URL = 'https://master.projetmia.fr'
|
||||||
|
SLAVE_URL = 'https://slave.projetmia.fr'
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
DATABASE = mia
|
DATABASE = mia
|
||||||
DATABASE_HOST = db.thecoredev.fr
|
DATABASE_HOST = 127.0.0.1
|
||||||
DATABASE_PORT = 3306
|
DATABASE_PORT = 3306
|
||||||
DATABASE_USER = mia
|
DATABASE_USER = mia
|
||||||
DATABASE_PASSWORD = CalomOk0t-ISvpw-
|
DATABASE_PASSWORD = CalomOk0t-ISvpw-
|
||||||
|
|
||||||
|
MASTER_URL = 'https://master.projetmia.fr'
|
||||||
|
SLAVE_URL = 'https://slave.projetmia.fr'
|
||||||
|
|||||||
184
README.md
Normal file
184
README.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# Création d'une IHM pour le projet MIA
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Ce projet couvre les IHM d'accès aux sessions de visio-conférence pour le projet MIA.
|
||||||
|
|
||||||
|
Ces IHM se découpent en deux étapes :\
|
||||||
|
1 - connexion d'un utilisateur sur une session de visio\
|
||||||
|
2 - identification de l'utilisateur sur la session
|
||||||
|
|
||||||
|
## Architecture du module
|
||||||
|
|
||||||
|
```
|
||||||
|
mia
|
||||||
|
├── ...
|
||||||
|
├── node_modules
|
||||||
|
│ ├── Tous les modules Node nécessaires à l'application (body-parser, bcrypt, express, ...)
|
||||||
|
├── public
|
||||||
|
│ ├── functions.js (contient des fonctions Javascript utilisées globalement sur tout le projet)
|
||||||
|
│ ├── session.js (fonctions spécifiques à l'écran de création des sessions)
|
||||||
|
│ ├── styles.css (feuilles de style globales de l'appli)
|
||||||
|
├── views
|
||||||
|
│ ├── create-session.hbs (écran de création d'une session de visio)
|
||||||
|
│ ├── login-session.hbs (écran de connexion sur une session)
|
||||||
|
│ ├── login.hbs (écran d'identification de l'utilisateur)
|
||||||
|
│ ├── login.hbs.old (DEPRECATED : ancienne version de l'identification de l'utilisateur, comprenant la saisie d'une adresse email ou d'un pseudo)
|
||||||
|
│ ├── register.hbs (DEPRECATED : écran de création du compte utilisateur)
|
||||||
|
├──.env (variables globales, pour l'environnement de test)
|
||||||
|
├──.env.prod (idem, pour l'environnement de prod)
|
||||||
|
├──mia_ihm.js (....)
|
||||||
|
├──package-lock.json
|
||||||
|
├──package.json
|
||||||
|
├──README.md (le présent fichier)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation des dépendances
|
||||||
|
|
||||||
|
Exemple de commande :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install bcrypt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Description des différents écrans
|
||||||
|
|
||||||
|
### create-session
|
||||||
|
|
||||||
|
Cet écran permet la création unitaire d'une session de visio.
|
||||||
|
Ceci permet à n'importe quel utilisateur de créer une session à venir --> cette session apparaitra dans la liste présente sur la page des sessions.
|
||||||
|
|
||||||
|
#### Accès à l'écran
|
||||||
|
|
||||||
|
Depuis l'écran de choix de la session, via un lien hypertexte situé au bas du formulaire.
|
||||||
|
|
||||||
|
#### Contrôles mis en place
|
||||||
|
|
||||||
|
* Le mot de passe (et sa confirmation) doivent être identiques, sinon message d'erreur.
|
||||||
|
* Champs obligatoires : **intitulé** et **date de début**
|
||||||
|
* Le nombre de participants (si renseigné) doit être un nombre entier strictement positif.
|
||||||
|
|
||||||
|
#### Actions effectuées depuis cet écran
|
||||||
|
|
||||||
|
A la soumission du formulaire, si tous les contrôles sont passants, une nouvelle session est créée en base de données dans la table "session".\
|
||||||
|
Y sont stockés :
|
||||||
|
* un UUID généré automatiquement (champ id)
|
||||||
|
* l'intitulé de la session (champ topic)
|
||||||
|
* le mot de passe de la session (champ password, chiffré via le module bcrypt)
|
||||||
|
* la date/heure de début (scheduled_on), au format YYYYMMDDHHmm
|
||||||
|
* le nombre de participants attendus (champ nb_of_attended)
|
||||||
|
* le nombre de participants réel (champ nb_of_participants) valorisé par défaut à 0
|
||||||
|
|
||||||
|
L'utilisateur est ensuite rédirigé vers la page **login-session**
|
||||||
|
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
### login-session
|
||||||
|
|
||||||
|
Ecran d'accueil du site, il permet à l'utilisateur de se connecter sur une session existante (listées dans une liste déroulante).
|
||||||
|
|
||||||
|
Dans cet écran, l'utilisateur peut préciser son rôle ("Présentateur" ou simple "Auditeur"), sur la session qu'il aura sélectionné.\
|
||||||
|
Par défaut, l'utilisateur est considéré comme "Auditeur".
|
||||||
|
|
||||||
|
|
||||||
|
#### Accès à l'écran
|
||||||
|
|
||||||
|
* Page d'accueil du site
|
||||||
|
* Via le lien "Projet MIA" apparaissant dans le header du site
|
||||||
|
|
||||||
|
#### Contrôles mis en place
|
||||||
|
|
||||||
|
* Le mot de passe saisi par l'utilisateur est contrôlé (après déchiffremment) et comparé avec celui stocké en BDD --> si cela ne correspond pas, un message d'erreur apparait à l'utilisateur.
|
||||||
|
* L'utilisateur doit saisir un mot de passe.
|
||||||
|
|
||||||
|
#### Actions effectuées depuis cet écran
|
||||||
|
|
||||||
|
* L'utilisateur peut créer une nouvelle session, via le lien hypertexte _"Besoin d'une nouvelle session ?..."_
|
||||||
|
* Au clic sur le bouton, l'existence de la session (et la conformité de son mot de passe) sont vérifiées.
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
### login
|
||||||
|
|
||||||
|
Ecran qui permet à l'utilisateur de s'identifier sur la session via l'identifiant de son choix : il peut saisir un texte quelconque ou bien une adresse email, par exemple.
|
||||||
|
|
||||||
|
#### Accès à l'écran
|
||||||
|
|
||||||
|
Depuis la page des sessions, après vérification de l'accès autorisé à la session.
|
||||||
|
|
||||||
|
#### Contrôles mis en place
|
||||||
|
|
||||||
|
* L'identifiant saisi n'a pas de format particulier.
|
||||||
|
* L'identifiant ne doit pas déjà être utilisé sur la session préalablement choisie par le client --> ce contrôle s'effectue en consultant les utilisateurs déjà déclarés sur la session en cours de saisie (table **participation**)
|
||||||
|
|
||||||
|
#### Actions effectuées depuis cet écran
|
||||||
|
|
||||||
|
Au clic sur le bouton "Se connecter", si les contrôles sont passants, l'utilisateur sera rédirigé vers une page Web dont l'URL est définie dans le fichier .env (donnée MASTER_URL ou SLAVE_URL en fonction du rôle que l'utilisateur s'est choisi sur l'écran des sessions) :
|
||||||
|
* si rôle = "Présentateur" --> MASTER_URL
|
||||||
|
* si rôle = "Auditeur" --> SLAVE_URL
|
||||||
|
|
||||||
|
Simultanément, la présence de l'utilisateur sur la session est tracée :
|
||||||
|
* incrémentation du compteur des participants, dans la table **session**
|
||||||
|
* création de l'utilisateur dans la table **user** avec un identifiant unique (UUID) auto-généré
|
||||||
|
* enregistrement de la présence de l'utilisateur sur la session, dans la table **participation**
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
### login.old
|
||||||
|
|
||||||
|
**DEPRECATED**
|
||||||
|
|
||||||
|
Suite à un point avec l'équipe Cortex le 16/11/2023, cet écran a été retiré et remplacé par une version simplifiée (avec uniquement la saisie d'un identifiant).
|
||||||
|
|
||||||
|
Cet écran a pour objectif de permettre à l'utilisateur de s'identifier via la saisie de son identifiant (soit un pseudo, soit une adresse email) et du mot de passe associé.
|
||||||
|
|
||||||
|
#### Accès à l'écran
|
||||||
|
|
||||||
|
**A date, plus d'accès possible à cet écran**
|
||||||
|
|
||||||
|
#### Contrôles mis en place
|
||||||
|
|
||||||
|
* L'utilisateur doit avoir saisi soit un pseudo soit une adresse email
|
||||||
|
* On vérifie que l'identifiant saisi n'est pas déjà utilisé --> si c'est le cas, l'utilisateur sera informé
|
||||||
|
* L'utilisateur doit saisir (et confirmer) un mot de passe
|
||||||
|
|
||||||
|
#### Actions effectuées depuis cet écran
|
||||||
|
|
||||||
|
* L'utilisateur peut créer un nouveau compte via le lien hypertexte présent au bas du fomulaire (_"Pas encore inscrit ?..."_)--> redirection vers la page **register**
|
||||||
|
* Au clic sur le bouton _"Se connecter"_, le nombre de participants sur la session (table **session**) est incrémenté et l'utilisateur est ajouté dans la table **participation**
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
### register
|
||||||
|
|
||||||
|
**DEPRECATED**
|
||||||
|
|
||||||
|
Cet écran permet de créer un nouveau compte utilisateur.
|
||||||
|
|
||||||
|
Il est demandé (obligatoire) à l'utilisateur d'y saisir :
|
||||||
|
* un identifiant (soit l'adresse email soit le pseudonyme)
|
||||||
|
* un mot de passe (et sa confirmation)
|
||||||
|
|
||||||
|
Il est proposé (facultatif) de renseigner :
|
||||||
|
* Son genre (Monsieur ou Madame)
|
||||||
|
* Som nom et/ou son prénom
|
||||||
|
* Son titre ou fonction
|
||||||
|
|
||||||
|
|
||||||
|
#### Accès à l'écran
|
||||||
|
|
||||||
|
**A date, plus d'accès possible à cet écran**
|
||||||
|
|
||||||
|
#### Contrôles mis en place
|
||||||
|
|
||||||
|
* Les 2 mots de passe doivent concorder, sinon message d'erreur
|
||||||
|
* L'utilisateur doit avoir renseigné au moins le pseudo ou l'adresse email (il peut renseigner les deux, et plus)
|
||||||
|
* L'identifiant ne doit pas déjà être connu dans la BDD
|
||||||
|
|
||||||
|
#### Actions effectuées depuis cet écran
|
||||||
|
|
||||||
|
* Si l'utilisateur s'aperçoit qu'il a finalement déjà un compte, il peut abandoinner la création d'un nouveau compte en cliquant sur le lien _"Déjà inscrit ?..."_
|
||||||
|
* Au clic sur le bouton _"M'enregistrer"_, l'utilisateur sera créé en base de données et un identifiant unique (UUID) lui sera automatiquement attribué en BDD.
|
||||||
@@ -30,19 +30,10 @@ db.connect((error) => {
|
|||||||
app.set('view engine', 'hbs');
|
app.set('view engine', 'hbs');
|
||||||
app.use(express.static(publicDir));
|
app.use(express.static(publicDir));
|
||||||
app.use('/css', express.static(__dirname + '/node_modules/bootstrap/dist/css'));
|
app.use('/css', express.static(__dirname + '/node_modules/bootstrap/dist/css'));
|
||||||
|
app.use('/css', express.static(__dirname + '/node_modules/@eonasdan/tempus-dominus/dist/css'));
|
||||||
|
app.use('/js', express.static(__dirname + '/node_modules/@eonasdan/tempus-dominus/dist/js'));
|
||||||
app.use(bodyParser.urlencoded({extended: false}));
|
app.use(bodyParser.urlencoded({extended: false}));
|
||||||
app.use(express.json())
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
|
||||||
async function recupListeSessions() {
|
|
||||||
let today = functions.getNowDate("yyyymmdd");
|
|
||||||
|
|
||||||
// On récupère la liste des sessions actives et pour lesquelles il reste de la place
|
|
||||||
db.query('SELECT ID, topic, DATE_FORMAT(scheduled_on, "%d/%m/%Y") as "date", DATE_FORMAT(scheduled_on, "%Hh%i") as "heure", IF(nb_of_attended-nb_of_participants=0, true, false) as "maxAtteint" FROM session WHERE DATE_FORMAT(scheduled_on, "%Y%m%d") >= ?', [today], async (error, result) => {
|
|
||||||
if(error){ console.log(error); }
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ******************************************* Arrivée sur la page d'accueil *******************************************
|
// ******************************************* Arrivée sur la page d'accueil *******************************************
|
||||||
@@ -63,13 +54,10 @@ app.get("/", (req, res) => {
|
|||||||
|
|
||||||
app.get("/index", (req, res) => { res.render("index") });
|
app.get("/index", (req, res) => { res.render("index") });
|
||||||
app.get("/login", (req, res) => { res.render("login") });
|
app.get("/login", (req, res) => { res.render("login") });
|
||||||
app.get("/register", (req, res) => {
|
app.get("/register", (req, res) => { res.status(404).send('Page Not found'); /* res.render("register", { session: req.query.s, role: req.query.r}) */ });
|
||||||
res.render("register", { session: req.query.s, role: req.query.r})
|
|
||||||
});
|
|
||||||
app.get("/create-session", (req, res) => { res.render("create-session") });
|
app.get("/create-session", (req, res) => { res.render("create-session") });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ******************************************* Création d'un nouveau compte *******************************************
|
// ******************************************* Création d'un nouveau compte *******************************************
|
||||||
app.post("/auth/register", (req, res) => {
|
app.post("/auth/register", (req, res) => {
|
||||||
const { gender, name, firstname, nickname, title, email, password, password_confirm, session, role } = req.body
|
const { gender, name, firstname, nickname, title, email, password, password_confirm, session, role } = req.body
|
||||||
@@ -164,12 +152,8 @@ app.post("/auth/check-login-no-security", (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == 'A') {
|
if (role == 'A') { res.redirect(process.env.MASTER_URL); }
|
||||||
res.redirect('https://slave.thecoredev.fr');
|
else { res.redirect(process.env.SLAVE_URL); }
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.redirect('https://slave.thecoredev.fr');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -210,13 +194,10 @@ app.post("/auth/check-login", (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userId = result[0].ID;
|
userId = result[0].ID;
|
||||||
if (functions.comparePassword(password, result[0].password)) {
|
|
||||||
// Le user est connecté avec succès : on vérifie qu'il n'est pas déjà inscrit à la session et si pas le cas, on l'inscrit et on incrémente le compteur des participants
|
|
||||||
db.query('SELECT * FROM participation WHERE user = ?', [userId], async (error, result) => {
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
bcrypt.compare(password, result[0].password)
|
||||||
|
.then(result => {
|
||||||
|
if (result) {
|
||||||
if (result.length == 0) {
|
if (result.length == 0) {
|
||||||
db.query('INSERT INTO participation (user, session, role_during_session) VALUES (?,?,?)', [userId, session[0], role[0]], function (err, result) {
|
db.query('INSERT INTO participation (user, session, role_during_session) VALUES (?,?,?)', [userId, session[0], role[0]], function (err, result) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
@@ -228,22 +209,18 @@ app.post("/auth/check-login", (req, res) => {
|
|||||||
console.log("1 record updated");
|
console.log("1 record updated");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if (role == 'A') {
|
if (role == 'A') { res.redirect('https://slave.thecoredev.fr'); }
|
||||||
res.redirect('https://slave.thecoredev.fr');
|
else { res.redirect('https://slave.thecoredev.fr'); }
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
res.redirect('https://slave.thecoredev.fr');
|
return res.render('login', {
|
||||||
|
error: 'Mot de passe incorrect : corriger votre saisie',
|
||||||
|
session: session,
|
||||||
|
role: role
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
else {
|
|
||||||
return res.render('login', {
|
|
||||||
error: 'Mot de passe incorrect : corriger votre saisie',
|
|
||||||
"session": session,
|
|
||||||
"role": role
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
"name": "mia",
|
"name": "mia",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "test",
|
"description": "test",
|
||||||
"main": "app.js",
|
"main": "mia_ihm.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "nodemon --inspect app.js"
|
"start": "nodemon mia_ihm.js"
|
||||||
},
|
},
|
||||||
"author": "Laurent LE CORRE",
|
"author": "Laurent LE CORRE",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
// DatePicker sur l'écran de création d'une session
|
// DatePicker sur l'écran de création d'une session
|
||||||
window.datetimepicker1 = $('#datetimepicker1');
|
window.datetimepicker1 = $('#datetimepicker1');
|
||||||
|
|
||||||
datetimepicker1.tempusDominus({
|
datetimepicker1.tempusDominus({
|
||||||
//put your config here
|
|
||||||
allowInputToggle: true,
|
allowInputToggle: true,
|
||||||
stepping: 15,
|
stepping: 15,
|
||||||
localization: {
|
localization: {
|
||||||
40
schema.sql
Normal file
40
schema.sql
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
CREATE DATABASE `mia`;
|
||||||
|
|
||||||
|
USE `mia`;
|
||||||
|
|
||||||
|
-- mia.participation definition
|
||||||
|
|
||||||
|
CREATE TABLE `participation` (
|
||||||
|
`user` uuid DEFAULT NULL,
|
||||||
|
`session` uuid DEFAULT NULL,
|
||||||
|
`role_during_session` varchar(100) DEFAULT 'A'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
|
||||||
|
-- mia.`session` definition
|
||||||
|
|
||||||
|
CREATE TABLE `session` (
|
||||||
|
`id` uuid NOT NULL,
|
||||||
|
`scheduled_on` datetime DEFAULT NULL,
|
||||||
|
`topic` varchar(100) DEFAULT NULL,
|
||||||
|
`password` varchar(255) DEFAULT NULL,
|
||||||
|
`nb_of_attended` int(11) DEFAULT NULL,
|
||||||
|
`nb_of_participants` int(11) DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
|
||||||
|
-- mia.`user` definition
|
||||||
|
|
||||||
|
CREATE TABLE `user` (
|
||||||
|
`id` uuid NOT NULL,
|
||||||
|
`name` varchar(30) DEFAULT NULL,
|
||||||
|
`firstname` varchar(50) DEFAULT NULL,
|
||||||
|
`nickname` varchar(100) DEFAULT NULL,
|
||||||
|
`title` varchar(100) DEFAULT NULL,
|
||||||
|
`gender` varchar(1) DEFAULT NULL,
|
||||||
|
`email` varchar(200) DEFAULT NULL,
|
||||||
|
`password` varchar(255) DEFAULT NULL,
|
||||||
|
`session` uuid DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.10/dist/js/tempus-dominus.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.10/dist/js/tempus-dominus.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.10/dist/js/jQuery-provider.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.10/dist/js/jQuery-provider.js"></script>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.10/dist/css/tempus-dominus.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.10/dist/css/tempus-dominus.css" />
|
||||||
<script type="module" src="/scripts-session.js"></script>
|
<script type="module" src="/session.js"></script>
|
||||||
</head>
|
</head>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Reference in New Issue
Block a user