Version 0.4.0 - Ajout des metrics
All checks were successful
Neutral/pipeline/head This commit looks good

This commit is contained in:
CICD - Pipeline 2023-11-28 21:05:44 +01:00
parent 04fcece8d4
commit b19243a8af
17 changed files with 1121 additions and 384 deletions

View File

@ -6,7 +6,7 @@ const clog = new LogType("Fichier")
const os = require("os");
const uuid = require('uuid');
var mime = require('mime-types');
const { set } = require("../main")
// check if shared folder exist, if not create it
if(!fs.existsSync(__glob.SHARED)) {
@ -229,4 +229,27 @@ module.exports.newFile = function(settings) {
return "EXIST"
}
}
}
module.exports.pasteFile = function(settings) {
if(settings.action == "cut") {
try {
fs.renameSync(settings.file.fileDirectory, settings.newPath + path.sep + settings.name)
return "OK"
} catch(err) {
console.log(err)
return "NOT_PERMITTED"
}
} else if(settings.action == "copy") {
try {
fs.copyFileSync(settings.file.fileDirectory, settings.newPath + path.sep + settings.name)
return "OK"
} catch(err) {
console.log(err)
return "NOT_PERMITTED"
}
}
}

74
bin/server-metrics.js Normal file
View File

@ -0,0 +1,74 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables")
const clog = new LogType("Serveur Metrics")
const osutils = require("os-utils")
const os = require("os")
const { statfs } = require("fs")
const config = require("./config")
module.exports.getMetrics = async function(server) {
return new Promise((resolve, reject) => {
if(server == "Alpha") {
var resp = "NULL"
const space = statfs("/", (err, stats) => {
if (err) {
throw err
}
osutils.cpuUsage(function(cpuUsage) {
resp = {
cpu: Math.round(cpuUsage * 1000),
usedram: osutils.totalmem() - osutils.freemem(),
totalram: osutils.totalmem(),
usedisk: stats.blocks - stats.bfree,
totaldisk: stats.blocks,
name: server
}
resolve(resp)
})
})
} else if(server == "Omega") {
const key = config.getFile().OMEGA_KEY;
fetch("http://omega.raphix.fr:4000/metrics?key="+key, {
method: "GET",
headers: {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
}
})
.then(response => response.json())
.then(raw => {
raw.name = server;
resolve(raw);
})
.catch(error => {
reject(error);
});
}
})
}

View File

