Version 0.7.1 - Ajout des logs

This commit is contained in:
CICD - Pipeline 2023-08-31 19:31:09 +02:00
parent 30a0208cf1
commit 51820268cd
12 changed files with 331 additions and 172 deletions

View File

@ -79,15 +79,59 @@
"thumbnail": "https://img.youtube.com/vi/U-RyWK2DFS8/default.jpg" "thumbnail": "https://img.youtube.com/vi/U-RyWK2DFS8/default.jpg"
}, },
{ {
"track": "QAAA5QIAC1RvdXQgQ2hhbmdlABBqdWxpZW5sYW1hc3Nvbm5lAAAAAAADBqkAcFU6aHR0cHM6Ly9hcGktdjIuc291bmRjbG91ZC5jb20vbWVkaWEvc291bmRjbG91ZDp0cmFja3M6ODM3ODEzNDYvMWVjYTY3ZmEtZjg3ZS00MjFkLWExYzctNTVhY2M4ZTdkODIyL3N0cmVhbS9obHMAAQAzaHR0cHM6Ly9zb3VuZGNsb3VkLmNvbS9qdWxpZW5sYW1hc3Nvbm5lL3RvdXQtY2hhbmdlAApzb3VuZGNsb3VkAAAAAAAAAAA=", "track": "QAAAnAIAOUlGU0NMIDEzdGggVWx0aW1hdGUgQW5uaXZlcnNhcnkgVHJhaWxlciB8IENvZGUgTHlva28gR2FtZQAJSW1tdWRlbGtpAAAAAAABrbAAC2tZTTV4WUxzMUJBAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9a1lNNXhZTHMxQkEAB3lvdXR1YmUAAAAAAAAAAA==",
"title": "Tout Change", "title": "IFSCL 13th Ultimate Anniversary Trailer | Code Lyoko Game",
"identifier": "U:https://api-v2.soundcloud.com/media/soundcloud:tracks:83781346/1eca67fa-f87e-421d-a1c7-55acc8e7d822/stream/hls", "identifier": "kYM5xYLs1BA",
"author": "julienlamassonne", "author": "Immudelki",
"duration": 198313, "duration": 110000,
"isSeekable": true, "isSeekable": true,
"isStream": false, "isStream": false,
"uri": "https://soundcloud.com/julienlamassonne/tout-change", "uri": "https://www.youtube.com/watch?v=kYM5xYLs1BA",
"thumbnail": "https://img.youtube.com/vi/kYM5xYLs1BA/default.jpg"
},
{
"track": "QAAAfQIAH0fDqW7DqXJpcXVlIFBva8OpbW9uIENocm9uaWNsZXMABHRvdG8AAAAAAACUcAALSzRzRm5RTFV1ZXcAAQAraHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1LNHNGblFMVXVldwAHeW91dHViZQAAAAAAAAAA",
"title": "Générique Pokémon Chronicles",
"identifier": "K4sFnQLUuew",
"author": "toto",
"duration": 38000,
"isSeekable": true,
"isStream": false,
"uri": "https://www.youtube.com/watch?v=K4sFnQLUuew",
"thumbnail": "https://img.youtube.com/vi/K4sFnQLUuew/default.jpg"
},
{
"track": "QAABEAIALVJlcGxpY2F0aW9uIG9mIENpbmVtb3JwaGlxdWUgLSBOZXcgQ2hhbGxlbmdlcwAGUmFwaGl4AAAAAAABqJgAck86aHR0cHM6Ly9hcGktdjIuc291bmRjbG91ZC5jb20vbWVkaWEvc291bmRjbG91ZDp0cmFja3M6MTYwNDMwNjc4NC81YmUxOWNiZS03NTkyLTQ0ODktYjMzMC0yN2RlZGMyYTFjZjIvc3RyZWFtL2hscwABAERodHRwczovL3NvdW5kY2xvdWQuY29tL3JhcGhpeC0zNTkzMDEwNjkvY2luZW1vcnBoaXF1ZS1uZXctY2hhbGxlbmdlcwAKc291bmRjbG91ZAAAAAAAAAAA",
"title": "Replication of Cinemorphique - New Challenges",
"identifier": "O:https://api-v2.soundcloud.com/media/soundcloud:tracks:1604306784/5be19cbe-7592-4489-b330-27dedc2a1cf2/stream/hls",
"author": "Raphix",
"duration": 108696,
"isSeekable": true,
"isStream": false,
"uri": "https://soundcloud.com/raphix-359301069/cinemorphique-new-challenges",
"thumbnail": null "thumbnail": null
},
{
"track": "QAAAqwIANE11cnJheSBIZWFkIC0gT25lIE5pZ2h0IEluIEJhbmdrb2sgKExvbmcgSW50cm8gMTo0NykAHUpvc8OpIE9zdmFsZG8gT3J0aWdvemEg7aC87b6tAAAAAAAEp2gAC1l2RGdNZEdDUmEwAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9WXZEZ01kR0NSYTAAB3lvdXR1YmUAAAAAAAAAAA==",
"title": "Murray Head - One Night In Bangkok (Long Intro 1:47)",
"identifier": "YvDgMdGCRa0",
"author": "José Osvaldo Ortigoza 🎭",
"duration": 305000,
"isSeekable": true,
"isStream": false,
"uri": "https://www.youtube.com/watch?v=YvDgMdGCRa0",
"thumbnail": "https://img.youtube.com/vi/YvDgMdGCRa0/default.jpg"
},
{
"track": "QAAAkAIALkJvbiBKb3ZpIC0gSXQncyBNeSBMaWZlIChPZmZpY2lhbCBNdXNpYyBWaWRlbykACEJvbiBKb3ZpAAAAAAAEEvgAC3Z4MnU1dVV1M0RFAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9dngydTV1VXUzREUAB3lvdXR1YmUAAAAAAAAAAA==",
"title": "Bon Jovi - It's My Life (Official Music Video)",
"identifier": "vx2u5uUu3DE",
"author": "Bon Jovi",
"duration": 267000,
"isSeekable": true,
"isStream": false,
"uri": "https://www.youtube.com/watch?v=vx2u5uUu3DE",
"thumbnail": "https://img.youtube.com/vi/vx2u5uUu3DE/default.jpg"
} }
] ]
} }