@ -1,14 +1,15 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables")
const auth = require("./auth")
const files = require("./files")
const { __glob } = require("./global-variables.js")
const auth = require("./auth.js")
const files = require("./files.js")
const links = require("./links.js")
const service = require("./services")
const service = require("./services.js")
const plog = new LogType("Web")
const cook = require("cookie")
const http = require("http")
const servermetrics = require("./server-metrics.js")
/**
* NOTE INTERNE
@ -94,6 +95,11 @@ module.exports.serverIO = function(server) {
})
PostRequest("FX_PASTE", (settings) => {
PostAnswer("FX_PASTE", files.pasteFile(settings))
})
}
if(user.checkPermission("SERVICES")) {
@ -136,6 +142,15 @@ module.exports.serverIO = function(server) {
}
if(user.checkPermission("SERVERS")) {
PostRequest("SERVER_GET_METRICS_Alpha", async (settings) => {
PostAnswer("SERVER_GET_METRICS_Alpha", {answer: "OK", metrics: await servermetrics.getMetrics(settings), name: settings});
});
PostRequest("SERVER_GET_METRICS_Omega", async (settings) => {
PostAnswer("SERVER_GET_METRICS_Omega", {answer: "OK", metrics: await servermetrics.getMetrics(settings), name: settings});
});
}
socket.on("disconnect", () => {
plog.log("Déconnexion du panel par '" + user.username + "' avec le socket : " + socket.id)

View File

@ -20,7 +20,7 @@ wlog.step.init("start_server", "Démarrage du serveur Express JS")
var app = require('../main');
var http = require('http');
var config = require("./config")
var serverIO = require("./server")
var serverIO = require("./servers")
/**
* Get port from environment and store in Express.

18
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "neutral",
"version": "0.2.0",
"version": "0.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "neutral",
"version": "0.2.0",
"version": "0.4.0",
"license": "ISC",
"dependencies": {
"cookie-parser": "~1.4.4",
@ -17,6 +17,7 @@
"http-errors": "~1.6.3",
"loguix": "1.4.1",
"nodemon": "^3.0.1",
"os-utils": "^0.0.14",
"serve-favicon": "^2.0.4",
"socket.io": "^4.7.2",
"uuid": "^9.0.1"
@ -718,6 +719,14 @@
"node": ">= 0.8"
}
},
"node_modules/os-utils": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/os-utils/-/os-utils-0.0.14.tgz",
"integrity": "sha512-ajB8csaHLBvJOYsHJkp8YdO2FvlBbf/ZxaYQwXXRDyQ84UoE+uTuLXxqd0shekXMX6Qr/pt/DDyLMRAMsgfWzg==",
"engines": {
"node": "*"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -1635,6 +1644,11 @@
"ee-first": "1.1.1"
}
},
"os-utils": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/os-utils/-/os-utils-0.0.14.tgz",
"integrity": "sha512-ajB8csaHLBvJOYsHJkp8YdO2FvlBbf/ZxaYQwXXRDyQ84UoE+uTuLXxqd0shekXMX6Qr/pt/DDyLMRAMsgfWzg=="
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "neutral",
"version": "0.3.4",
"version": "0.4.0",
"description": "Panel d'administration de Raphix",
"main": "index.js",
"scripts": {
@ -25,10 +25,11 @@
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"serve-favicon": "^2.0.4",
"http-errors": "~1.6.3",
"loguix": "1.4.1",
"nodemon": "^3.0.1",
"os-utils": "^0.0.14",
"serve-favicon": "^2.0.4",
"socket.io": "^4.7.2",
"uuid": "^9.0.1"
},

View File

@ -1,7 +1,19 @@
var xMousePos = null;
var yMousePos = null;
// Get Document ID
document.onmousemove = function(e) {
xMousePos = e.clientX + window.scrollX;
yMousePos = e.clientY + window.scrollY;
}
document.oncontextmenu = function(e) {
xMousePos = e.clientX + window.scrollX;
yMousePos = e.clientY + window.scrollY;
}
/**
* Récupère depuis le document l'identifiant de l'élément
@ -13,10 +25,50 @@ function getID(id) {
return document.getElementById(id)
}
/**
*
* @param {Date} GetDate
* @returns
*/
function getFormattedDate(GetDate) {
const date = new Date(GetDate)
var gmonth = date.getMonth() + 1
var gday = date.getDate()
var gHour = date.getHours()
var gMinute = date.getMinutes()
var gSecondes = date.getSeconds()
if(date.getMonth() + 1 < 10) {
gmonth = "0" + (date.getMonth() + 1)
}
if(date.getDate() + 1 <= 10) {
gday = "0" + date.getDate()
}
if(date.getHours() + 1 <= 10) {
gHour = "0" + date.getHours()
}
if(date.getMinutes() + 1 <= 10) {
gMinute = "0" + date.getMinutes()
}
if(date.getSeconds() + 1 <= 10) {
gSecondes = "0" + date.getSeconds()
}
return gday + "/" + gmonth + "/" + date.getFullYear() + " - " + gHour + ":" + gMinute
}
/**
* Permet de faire une pop-up automatique
*/
class InfoPop {
class TextResponse {
constructor(name) {
this.name = name
this.element = getID(this.name)
@ -54,38 +106,30 @@ class InfoPop {
}
const viewsAccessible = new Map()
var xMousePos = null;
var yMousePos = null;
document.onmousemove = function(e) {
xMousePos = e.clientX + window.scrollX;
yMousePos = e.clientY + window.scrollY;
}
document.oncontextmenu = function(e) {
xMousePos = e.clientX + window.scrollX;
yMousePos = e.clientY + window.scrollY;
}
const AllViews = new Map()
var zIndex = 5
/**
* Permet de créer une fenêtre
* @param {object} properties Propriétés de la fenêtre
*/
class ViewWindow {
ViewHTML = null
ViewProperties =null
ViewWindowDiv
ViewProperties = null
ViewSpanInteger = null
ViewPopupSpanInteger = null
ViewPopupHTML = null
ViewPopupTitle = null
ViewItem = new ViewItem(this)
constructor(properties) {
if(!viewsAccessible.has(properties.title)) {
if(!AllViews.has(properties.title)) {
this.ViewProperties = properties
viewsAccessible.set(properties.title, true)
AllViews.set(properties.title, true)
this.ViewHTML = `
<div draggable="true" id='${properties.title}_window' style='width: ${properties.width}; height: ${properties.height}' class='view-window'>
@ -93,8 +137,12 @@ class ViewWindow {
<div id='${properties.title}_header' class='view-window-header'>
<span style='width: 40px'></span>
<p>${properties.title}</p>
<div class='view-closediv'>
<span id='${properties.title}_reduce' class='btn-cover'><i class="fa-solid fa-window-minimize"></i></span>
<span id='${properties.title}_minimize' class='btn-cover'><i class="fa-solid fa-window-maximize"></i></span>
<span id='${properties.title}_close' class='view-close'><i class='fa fa-xmark'></i></span>
</div>
</div>
<div id='${properties.title}_content' class='view-window-content'>
</div>
<div id='${properties.title}_popupDiv'></div>
@ -105,15 +153,49 @@ class ViewWindow {
document.body.appendChild(this.ViewSpanInteger);
this.ViewSpanInteger.outerHTML = this.ViewHTML
const header = document.getElementById(properties.title + "_header")
const windowDiv = document.getElementById(properties.title + "_window")
this.ViewWindowDiv = windowDiv
const closeWindow = document.getElementById(properties.title + "_close")
const minimizeWindow = document.getElementById(properties.title + "_minimize")
const reduceWindow = document.getElementById(properties.title + "_reduce")
closeWindow.addEventListener("click", () => {
this.destroy()
})
reduceWindow.addEventListener("click", () => {
windowDiv.style.display = "none"
})
minimizeWindow.addEventListener("click", () => {
const windowDiv = document.getElementById(properties.title + "_window")
const contentDiv = document.getElementById(properties.title + "_content")
const minimizeIcon = document.getElementById(properties.title + "_minimize").children[0]
if(windowDiv.classList.contains("minimized")) {
windowDiv.classList.remove("minimized")
contentDiv.style.display = "unset"
windowDiv.style.backgroundColor = "#605e58c1"
minimizeIcon.classList.remove("fa-window-restore")
minimizeIcon.classList.add("fa-window-maximize")
} else {
windowDiv.classList.add("minimized")
contentDiv.style.display = "none"
windowDiv.style.backgroundColor = "transparent"
minimizeIcon.classList.remove("fa-window-maximize")
minimizeIcon.classList.add("fa-window-restore")
this.destroyPopup()
}
})
const header = document.getElementById(properties.title + "_header")
const windowDiv = document.getElementById(properties.title + "_window")
let isDragging = false;
let offsetX, offsetY;
@ -160,12 +242,15 @@ class ViewWindow {
}
this.ViewItem.show()
}
destroy() {
const win = getID(`${this.ViewProperties.title}_window`)
win.outerHTML = " "
viewsAccessible.delete(this.ViewProperties.title)
AllViews.delete(this.ViewProperties.title)
this.ViewItem.hide()
}
setContent(text) {
@ -225,49 +310,105 @@ class ViewWindow {
}
destroyPopup() {
const contentDiv = getID(this.ViewProperties.title + "_content")
for(var child of contentDiv.children) {
child.style.pointerEvents = "unset"
}
contentDiv.classList.remove("blur")
const popup = getID(this.ViewPopupTitle + "_popup")
popup.outerHTML = ""
if(popup) {
const contentDiv = getID(this.ViewProperties.title + "_content")
for(var child of contentDiv.children) {
child.style.pointerEvents = "unset"
}
contentDiv.classList.remove("blur")
popup.outerHTML = ""
}
}
}
function createView(viewType) {
if(viewType == 'files_explorer') {
generateFileExplorerView()
var AllComponents = new Array()
/**
* Permet de créer un composant de vue
* @param {object} properties Propriétés du composant de vue
*/
class ViewComponent {
name;
icon;
permission;
window;
constructor (properties) {
this.name = properties.name
this.icon = properties.icon
this.permission = properties.permission
AllComponents.push(this)
}
if(viewType == 'service') {
generateServiceView()
}
if(viewType == "links") {
inject(array) {
generateLinksView()
array.push(`<div id='${this.name}' class='view'>
<span class='view-image'><i class='${this.icon}'></i></span>
<p class='view-text'>${this.name}</p>
</div>`)
}
createWindow(functionToExecute) {
this.window = functionToExecute
}
bindView() {
const component = getID(this.name)
if(component) {
component.addEventListener("click", () => {
console.log(component)
if(this.window) {
this.window()
}
})
}
}
forceWindow() {
if(this.window) {
this.window()
}
}
}
/**
* Permet de créer un menu déroulant
* @param {object} properties Propriétés du menu déroulant
*/
var ActualDroppableMenu = null
class DroppableMenu {
options = new Array()
id = null
DMSpanInteger = null
constructor(properties) {
#enableHide = false
constructor() {
this.id = properties.id
this.id = Math.random().toString(36).substring(7)
this.options.push(`<div id='dm-${this.id}' class='dm-menu'>`)
}
add(action, string) {
@ -277,13 +418,18 @@ class DroppableMenu {
}
show() {
console.log("DROPPABLE MENU - " + this.id)
if(ActualDroppableMenu) {
ActualDroppableMenu.hide()
}
ActualDroppableMenu = this
this.options.push(`</div>`)
if(xMousePos && yMousePos) {
this.DMSpanInteger = document.createElement("span")
document.body.appendChild(this.DMSpanInteger);
this.DMSpanInteger.outerHTML = this.options.join('')
@ -292,22 +438,21 @@ class DroppableMenu {
menu.style.zIndex = zIndex + 2
zIndex+=1
menu.style.left = (xMousePos - 40) + "px"
menu.style.top = (yMousePos - 40) + "px"
menu.addEventListener('mouseleave', () => {
menu.outerHTML = ""
})
}
this.#enableHide = false
document.addEventListener("click", () => {
this.hide()
})
}
get(action) {
@ -316,45 +461,331 @@ class DroppableMenu {
}
hide() {
const menu = getID(`dm-${this.id}`)
menu.outerHTML = ""
if(menu) {
menu.outerHTML = ""
}
}
}
function getFormattedDate(GetDate) {
const date = new Date(GetDate)
var gmonth = date.getMonth() + 1
var gday = date.getDate()
var gHour = date.getHours()
var gMinute = date.getMinutes()
var gSecondes = date.getSeconds()
if(date.getMonth() + 1 < 10) {
gmonth = "0" + (date.getMonth() + 1)
}
if(date.getDate() + 1 <= 10) {
gday = "0" + date.getDate()
/**
* Permet de créer un composant de type "Serveur"
*/
class Server {
constructor(properties) {
this.name = properties.name
this.description = properties.description
this.icon = properties.icon
}
if(date.getHours() + 1 <= 10) {
gHour = "0" + date.getHours()
generateHTML() {
return `
<div class="servers-box">
<div class="servers-box-title">
<div class='servers-box-title-info'>
<i class='${this.icon}'></i>
<p>${this.name}</p>
</div>
<p style='font-size: 12px;'>${this.description}</p>
</div>
<div class="servers-box-content">
<div class='servers-metrics'>
<div class='servers-metrics-box'>
<div>
<i class='fa-solid fa-memory'></i>
<p>RAM</p>
</div>
<p id='${this.name}_ram'>-- Mo / -- Mo</p>
</div>
<div class='servers-metrics-box'>
<div>
<i class='fa-solid fa-microchip'></i>
<p>CPU</p>
</div>
<p id='${this.name}_cpu'>---%</p>
</div>
<div class='servers-metrics-box'>
<div>
<i class='fa-solid fa-hdd'></i>
<p>DISK</p>
</div>
<p id='${this.name}_disk'>-- Go / -- Go</p>
</div>
</div>
</div>
</div>
`
}
if(date.getMinutes() + 1 <= 10) {
gMinute = "0" + date.getMinutes()
loadScript() {
const ramSpan = getID(`${this.name}_ram`)
const cpuSpan = getID(`${this.name}_cpu`)
const diskSpan = getID(`${this.name}_disk`)
setInterval(() => {
if(getID(`${this.name}_ram`)) {
const request = post(`SERVER_GET_METRICS_` + this.name, this.name )
console.log(this.name)
request.then((answer) => {
console.log(answer)
console.log(this.name)
//Transform all metrics in mb to answer.metrics to Go or %
answer.metrics.usedram = Math.round(answer.metrics.usedram)
answer.metrics.totalram = Math.round(answer.metrics.totalram)
//Transform all metrics in blocks to Go
answer.metrics.usedisk = Math.round((answer.metrics.usedisk * 3.814697265625) / 1000000)
answer.metrics.totaldisk = Math.round((answer.metrics.totaldisk * 3.814697265625) / 1000000)
if(answer.metrics.name == this.name) {
ramSpan.innerHTML = `${answer.metrics.usedram} Mo / ${answer.metrics.totalram} Mo`
cpuSpan.innerHTML = answer.metrics.cpu + "%"
diskSpan.innerHTML = `${answer.metrics.usedisk} Go / ${answer.metrics.totaldisk} Go`
}
})
}
}, 1000)
}
}
/**
* Créer un composant de vue de type "Service"
* @param {object} properties Propriétés du composant de vue
*/
class Service {
name = null
description = null
icon = null
url = null
canAccess = false
isOnline = false
constructor(properties) {
this.name = properties.name
this.description = properties.description
this.icon = properties.icon
this.url = properties.url
this.canAccess = properties.canAccess
}
if(date.getSeconds() + 1 <= 10) {
gSecondes = "0" + date.getSeconds()
generateHTML() {
return `
<div class="sv">
<div class='sv-info'>
<img class="sv-icon" src="${this.icon}" alt="${this.name}">
<div>
<h1>${this.name}</h1>
<p>${this.description}</p>
<p>Etat : <span id='${this.name}_status' class="sv-status">Vérification en cours ...</span></p>
</div>
</div>
<div class="sv-actions">
${this.canAccess ? `<a href="${this.url}" target="_blank"><button class="btn green"><span>Accéder au service</span></button></a>` : ""}
<button id='${this.name}_svpower' class='btn yellow'n><span>Options d'alimentation<span></button>
</div>
</div>
`
}
return gday + "/" + gmonth + "/" + date.getFullYear() + " - " + gHour + ":" + gMinute
}
loadScript() {
return new Promise((resolve, reject) => {
const statusSpan = getID(`${this.name}_status`)
const request = post(`SV_GET_SERVICE_STATUS`, this.url)
request.then((answer) => {
if(answer.name == this.url) {
if(answer.answer == "ONLINE") {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle green"></i> En ligne</span>'
this.isOnline = true
} else {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle red"></i> Hors ligne</span>'
}
resolve("LOADED")
}
})
const powerButton = getID(`${this.name}_svpower`)
// Make a popup of View to select if you want to start, stop or restart the service by doing a request
powerButton.addEventListener("click", () => {
View.createPopup({
title: `<i class='fa-solid fa-power-off'></i> Gestion de l'alimentation du service`,
content: `
<p class='sv-power-select'>${this.name}</p>
<p id='sv-power-info'></p>
<div class="sv-power">
<button id="${this.name}_start" class="btn green"><span>Démarrer</span></button>
<button id="${this.name}_restart" class="btn yellow"><span>Redémarrer</span></button>
<button id="${this.name}_stop" class="btn red"><span> Arrêter</span></button>
</div>
`
})
const startButton = getID(`${this.name}_start`)
const stopButton = getID(`${this.name}_stop`)
const restartButton = getID(`${this.name}_restart`)
const info = new InfoPop("sv-power-info")
if(this.isOnline) {
startButton.style.display = "none"
info.info("Verifiez que le service n'est pas utilisé par quelqu'un d'autre avant de le redémarrer ou de l'arrêter")
} else {
stopButton.style.display = "none"
restartButton.style.display = "none"
info.info("Si le service ne démarre pas, vérifiez l'intégrité du service")
}
startButton.addEventListener("click", () => {
const request = post(`SV_START_SERVICE`, this.url)
request.then((answer) => {
if(answer.answer == "OK") {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle green"></i> En ligne</span>'
View.destroyPopup("`<i class='fa-solid fa-power-off'></i> Gestion de l'alimentation du service`")
this.isOnline = true
} else {
info.err("Impossible de démarrer le service")
}
})
})
stopButton.addEventListener("click", () => {
const request = post(`SV_STOP_SERVICE`, this.url)
request.then((answer) => {
if(answer.answer == "OK") {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle red"></i> Hors ligne</span>'
this.isOnline = false
View.destroyPopup("`<i class='fa-solid fa-power-off'></i> Gestion de l'alimentation du service`")
} else {
info.err("Impossible d'arrêter le service")
}
})
})
restartButton.addEventListener("click", () => {
console.log("RESTART")
const request = post(`SV_RESTART_SERVICE`, this.url)
request.then((answer) => {
if(answer.answer == "OK") {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle green"></i> En ligne</span>'
View.destroyPopup("`<i class='fa-solid fa-power-off'></i> Gestion de l'alimentation du service`")
this.isOnline = true
} else {
info.err("Impossible de redémarrer le service")
}
})
})
})
})
}
}
/**
* Permet de créer un item de la barre des tâches
* @param {ViewWindow} window ViewWindow associé à l'item
*/
class ViewItem {
/**
*
* @param {ViewWindow} window
*/
constructor(window) {
this.window = window
}
show() {
const viewItems = getID("views-items")
const item = document.createElement("div")
item.id = `${this.window.ViewProperties.title}_item`
item.classList.add("view-item")
item.innerHTML = this.window.ViewProperties.title
viewItems.appendChild(item)
item.addEventListener("click", () => {
zIndex+=1
this.window.ViewWindowDiv.style.zIndex = zIndex + 1
this.window.ViewWindowDiv.style.display = "unset"
})
item.addEventListener("contextmenu", () => {
const dp = new DroppableMenu()
dp.add("index", `<i class='fa fa-window-restore'></i> Afficher`)
dp.add("close", `<span class='menu-signout'><i class='fa fa-xmark'></i> Fermer</span>`)
dp.show()
const index = dp.get("index")
const close = dp.get("close")
index.addEventListener("click", () => {
zIndex+=1
this.window.ViewWindowDiv.style.display = "unset"
this.window.ViewWindowDiv.style.zIndex = zIndex + 1
dp.hide()
})
close.addEventListener("click", () => {
this.window.destroy()
dp.hide()
})
})
}
hide() {
const viewItems = getID("views-items")
const item = getID(`${this.window.ViewProperties.title}_item`)
item.outerHTML = ""
}
}

View File

@ -3,7 +3,9 @@ document.addEventListener("contextmenu", (e) => {
e.preventDefault()
})
function generateFileExplorerView() {
explorer.createWindow(() => {
var inCopyOrCut = null;
const View = new ViewWindow({
title: `<i class="fa fa-folder"></i> Gestionnaire de fichiers`,
@ -11,6 +13,15 @@ function generateFileExplorerView() {
height: "600px"
})
console.log("")
goHomePath()
function goHomePath() {
const rFiles = post("FX_GET", "homepath")
@ -53,6 +64,57 @@ function generateFileExplorerView() {
const home = getID(View.getViewTitle() + '_home')
const sharebtn = getID(View.getViewTitle() + '_sharebtn')
View.ViewWindowDiv.addEventListener("contextmenu", () => {
if(inCopyOrCut != null) {
const dMenu = new DroppableMenu()
dMenu.add("paste", "<i class='fa fa-paste'></i> Coller")
dMenu.show()
dMenu.get("paste").addEventListener("click", () => {
console.log({newPath: rootInput.value, action: inCopyOrCut.action, file: inCopyOrCut.file, name: inCopyOrCut.file.name, orginalPath: inCopyOrCut.orginalPath})
dMenu.hide()
const reqFiles = post("FX_PASTE", {newPath: rootInput.value, action: inCopyOrCut.action, file: inCopyOrCut.file, name: inCopyOrCut.file.name, orginalPath: inCopyOrCut.orginalPath})
reqFiles.then((result) => {
if(result == "OK") {
const reqFiles = post("FX_GET", rootInput.value)
reqFiles.then((result) => {
loadFiles(result)
})
} else if(result == "NOT_PERMITTED") {
const reqFiles = post("FX_GET", rootInput.value)
reqFiles.then((result) => {
loadFiles(result)
})
View.createPopup({
title: `<i class='fa fa-warning'></i> Erreur`,
content: `<p class='yellow'>Vous n'avez pas les permissions pour coller ce fichier.</p>`
})
} else {
const reqFiles = post("FX_GET", rootInput.value)
reqFiles.then((result) => {
loadFiles(result)
})
View.createPopup({
title: `<i class='fa fa-warning'></i> Erreur`,
content: `<p class='yellow'>Une erreur est survenue.</p>`
})
}
})
inCopyOrCut = null
})
} else {
console.log("LISTENER : " + rootInput.value)
}
})
home.addEventListener("click", () => {
goHomePath()
@ -77,7 +139,7 @@ function generateFileExplorerView() {
const foldername = getID(View.getViewTitle() + "_foldername")
const foldercreate = getID(View.getViewTitle() + "_foldercreate")
const folderInfo = new InfoPop(View.getViewTitle() + "_folderInfo")
const folderInfo = new TextResponse(View.getViewTitle() + "_folderInfo")
folderInfo.setSize("13px")
@ -137,7 +199,7 @@ function generateFileExplorerView() {
const filename = getID(View.getViewTitle() + "_filename")
const filecreate = getID(View.getViewTitle() + "_filecreate")
const fileupload = getID(View.getViewTitle() + "_fileupload")
const fileInfo = new InfoPop(View.getViewTitle() + "_fileInfo")
const fileInfo = new TextResponse(View.getViewTitle() + "_fileInfo")
fileInfo.setSize("13px")
@ -190,7 +252,7 @@ function generateFileExplorerView() {
const fileuploadInput = getID(View.getViewTitle() + "_fileuploadInput")
const fileuploadBtn = getID(View.getViewTitle() + "_fileuploadBtn")
const fileuploadInfo = new InfoPop(View.getViewTitle() + "_fileuploadInfo")
const fileuploadInfo = new TextResponse(View.getViewTitle() + "_fileuploadInfo")
fileuploadBtn.addEventListener("click", () => {
@ -324,9 +386,7 @@ function generateFileExplorerView() {
for(const file of files.content) {
const dropMenu = new DroppableMenu({
"id": file.id
})
const element = document.getElementById(file.id)
@ -347,18 +407,41 @@ function generateFileExplorerView() {
editFile()
})
dropMenu.add("edit", "<i class='fa-solid fa-pen'></i> Editer")
dropMenu.add("share", "<i class='fa fa-share'></i> Partager")
dropMenu.add("download", "<i class='fa fa-download'></i> Télécharger")
}
dropMenu.add("rename", "<i class='fa-solid fa-file-signature'></i> Renommer")
dropMenu.add("delete", "<i class='fa fa-trash'></i> Supprimer")
getID(file.id).addEventListener("contextmenu", () => {
const dropMenu = new DroppableMenu()
dropMenu.add("copy", "<i class='fa fa-copy'></i> Copier")
dropMenu.add("cut", "<i class='fa fa-cut'></i> Couper")
dropMenu.add("rename", "<i class='fa-solid fa-file-signature'></i> Renommer")
dropMenu.add("delete", "<i class='fa fa-trash'></i> Supprimer")
if(!file.directory) {
dropMenu.add("edit", "<i class='fa-solid fa-pen'></i> Editer")
dropMenu.add("share", "<i class='fa fa-share'></i> Partager")
dropMenu.add("download", "<i class='fa fa-download'></i> Télécharger")
}
dropMenu.show()
dropMenu.get("copy").addEventListener("click", () => {
dropMenu.hide()
inCopyOrCut = {"action": "copy", "file": file, "orginalPath": files.root}
})
dropMenu.get("cut").addEventListener("click", () => {
dropMenu.hide()
inCopyOrCut = {"action": "cut", "file": file, "orginalPath": files.root}
})
dropMenu.get("delete").addEventListener("click", () => {
dropMenu.hide()
const reqFiles = post("FX_DELETE", file.fileDirectory)
@ -403,7 +486,7 @@ function generateFileExplorerView() {
const rename = getID(View.getViewTitle() + "_rename")
const renameBtn = getID(View.getViewTitle() + "_renameBtn")
const renameInfo = new InfoPop(View.getViewTitle() + "_renameInfo")
const renameInfo = new TextResponse(View.getViewTitle() + "_renameInfo")
renameInfo.setSize("13px")
rename.value = file.name
rename.focus()
@ -465,7 +548,7 @@ function generateFileExplorerView() {
const sharelink = getID(View.getViewTitle() + "_sharelink")
const shareBtn = getID(View.getViewTitle() + "_shareBtn")
const shareInfo = new InfoPop(View.getViewTitle() + "_shareInfo")
const shareInfo = new TextResponse(View.getViewTitle() + "_shareInfo")
shareInfo.setSize("13px")
sharelink.value = result
sharelink.focus()
@ -583,10 +666,7 @@ function generateFileExplorerView() {
editorContent.style.resize = "none"
editorClose.addEventListener("click", () => {
editor.destroy()
})
}
@ -603,7 +683,7 @@ function generateFileExplorerView() {
}
})
function getIcon(file) {

View File

@ -3,6 +3,8 @@ const infoUserimage = getID("infoUserimage")
const infoDisplayname = getID("infoDisplayname")
const views = getID("views")
// User Request
const REQ_user = get("USERINFO")
@ -17,55 +19,12 @@ REQ_user.then((ANS_user) => {
const AvailableViews = new Array()
permissions.forEach((permValue) => {
if(permValue == "FILES_EXPLORER") {
AvailableViews.push(`<div onclick="createView('files_explorer')" class="view">
<span class="view-image"><i class="fa-solid fa-folder"></i></span>
<p class="view-text">Gestionnaire de fichiers</p>
</div>`)
}
if(permValue == "SERVICES") {
AvailableViews.push(`<div onclick="createView('service')" class="view">
<span class="view-image"><i class="fa-solid fa-layer-group"></i></span>
<p class="view-text">Gestion des services</p>
</div>`)
}
if(permValue == "SERVERS") {
AvailableViews.push(`<div class="view">
<span class="view-image"><i class="fa fa-server"></i></span>
<p class="view-text">Gestion des serveurs</p>
</div>`)
}
if(permValue == "PIPELINES") {
AvailableViews.push(` <div class="view">
<span class="view-image"><i class="fa-brands fa-jenkins"></i></span>
<p class="view-text">Gestion des pipelines</p>
</div>`)
}
if(permValue == "METRICS") {
AvailableViews.push(` <div class="view">
<span class="view-image"><i class="fa-solid fa-square-poll-vertical"></i></span>
<p class="view-text">Web Metrics</p>
</div>`)
}
if(permValue == "USERS") {
AvailableViews.push(` <div class="view">
<span class="view-image"><i class="fa fa-users"></i></span>
<p class="view-text">Gestion des utilisateurs</p>
</div>`)
}
if(permValue == "LINKS") {
AvailableViews.push(` <div onclick="createView('links')" class="view">
<span class="view-image"><i class="fa-solid fa-link"></i></span>
<p class="view-text">Générateur de lien</p>
</div>`)
}
if(permValue == "SETTINGS") {
AvailableViews.push(` <div class="view">
<span class="view-image"><i class="fa fa-gear"></i></span>
<p class="view-text">Paramètres</p>
</div>`)
}
AllComponents.forEach((component) => {
if(component.permission == permValue) {
component.inject(AvailableViews)
}
})
})
@ -73,7 +32,7 @@ REQ_user.then((ANS_user) => {
if(AvailableViews.join("") == "") {
AvailableViews.push("<p class='yellow'><i class='fa-solid fa-warning'></i> Aucune permission ne semble vous êtes accordée<br>Demandez à Raphix afin de résoudre ce problème</p>")
AvailableViews.push("<p style='position: absolute; width: 100%;' class='yellow t-center'><i class='fa-solid fa-warning'></i> Aucune permission ne semble vous êtes accordée<br>Demandez à Raphix afin de résoudre ce problème</p>")
views.classList.remove("views-box")
} else {
@ -82,5 +41,14 @@ REQ_user.then((ANS_user) => {
views.innerHTML = AvailableViews.join("")
// BindView
AllComponents.forEach((component) => {
component.bindView()
})
servers.forceWindow()
})

View File

@ -1,4 +1,4 @@
function generateLinksView() {
links.createWindow(() => {
const View = new ViewWindow({
title: `<i class="fa-solid fa-link"></i> Générateur de liens`,
@ -40,7 +40,7 @@ function generateLinksView() {
const titleInput = getID(`${View.getViewTitle()}_title`)
const urlInput = getID(`${View.getViewTitle()}_url`)
const addBtn = getID(`${View.getViewTitle()}_confirm`)
const info = new InfoPop(`${View.getViewTitle()}_info`)
const info = new TextResponse(`${View.getViewTitle()}_info`)
const ablink = getID(`${View.getViewTitle()}_ablink`)
const newlinkSpan = getID(`${View.getViewTitle()}_newlink_span`)
@ -206,7 +206,7 @@ function generateLinksView() {
const titleInput = getID(`${View.getViewTitle()}_edittitle`)
const urlInput = getID(`${View.getViewTitle()}_editurl`)
const editBtnConfirm = getID(`${View.getViewTitle()}_editconfirm`)
const info = new InfoPop(`${View.getViewTitle()}_editinfo`)
const info = new TextResponse(`${View.getViewTitle()}_editinfo`)
titleInput.value = link.title
urlInput.value = link.url
@ -294,4 +294,4 @@ function generateLinksView() {
}
}
})

View File

@ -2,7 +2,7 @@ const username = getID("username")
const password = getID("password")
const submit = getID("submit")
const loginInfo = new InfoPop("login-info")
const loginInfo = new TextResponse("login-info")
loginInfo.clear()

View File

@ -0,0 +1,57 @@
// My View Component
const explorer = new ViewComponent({
name: "Explorateur de fichier",
icon: "fa-solid fa-folder",
permission: "FILES_EXPLORER"
})
const services = new ViewComponent({
name: "Gestion des services",
icon: "fa-solid fa-layer-group",
permission: "SERVICES"
})
const servers = new ViewComponent({
name: "Gestion des serveurs",
icon: "fa fa-server",
permission: "SERVERS"
})
const pipelines = new ViewComponent({
name: "Gestion des pipelines",
icon: "fa-solid fa-code-merge",
permission: "PIPELINES"
})
const metrics = new ViewComponent({
name: "Web Metrics",
icon: "fa-solid fa-square-poll-vertical",
permission: "METRICS"
})
const users = new ViewComponent({
name: "Gestion des utilisateurs",
icon: "fa-solid fa-users",
permission: "USERS"
})
const links = new ViewComponent({
name: "Gestion des liens",
icon: "fa-solid fa-link",
permission: "LINKS"
})
const settings = new ViewComponent({
name: "Paramètres",
icon: "fa-solid fa-cog",
permission: "SETTINGS"
})

View File

@ -0,0 +1,73 @@
servers.createWindow(async () => {
const View = new ViewWindow({
title: `<i class="fa fa-server"></i> Gestion des serveurs`,
width: "1000px",
height: "450px"
})
const serversList = new Array()
const alpha = new Server({
name: "Alpha",
description: "Serveur principal",
icon: "fa-solid fa-hourglass-start",
})
const omega = new Server({
name: "Omega",
description: "Serveur secondaire",
icon: "fa-solid fa-stop",
})
serversList.push(alpha.generateHTML())
serversList.push(omega.generateHTML())
View.setContent(`
<div class="servers">
${serversList.join("")}
</div>`)
await alpha.loadScript()
await omega.loadScript()
})
/**
<div class="servers-box">
<div class="servers-box-title">
<div class='servers-box-title-info'>
<i class="fa fa-server"></i>
<p>Alpha</p>
</div>
<button class="btn yellow"><span>Redémarrer</span></button>
</div>
<div class="servers-box-content">
<div class='servers-metrics'>
<div class='servers-metrics-box'>
<div>
<i class='fa-solid fa-memory'></i>
<p>RAM</p>
</div>
<p>1.5 Go / 2 Go</p>
</div>
<div class='servers-metrics-box'>
<div>
<i class='fa-solid fa-microchip'></i>
<p>CPU</p>
</div>
<p>1.5 Go / 2 Go</p>
</div>
<div class='servers-metrics-box'>
<div>
<i class='fa-solid fa-hdd'></i>
<p>DISK</p>
</div>
<p>1.5 Go / 2 Go</p>
</div>
</div>
</div>
</div>
*/

View File

@ -1,164 +1,7 @@
async function generateServiceView() {
class Service {
name = null
description = null
icon = null
url = null
canAccess = false
isOnline = false
constructor(properties) {
this.name = properties.name
this.description = properties.description
this.icon = properties.icon
this.url = properties.url
this.canAccess = properties.canAccess
}
generateHTML() {
return `
<div class="sv">
<div class='sv-info'>
<img class="sv-icon" src="${this.icon}" alt="${this.name}">
<div>
<h1>${this.name}</h1>
<p>${this.description}</p>
<p>Etat : <span id='${this.name}_status' class="sv-status">Vérification en cours ...</span></p>
</div>
</div>
<div class="sv-actions">
${this.canAccess ? `<a href="${this.url}" target="_blank"><button class="btn green"><span>Accéder au service</span></button></a>` : ""}
<button id='${this.name}_svpower' class='btn yellow'n><span>Options d'alimentation<span></button>
</div>
</div>
`
}
loadScript() {
return new Promise((resolve, reject) => {
const statusSpan = getID(`${this.name}_status`)
const request = post(`SV_GET_SERVICE_STATUS`, this.url)
request.then((answer) => {
if(answer.name == this.url) {
if(answer.answer == "ONLINE") {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle green"></i> En ligne</span>'
this.isOnline = true
} else {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle red"></i> Hors ligne</span>'
}
resolve("LOADED")
}
})
const powerButton = getID(`${this.name}_svpower`)
// Make a popup of View to select if you want to start, stop or restart the service by doing a request
powerButton.addEventListener("click", () => {
View.createPopup({
title: `<i class='fa-solid fa-power-off'></i> Gestion de l'alimentation du service`,
content: `
<p class='sv-power-select'>${this.name}</p>
<p id='sv-power-info'></p>
<div class="sv-power">
<button id="${this.name}_start" class="btn green"><span>Démarrer</span></button>
<button id="${this.name}_restart" class="btn yellow"><span>Redémarrer</span></button>
<button id="${this.name}_stop" class="btn red"><span> Arrêter</span></button>
</div>
`
})
const startButton = getID(`${this.name}_start`)
const stopButton = getID(`${this.name}_stop`)
const restartButton = getID(`${this.name}_restart`)
const info = new InfoPop("sv-power-info")
if(this.isOnline) {
startButton.style.display = "none"
info.info("Verifiez que le service n'est pas utilisé par quelqu'un d'autre avant de le redémarrer ou de l'arrêter")
} else {
stopButton.style.display = "none"
restartButton.style.display = "none"
info.info("Si le service ne démarre pas, vérifiez l'intégrité du service")
}
startButton.addEventListener("click", () => {
const request = post(`SV_START_SERVICE`, this.url)
request.then((answer) => {
if(answer.answer == "OK") {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle green"></i> En ligne</span>'
View.destroyPopup("`<i class='fa-solid fa-power-off'></i> Gestion de l'alimentation du service`")
this.isOnline = true
} else {
info.err("Impossible de démarrer le service")
}
})
})
stopButton.addEventListener("click", () => {
const request = post(`SV_STOP_SERVICE`, this.url)
request.then((answer) => {
if(answer.answer == "OK") {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle red"></i> Hors ligne</span>'
this.isOnline = false
View.destroyPopup("`<i class='fa-solid fa-power-off'></i> Gestion de l'alimentation du service`")
} else {
info.err("Impossible d'arrêter le service")
}
})
})
restartButton.addEventListener("click", () => {
console.log("RESTART")
const request = post(`SV_RESTART_SERVICE`, this.url)
request.then((answer) => {
if(answer.answer == "OK") {
statusSpan.innerHTML = '<span style="font-size: 12px;"><i class="fa-solid fa-circle green"></i> En ligne</span>'
View.destroyPopup("`<i class='fa-solid fa-power-off'></i> Gestion de l'alimentation du service`")
this.isOnline = true
} else {
info.err("Impossible de redémarrer le service")
}
})
})
})
})
}
}
services.createWindow(async () => {
/**
* CODE OF SERVICE.JS
*/
const allServices = new Array()
const View = new ViewWindow({
@ -236,5 +79,5 @@ async function generateServiceView() {
await cvraphix.loadScript()
await lavalink.loadScript()
}
})

View File

@ -293,14 +293,21 @@ a {
.subpanel-box {
width: 900px;
height: 50px;
border-radius: 10px;
padding: 2%;
width: 100%;
position: absolute;
background-color: #09090963;
backdrop-filter: blur(10px);
bottom: 0;
align-items: center;
}
.subpanel-content {
display: flex;
justify-content: space-between;
background-color: #605e5863;
backdrop-filter: blur(10px);
padding: 20px;
}
.subpanel-image {
@ -310,6 +317,16 @@ a {
border-radius: 100%;
}
.subpanel-logo {
width: 50px;
transition: 0.3s;
}
.subpanel-logo:hover {
transform: scale(0.9);
}
.subpanel-user {
display: flex;
@ -319,12 +336,21 @@ a {
}
.subpanel-userinfo {
text-align: start;
text-align: end;
display: flex;
flex-direction: column;
gap: 5px;
}
.subpanel-actions {
display: flex;
flex-direction: row;
gap: 20px;
align-items: center;
}
.subpanel-username {
font-size: 14px;
@ -332,6 +358,66 @@ a {
margin-block: 0 !important;
}
.views-items {
width: 100%;
display: flex;
flex-direction: row;
gap: 15px;
}
.view-item {
display: flex;
align-items: center;
flex-direction: row;
gap: 10px;
font-size: 14px;
background-color: #3230319d;
padding: 5px;
padding-left: 10px;
padding-right: 10px;
border-radius: 15px;
transition: 0.3s;
user-select: none;
}
.view-item:hover {
background-color: #4e4b4d9d;
cursor: pointer;
transform: scale(1.05);
}
.view-item:active {
background-color: #4e4b4d9d;
cursor: pointer;
transform: scale(0.975);
}
.view-item p {
text-align: start;
}
.menu-signout {
color: #ff5d51;
cursor: pointer;
font-size: 14px;
display: grid;
grid-template-columns: 0.2fr 0.8fr;
gap: 10px;
}
.dm-element:hover .menu-signout {
color: black;
}
.subpanel-dispname {
@ -513,6 +599,17 @@ a {
color: #ff5d51;
}
.view-closediv {
display: flex;
flex-direction: row;
gap: 20px;
}
.minimized {
height: 7% !important;
}
/* Files Explorer */
@ -792,4 +889,81 @@ a {
.ln-link-url {
font-size: 12px;
}
/* SERVERS */
.servers {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 20px;
user-select: none;
gap: 30px;
-webkit-user-select: none;
}
.servers-box {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
.servers-box-title {
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
padding: 10px;
border-radius: 10px;
background-color: #1b1b1bc1;
transition: 0.1s;
justify-content: space-between;
}
.servers-box-title-info {
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
}
.servers-metrics {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.servers-box-content {
padding: 10px;
background-color: #3D3B3C;
margin-top: 20px;
border-radius: 10px;
}
.servers-metrics-box {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
border-radius: 10px;
background-color: #1b1b1bc1;
transition: 0.1s;
}
.servers-metrics-box div {
display: flex;
align-items: center;
gap: 10px;
}

View File

@ -10,7 +10,7 @@ router.get('/', function(req, res, next) {
res.redirect(302, "/")
} else {
res.clearCookie('token')
res.render('login', {version: process.env.npm_package_version});
res.render('login', {version: require("../package.json").version});
}

View File

@ -8,58 +8,42 @@
<div class="panel-box m-align t-center">
<div class="logo"><img class="logo-img" src="/images/FormatLogo_WHITE.svg"><p>Neutral</p></div>
<div id="views" class="views-box">
<div class="view">
<span class="view-image"><i class="fa-solid fa-folder"></i></span>
<p class="view-text">Gestionnaire de fichiers</p>
</div>
<div class="view">
<span class="view-image"><i class="fa-solid fa-layer-group"></i></span>
<p class="view-text">Gestion des services</p>
</div>
<div class="view">
<span class="view-image"><i class="fa fa-server"></i></span>
<p class="view-text">Gestion des serveurs</p>
</div>
<div class="view">
<span class="view-image"><i class="fa-brands fa-jenkins"></i></span>
<p class="view-text">Gestion des pipelines</p>
</div>
<div class="view">
<span class="view-image"><i class="fa-solid fa-square-poll-vertical"></i></span>
<p class="view-text">Web Metrics</p>
</div>
<div class="view">
<span class="view-image"><i class="fa fa-users"></i></span>
<p class="view-text">Gestion des utilisateurs</p>
</div>
<div class="view">
<span class="view-image"><i class="fa-solid fa-link"></i></span>
<p class="view-text">Générateur de lien</p>
</div>
<div class="view">
<span class="view-image"><i class="fa fa-gear"></i></span>
<p class="view-text">Paramètres</p>
</div>
<p style="position: absolute; width: 100%;" class='yellow t-center'><i class='fa-solid fa-warning'></i> Aucune permission ne semble vous êtes accordée<br>Demandez à Raphix afin de résoudre ce problème</p>
</div>
</div>
<div class="subpanel-box m-align t-center">
<div class="subpanel-user">
<img id="infoUserimage" class="subpanel-image" src="/images/users/default.jpg">
<div class="subpanel-userinfo">
<p id="infoDisplayname" class="subpanel-dispname">DisplayName</p>
<p id="infoUsername" class="subpanel-username">Username</p>
<div class="subpanel-content">
<div class="subpanel-actions">
<img id="menu-logo" class="subpanel-logo" src="/images/FormatLogo_WHITE.svg">
<div id="views-items" class="views-items">
<div class="view-item menu-signout" onclick="window.location = '/login/signout'">
<i class="fas fa-sign-out-alt"></i>
<p>Déconnexion</p>
</div>
</div>
</div>
<div class="subpanel-user">
<div class="subpanel-userinfo">
<p id="infoDisplayname" class="subpanel-dispname">DisplayName</p>
<p id="infoUsername" class="subpanel-username">Username</p>
</div>
<img id="infoUserimage" class="subpanel-image" src="/images/users/default.jpg">
</div>
</div>
<a href="/login/signout"><button class="btn yellow"><span><i class="fa fa-sign-out"></i> Déconnexion</span></button></a>
</div>
<script src="/socket.io/socket.io.js"></script>
<script defer="" src="https://use.fontawesome.com/releases/v6.4.2/js/all.js" crossorigin="anonymous"></script>
<script src="/javascripts/basics.js"></script>
<script src="/javascripts/middle.js"></script>
<script src="/javascripts/io.js"></script>
<script src="/javascripts/link.js"></script>
<script src="/javascripts/service.js"></script>
<script src="/javascripts/server.js"></script>
<script src="/javascripts/filexplorer.js"></script>
<script src="/javascripts/basics.js"></script>
<script src="/javascripts/indexscript.js"></script>
</body>