View File

@ -5,7 +5,7 @@
"access_token": "jY17lCWaOpTdPQCnz50kfNm7ABpsGz", "access_token": "jY17lCWaOpTdPQCnz50kfNm7ABpsGz",
"expires_in": 604800, "expires_in": 604800,
"refresh_token": "3TronknZ7j5kVSDBjI3OechmCYF8ck", "refresh_token": "3TronknZ7j5kVSDBjI3OechmCYF8ck",
"scope": "identify guilds.members.read guilds" "scope": "guilds.members.read guilds identify"
}, },
"avatar": null, "avatar": null,
"communication_disabled_until": null, "communication_disabled_until": null,
@ -34,80 +34,7 @@
"deaf": false, "deaf": false,
"bio": "", "bio": "",
"banner": null, "banner": null,
"token": "bbc2f9a1-a52c-4548-895b-2c3b9095e2d4" "token": "80009f8e-6caf-4db5-8454-c50471ac3b55",
}, "admin": true
{
"auth": {
"token_type": "Bearer",
"access_token": "jY17lCWaOpTdPQCnz50kfNm7ABpsGz",
"expires_in": 604800,
"refresh_token": "3TronknZ7j5kVSDBjI3OechmCYF8ck",
"scope": "identify guilds guilds.members.read"
},
"avatar": null,
"communication_disabled_until": null,
"flags": 0,
"joined_at": "2023-03-06T18:20:23.413000+00:00",
"nick": "Raphix",
"pending": false,
"premium_since": null,
"roles": [
"397725552968204288"
],
"user": {
"id": "486943594893017119",
"username": "raphixscrap",
"avatar": "7129e80c23aa3ba3d2d45814dd352a29",
"discriminator": "0",
"public_flags": 4194368,
"flags": 4194368,
"banner": null,
"accent_color": 10712214,
"global_name": "Raphix",
"avatar_decoration_data": null,
"banner_color": "#a37496"
},
"mute": false,
"deaf": false,
"bio": "",
"banner": null,
"token": "56a35d77-427f-4ac3-9db8-170f08fac724"
},
{
"auth": {
"token_type": "Bearer",
"access_token": "jY17lCWaOpTdPQCnz50kfNm7ABpsGz",
"expires_in": 604800,
"refresh_token": "3TronknZ7j5kVSDBjI3OechmCYF8ck",
"scope": "guilds.members.read identify guilds"
},
"avatar": null,
"communication_disabled_until": null,
"flags": 0,
"joined_at": "2023-03-06T18:20:23.413000+00:00",
"nick": "Raphix",
"pending": false,
"premium_since": null,
"roles": [
"397725552968204288"
],
"user": {
"id": "486943594893017119",
"username": "raphixscrap",
"avatar": "7129e80c23aa3ba3d2d45814dd352a29",
"discriminator": "0",
"public_flags": 4194368,
"flags": 4194368,
"banner": null,
"accent_color": 10712214,
"global_name": "Raphix",
"avatar_decoration_data": null,
"banner_color": "#a37496"
},
"mute": false,
"deaf": false,
"bio": "",
"banner": null,
"token": "d026da49-07d0-48cf-bfdb-9807ad9765ce"
} }
] ]

View File

@ -1,7 +1,7 @@
{ {
"name": "subsonics-web", "name": "subsonics-web",
"author": "Raphix", "author": "Raphix",
"version": "0.7.0", "version": "0.7.1",
"nodemonConfig": { "nodemonConfig": {
"ext": "js, html", "ext": "js, html",
"ignore": [ "ignore": [

View File

@ -18,6 +18,7 @@ const __glob = {
NODES: root + path.sep + "data" + path.sep + "nodes.json", NODES: root + path.sep + "data" + path.sep + "nodes.json",
README: root + path.sep + "README.md", README: root + path.sep + "README.md",
PLAYLIST: root + path.sep + "data" + path.sep + "playlist.json", PLAYLIST: root + path.sep + "data" + path.sep + "playlist.json",
LOGS: root + path.sep + "src" + path.sep + "modules" + path.sep + "logs"
}; };
const webroot = __glob.WEB_DIR + path.sep const webroot = __glob.WEB_DIR + path.sep

View File

@ -743,7 +743,7 @@ module.exports.updateMusicState = function (client, action) {
data["isOnline"] = false data["isOnline"] = false
} }
clog.log("Actualisation de tous les clients - Titre : " + currentTitle + " - Loop : " + data.loop + " - Shuffle : " + data.shuffle + " - Playing : " + data.playing + " - Volume : " + Math.trunc(data.volume / 10) ) clog.log("Actualisation Clients - Titre : " + currentTitle + " - Loop : " + data.loop + " - Shuffle : " + data.shuffle + " - Playing : " + data.playing + " - Volume : " + Math.trunc(data.volume / 10) )
return data return data

View File

@ -8,7 +8,8 @@ const wlog = new LogType("Web")
const subplayer = require(__glob.SUBPLAYER); const subplayer = require(__glob.SUBPLAYER);
const { List } = require("./sub-list") const { List } = require("./sub-list")
const subplaylist = require("./sub-playlist") const subplaylist = require("./sub-playlist")
var fs = require("fs")
var path = require("path")
module.exports.WebServer = class { module.exports.WebServer = class {
@ -213,6 +214,33 @@ function IOConnection(io) {
}) })
GetRequest(io, socket, "LOGS", () => {
var cookies = socket.handshake.headers.cookie
cookies = cook.parse(cookies)
var token = cookies.token
const user = auth.getUser(token)
const logs_data = new Array()
if(user.admin == true) {
const logs_folder = fs.readdirSync(__glob.LOGS)
for(var log of logs_folder) {
logs_data.push({"name":log, "value": fs.readFileSync(__glob.LOGS + path.sep + log).toString()})
}
socket.emit("ANSWER/GET/LOGS", logs_data)
}
})
@ -269,6 +297,8 @@ function IOConnection(io) {
}) })
GetRequest(io, socket, "SPECIAL/MJ", () => { GetRequest(io, socket, "SPECIAL/MJ", () => {

View File

@ -21,7 +21,7 @@ function get(request) {
socket.once("ANSWER/GET/" + request, (answer) => { socket.once("ANSWER/GET/" + request, (answer) => {
console.log("Réponse pour la requête : " + request) console.log("Réponse pour la requête : " + request)
resolve(answer) resolve(answer)
}) })

View File

@ -13,9 +13,8 @@ const backward = document.getElementById("backward")
const play = document.getElementById("play") const play = document.getElementById("play")
const forward = document.getElementById("forward") const forward = document.getElementById("forward")
const settings_dialog = document.getElementById("SETTINGS_dialog")
const settings_close = document.getElementById("SETTINGS_close") const settingsBtn = document.getElementById("settings_btn")
const settingsBtn = document.getElementById("settingsBtn")
@ -109,10 +108,6 @@ socket.on("DO_UPDATE_PLAYLIST", () => {
get("PLAYLIST") get("PLAYLIST")
}) })
get("PLAYLIST") get("PLAYLIST")
socket.on("ANSWER/GET/PLAYLIST", (data) => { socket.on("ANSWER/GET/PLAYLIST", (data) => {
@ -644,10 +639,7 @@ disconnect.addEventListener("click", () => {
}) })
/*settingsBtn.addEventListener("click", () => {
settings_dialog.showModal()
})*/
reportBtn.addEventListener("click", () => { reportBtn.addEventListener("click", () => {
@ -678,10 +670,6 @@ shuffle.addEventListener("click", () => {
}) })
settings_close.addEventListener("click",() => {
settings_dialog.close()
})
var durationAll = 0 var durationAll = 0
var durationProgress = 0 var durationProgress = 0
@ -697,6 +685,8 @@ durationBar.addEventListener("change", () => {
userInfo.then(user => { userInfo.then(user => {
settingsBtn.style.display = "none"
var betastar = "" var betastar = ""
if(user.beta) { if(user.beta) {
@ -704,6 +694,11 @@ userInfo.then(user => {
betastar = "<p class='beta'>BETA</p>" betastar = "<p class='beta'>BETA</p>"
} }
if(user.admin == true) {
settingsBtn.style.display = "flex"
}
userInfoDiv.innerHTML = "<div class='INDEX_userInfo_name'><p class='INDEX_gbname'>" + user.user.global_name + "</p><p class='INDEX_usrname'>" + user.user.username + "</p></div><div class='INDEX_picture'><img src='https://cdn.discordapp.com/avatars/" + user.user.id + "/" + user.user.avatar + "'>" + betastar + "</div>" userInfoDiv.innerHTML = "<div class='INDEX_userInfo_name'><p class='INDEX_gbname'>" + user.user.global_name + "</p><p class='INDEX_usrname'>" + user.user.username + "</p></div><div class='INDEX_picture'><img src='https://cdn.discordapp.com/avatars/" + user.user.id + "/" + user.user.avatar + "'>" + betastar + "</div>"

View File

@ -0,0 +1,165 @@
settingsBtn.addEventListener("click", () => {
mainView.innerHTML = `
<div class="SETTINGS">
<div class='ssidebar'>
<div id="settings_logs" class="sside_line"><i class="fa-solid fa-newspaper"></i> Logs</div>
<div id="settings_connexion" class="sside_line"><i class="fa-solid fa-tower-cell"></i> Connexions</div>
<div id="settings_users" class="sside_line"><i class="fa-solid fa-people-group"></i> Utilisateurs</div>
</div>
<div id="settings_content" class='SETTINGS_content'>
<div class=""><i class="fa fa-search"></i> Logs</div>
</div>
</div>
`
const settings_logs = document.getElementById("settings_logs")
const settings_connexion = document.getElementById("settings_connexion")
const settings_users = document.getElementById("settings_users")
const settings_content = document.getElementById("settings_content")
loadLogs()
settings_logs.addEventListener("click", () => {
loadLogs()
})
settings_connexion.addEventListener("click", () => {
loadConnexion()
})
settings_users.addEventListener("click", () => {
loadUsers()
})
settings_logs.style.color = "white"
function loadLogs() {
settings_content.innerHTML = `
<select id="logs_selection">
<option>Test</option>
<option>Test</option>
<option>Test</option>
</select>
</div>
<div id="logs_content" class="logs_content">
</div>
`
const logs_selection = document.getElementById("logs_selection")
const logs_content = document.getElementById("logs_content")
const logs_data = get("LOGS")
logs_data.then(data => {
var optionToPush = new Array()
for(var log of data) {
optionToPush.push("<option>" + log.name + "</option>")
}
logs_selection.innerHTML = optionToPush.reverse().join("")
for(var log of data) {
if(logs_selection.value == log.name) {
var logToPush = new Array()
for(var line of log.value.split("\n")) {
line.toString()
line = line.replace("[INFO]", "<span style='color: #41f2ff !important;'>[INFO]</span>")
.replace("[ERROR]", "<span style='color: #ff3232;'>[ERROR]</span>")
.replace("[WARN]", "<span style='color: #ffea00;'>[WARN]</span>")
.replace("[FATAL]", "<span style='color: red;'>[FATAL]</span>")
.replace("[STEP]", "<span style='color: #ff6bf5;'>[STEP]</span>");
logToPush.push("<p class='log_line'>" +line+ "</p>")
}
logs_content.innerHTML = logToPush.join("")
}
}
logs_selection.addEventListener("change", () => {
for(var log of data) {
if(logs_selection.value == log.name) {
var logToPush = new Array()
for(var line of log.value.split("\n")) {
line.toString()
line = line.replace("[INFO]", "<span style='color: #41f2ff !important;'>[INFO]</span>")
.replace("[ERROR]", "<span style='color: #ff3232;'>[ERROR]</span>")
.replace("[WARN]", "<span style='color: #ffea00;'>[WARN]</span>")
.replace("[FATAL]", "<span style='color: red;'>[FATAL]</span>")
.replace("[STEP]", "<span style='color: #ff6bf5;'>[STEP]</span>");
logToPush.push("<p class='log_line'>" + line + "</p>")
}
logs_content.innerHTML = logToPush.join("")
}
}
})
})
}
function loadConnexion() {
settings_content.innerHTML = `
<p>Connexions</p>
`
}
function loadUsers() {
settings_content.innerHTML = `
<p>Users</p>
`
}
const buttons = document.querySelectorAll(".sside_line");
buttons.forEach(button => {
button.addEventListener("click", function () {
buttons.forEach(btn => {
if (btn === button) {
btn.style.color = "white";
} else {
btn.style.color = "";
}
});
});
});
})

View File

@ -442,33 +442,7 @@ p {
/*SETTINGS*/ /*SETTINGS*/
.SETTINGS_dialog {
width: 40vw;
height: 25vw;
padding: 0;
background: linear-gradient(90deg, #515151 0%, #2e2e2e 100%);
border: none;
border-radius: 12px;
}
.SETTINGS_content {
display: flex;
width: 100%;
flex-direction: row;
}
.ssidebar {
width: 12vw;
position: sticky;
height: 25vw;
background-color: #2d2d2d;
border-radius: 12px 0 0 12px;
display: flex; /* Ajout : pour aligner les éléments verticalement à l'intérieur */
flex-direction: column; /* Ajout : pour aligner les éléments verticalement à l'intérieur */
}
.third-join { .third-join {
@ -563,24 +537,7 @@ p {
text-align: center; text-align: center;
} }
.SETTINGS_close {
color: red;
background-color: transparent;
border-radius: 50vw;
border-color: red;
border-style: solid;
padding: 5px;
width: 40px;
height: 40px;
transition: all 0.2s ease 0s;
}
.SETTINGS_close:hover {
color: white;
background-color: red;
cursor: pointer;
}
.invisible { .invisible {
@ -1031,4 +988,65 @@ p {
.ppTile { .ppTile {
width: 180px; width: 180px;
}
/*SETTINGS*/
.SETTINGS_content {
width: 100%;
margin-left: 2%;
border-left: solid 1px #515151;
padding: 2%;
overflow: auto;
}
.ssidebar {
width: 20%;
height: 100%;
display: flex; /* Ajout : pour aligner les éléments verticalement à l'intérieur */
flex-direction: column;
align-items: center;
justify-content: center;
}
.SETTINGS {
width: 100%;
display: flex;
flex-direction: row;
height: 64vh;
}
.sside_line {
width: 100%;
color: rgba(255, 255, 255, 0.719);
text-align: center;
padding: 10%;
font-size: 22px;
cursor: pointer;
}
#logs_selection {
width: 100%;
border-radius: 12px;
padding: 1%;
}
.logs_content {
margin-top: 50px;
color: white;
font-size: 12px;
font-family: 'Courier New', Courier, monospace;
display: flex;
flex-direction: column;
}
.log_line {
width: 100%;
} }

View File

@ -4,6 +4,8 @@ var path = require("path")
var auth = require("../../modules/sub-auth") var auth = require("../../modules/sub-auth")
var log = require("../../modules/sub-log") var log = require("../../modules/sub-log")
var uuid = require("uuid") var uuid = require("uuid")
var fs = require("fs");
const { __glob, __web } = require('../../modules/global-variables');
const wlog = log.getInstance("Web") const wlog = log.getInstance("Web")
const alog = log.getInstance("Authentification") const alog = log.getInstance("Authentification")

View File

@ -16,7 +16,7 @@
<div id="userInfoglobal"> <div id="userInfoglobal">
<div class="INDEX_userInfo" id="userInfo"></div> <div class="INDEX_userInfo" id="userInfo"></div>
<div class="INDEX_userPopup" id="userPopup"> <div class="INDEX_userPopup" id="userPopup">
<!--div id="settingsBtn" class="INDEX_line"><i class="fa-solid fa-wrench"></i> Paramètres</div>-->
<div id="reportBtn" class="INDEX_line"><i class="fa-solid fa-bug"></i> Rapport</div> <div id="reportBtn" class="INDEX_line"><i class="fa-solid fa-bug"></i> Rapport</div>
<div id="restartBtn" class="INDEX_line"><i class="fa-solid fa-power-off"></i> Redémarrer</div> <div id="restartBtn" class="INDEX_line"><i class="fa-solid fa-power-off"></i> Redémarrer</div>
<a class="INDEX_signout" href="/internal/logout"><i class="fa fa-sign-out " aria-hidden="true"></i> Déconnexion</a> <a class="INDEX_signout" href="/internal/logout"><i class="fa fa-sign-out " aria-hidden="true"></i> Déconnexion</a>
@ -33,6 +33,10 @@
<p><i class="fa fa-search"></i> Rechercher</p> <p><i class="fa fa-search"></i> Rechercher</p>
</div> </div>
<div id="settings_btn" class="pSearch checker">
<p><i class="fa fa-gear"></i> Paramètres</p>
</div>
<hr style="color: white;"> <hr style="color: white;">
<div class="playlist-content" id="playlist-content"> <div class="playlist-content" id="playlist-content">
@ -58,8 +62,6 @@
<%- welcome %> <%- welcome %>
</div> </div>
</div> </div>
</div> </div>
@ -110,32 +112,6 @@
</div> </div>
</div> </div>
<dialog class="SETTINGS_dialog" id="SETTINGS_dialog">
<div class="SETTINGS_content">
<div class="ssidebar">
<h2 style=" padding: 1vw; position: absolute;" class="dialogTitle"><i class="fa fa-wrench"></i> Paramètres</h2>
<div class="ssidebar_content">
<div class="ssidebar_line"><i class="fa-solid fa-newspaper"></i> Logs</div>
<div class="ssidebar_line"><i class="fa-solid fa-tower-cell"></i> Connexions</div>
<div class="ssidebar_line"><i class="fa-solid fa-people-group"></i> Gestion d'accès</div>
<div class="ssidebar_line"><i class="fa-solid fa-shield"></i> Bêta</div>
</div>
</div>
<div class="scontent">
<div class="slineclose">
<button id="SETTINGS_close" class="SETTINGS_close"><i class="fa-solid fa-xmark"></i></button>
</div>
<p style="color: red; font-size: 72px; text-align: center; font-family: 'Gunship', sans-serif;" >NOT WORKING</p>
</div>
</div>
</dialog>
<dialog class="report_dialog" id="report_dialog"> <dialog class="report_dialog" id="report_dialog">
<div class="rlineclose"> <div class="rlineclose">
<p class="rtitle"><i class="fa fa-bug"></i> Rapport de bug</p> <p class="rtitle"><i class="fa fa-bug"></i> Rapport de bug</p>
@ -159,6 +135,7 @@
<script src="/socket.io/socket.io.js"></script> <script src="/socket.io/socket.io.js"></script>
<script src="/javascript/IO.js"></script> <script src="/javascript/IO.js"></script>
<script src="/javascript/__index_script.js"></script> <script src="/javascript/__index_script.js"></script>
<script src="/javascript/__settings_script.js"></script>
<script defer src="https://use.fontawesome.com/releases/v6.4.2/js/all.js" crossorigin="anonymous"></script> <script defer src="https://use.fontawesome.com/releases/v6.4.2/js/all.js" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</body> </body>