Version 1.1.0 - Revert to app
All checks were successful
Neutral/pipeline/head This commit looks good

This commit is contained in:
Raphael 2024-12-29 15:38:16 +01:00
commit 1219e6fecc
58 changed files with 10152 additions and 0 deletions

137
.gitignore vendored Normal file
View File

@ -0,0 +1,137 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
#data
data
data/*
private

19
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,19 @@
pipeline {
agent any
stages {
stage('[Neutral] - Déploiement') {
steps {
script {
echo "[Neutral-Deploy] - Deploy Stage"
sh "ssh raphix@raphix.fr sudo apt update -y"
sh "ssh raphix@raphix.fr sudo apt upgrade -y"
sh "ssh raphix@raphix.fr sudo -S -u gitlab-ci /home/gitlab-ci/neutral_deploy.sh"
}
}
}
}
}

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2023 infrastructure
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# neutral
Panel d'administration de Raphix

113
bin/auth.js Normal file
View File

@ -0,0 +1,113 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables")
const alog = new LogType("Authentification")
const keygen = require("./keygen")
const users = require("./users")
/**
* Vérifie si le token est présent et appartient à un utilisateur
* @param {string} token
*/
module.exports.check = function(token) {
var isApproved = false;
var username = null
users.fetchUsers().forEach((fetchUser) => {
if(fetchUser.tokens.includes(token)) {
isApproved = true
username = fetchUser.username
}
})
if(isApproved) {
return true
} else {
if(token) {
alog.warn("Erreur d'authentification - Token n'existe pas : " + token)
}
return false
}
}
/**
* Permet de se connecter à Neutral
* @param {object} data
* @returns Token or AUTH_FAILED
*/
module.exports.login = function(data) {
var username = data.username
var password = data.password
if(users.fetchUsers().has(username)) {
const user = users.fetchUsers().get(username)
if(password == user.getPassword()) {
const token = user.generateToken()
alog.log("Connexion approuvé de l'utilisateur : " + username)
return token
} else {
alog.warn("Echec de connexion de l'utilisateur : " + username + " - Mot de passe incorrect")
return "AUTH_FAILED"
}
} else {
alog.warn("Echec de connexion de l'utilisateur : " + username + " - Utilisateur non-inscrit dans la base de donnée")
return "AUTH_FAILED"
}
}
/**
* Remove the token
* @param {string} token
* @returns
*/
module.exports.signout = function(token) {
var isDone = false;
var username = null
users.fetchUsers().forEach((fetchUser) => {
if(fetchUser.tokens.includes(token)) {
isDone = true
username = fetchUser.username
fetchUser.removeToken(token)
}
})
if(isDone) {
alog.log("Suppression du Token '" + token + "' de l'utilisateur : " + username)
return true
} else {
if(token) {
alog.warn("Erreur d'opération lors de la déconnexion - Token n'existe pas : " + token)
}
return false
}
}
module.exports.getUserByToken = function(token) {
var isApproved = false;
var userGetted = null
users.fetchUsers().forEach((fetchUser) => {
if(fetchUser.tokens.includes(token)) {
userGetted = fetchUser
}
})
if(userGetted) {
return userGetted
} else {
if(token) {
alog.warn("Erreur d'authentification - Token n'existe pas : " + token)
}
return false
}
}

48
bin/config.js Normal file
View File

@ -0,0 +1,48 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables")
const clog = new LogType("Configuration")
setup()
function setup() {
if(!fs.existsSync(__glob.CONFIG)) {
clog.log("Création du fichier de configuration dans : " + __glob.CONFIG)
fs.writeFileSync(__glob.CONFIG, JSON.stringify({
ENCRYPTION_KEY: "1",
}, null, 2))
}
}
/**
*
* @returns Config File
*/
module.exports.getFile = function () {
const file = JSON.parse(fs.readFileSync(__glob.CONFIG))
return file
}
/**
* Update le fichier configuration avec un object
* @param {Array} file
*/
module.exports.updateFile = function (file) {
if(fs.existsSync(__glob.CONFIG)) {
clog.log("Mise à jour du fichier configuration dans : " + __glob.CONFIG)
fs.writeFileSync(__glob.CONFIG, JSON.stringify(file, null, 2))
}
}
module.exports.getSettings = function () {
const file = this.getFile()
return {"jenkins_token": file.JENKINS_TOKEN, "omega_token": file.OMEGA_KEY}
}
module.exports.saveSettings = function (settings) {
const file = this.getFile()
file.JENKINS_TOKEN = settings.jenkins_token
file.OMEGA_KEY = settings.omega_token
this.updateFile(file)
}

268
bin/files.js Normal file
View File

@ -0,0 +1,268 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables")
const clog = new LogType("Fichier")
const os = require("os");
const uuid = require('uuid');
var mime = require('mime-types');
// check if shared folder exist, if not create it
if(!fs.existsSync(__glob.SHARED)) {
fs.mkdirSync(__glob.SHARED)
}
module.exports.getFiles = function(root) {
var response = new Object()
response.content = new Array()
try{
if(root == "homepath") {
root = os.homedir()
}
if(root == "sharepath") {
root = __glob.SHARED
}
if(root == "logpath") {
root = __glob.LOGS
}
if(!fs.existsSync(root)) {
response.content = "NOT_EXIST"
} else {
for(var file of fs.readdirSync(root)) {
const stat = fs.statSync(root + path.sep + file)
response.content.push({"name":file, id: uuid.v4().toString() ,"fileDirectory" : root + path.sep + file , "type":mime.lookup(file), "size":stat.size, "lastedition":stat.mtimeMs, "directory":stat.isDirectory()})
}
}
}catch(err) {
response.content = "NOT_PERMITTED"
}
response.root = root
response.parent = path.dirname(root)
return response
}
module.exports.createFolder = function(root) {
// Check if folder already exist
if(!fs.existsSync(root)) {
try {
fs.mkdirSync(root)
return "OK"
} catch(err) {
return "NOT_PERMITTED"
}
} else {
return 'EXIST'
}
}
module.exports.deleteFile = function(root) {
if(fs.existsSync(root)) {
try {
fs.rmSync(root, { recursive: true, force: true })
return "OK"
} catch(err) {
console.log(err)
return "NOT_PERMITTED"
}
} else {
return "NOT_EXIST"
}
}
module.exports.renameFile = function(settings) {
if(fs.existsSync(settings.root)) {
try {
fs.renameSync(settings.root + path.sep + settings.oldName, settings.root + path.sep + settings.newName)
return "OK"
} catch(err) {
return "NOT_PERMITTED"
}
} else {
return "NOT_EXIST"
}
}
module.exports.shareFile = function(settings) {
if(fs.existsSync(settings.root)) {
try {
//Create a sybolic link to shared folder
fs.symlinkSync(settings.root + path.sep + settings.name, __glob.SHARED + path.sep + settings.name)
// return the link of the file shared
if(process.env.DEV) {
return "http://localhost:3001/shared/" + settings.name
} else {
return "https://neutral.raphix.fr/shared/" + settings.name
}
} catch(err) {
console.log(err)
return "NOT_PERMITTED"
}
} else {
return "NOT_EXIST"
}
}
module.exports.getFile = function(root) {
if(fs.existsSync(root)) {
try {
// Check if the file is an image and if it is return the base64
if(mime.lookup(root).includes("image")) {
return "data:" + mime.lookup(root) + ";base64," + fs.readFileSync(root, "base64")
} else {
return fs.readFileSync(root, "utf-8")
}
} catch(err) {
console.log(err)
return "NOT_PERMITTED"
}
} else {
return "NOT_EXIST"
}
}
module.exports.saveFile = function(settings) {
try {
fs.writeFileSync(settings.root + path.sep + settings.name, settings.content)
return "OK"
} catch(err) {
console.log(err)
return "NOT_PERMITTED"
}
}
module.exports.downloadFile = function(root) {
if(fs.existsSync(root)) {
try {
return fs.readFileSync(root)
} catch(err) {
console.log(err)
return "NOT_PERMITTED"
}
} else {
return "NOT_EXIST"
}
}
module.exports.uploadFile = function(settings) {
try {
fs.writeFileSync(settings.root + path.sep + settings.name, settings.file)
return "OK"
} catch(err) {
console.log(err)
return "NOT_PERMITTED"
}
}
module.exports.newFile = function(settings) {
if(!fs.existsSync(settings)) {
try {
fs.writeFileSync(settings, "")
return "OK"
} catch(err) {
console.log(err)
return "NOT_PERMITTED"
}
} else {
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"
}
}
}

20
bin/global-variables.js Normal file
View File

@ -0,0 +1,20 @@
const path = require("path");
const root = path.resolve(__dirname, '../')
const __glob = {
ROUTES: root + path.sep + "routes" + path.sep,
ROOT: root,
LOGS: root + path.sep + "logs",
ICON: root + path.sep + "public" + path.sep + 'images' + path.sep + "FormatLogo_WHITE.ico",
DATA: root + path.sep + "data",
USERS: root + path.sep + "data" + path.sep + "users.json",
CONFIG: root + path.sep + "data" + path.sep + "config.json",
SHARED: root + path.sep + "data" + path.sep + "shared",
USERS_IMAGES: root + path.sep + "data" + path.sep + "user_images",
PACKAGE_JSON: root + path.sep + "package.json",
};
module.exports = { __glob };

29
bin/keygen.js Normal file
View File

@ -0,0 +1,29 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
var CryptoJS = require("crypto-js")
const { __glob } = require("./global-variables")
const clog = new LogType("KeyGen")
const config = require("./config")
const keypass = config.getFile().ENCRYPTION_KEY
setup()
function setup() {
if(keypass) {
clog.log("Clé de chiffrement trouvé et importé")
} else {
clog.error("Clé de chiffrement inconnu : Passage en mode par défaut")
}
}
module.exports.encrypt = function (text) {
let encryptedText = CryptoJS.AES.encrypt(text, keypass).toString();
return encryptedText;
}
module.exports.decrypt = function(text) {
let decryptedText = CryptoJS.AES.decrypt(text, keypass).toString(CryptoJS.enc.Utf8);
return decryptedText;
}

106
bin/links.js Normal file
View File

@ -0,0 +1,106 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables")
const ulog = new LogType("Links")
const uuid = require("uuid")
const config = require("./config")
const {ApplyLinks} = require("../routes/link")
if(!fs.existsSync(__glob.DATA + path.sep + "links.json")) {
fs.writeFileSync(__glob.DATA + path.sep + "links.json", JSON.stringify([], null, 2))
}
module.exports.getLinks = function() {
return JSON.parse(fs.readFileSync(__glob.DATA + path.sep + "links.json"))
}
const FirstLinkManager = new ApplyLinks(this.getLinks())
module.exports.addLink = function(settings) {
var canDo = true
const links = this.getLinks()
var id = makeid(8)
if(settings.abstractLink) {
settings.dest = id.toString()
}
// Check if a destination already exists between links and if it's the case, we return an error "ALREADY_EXiST"
links.forEach((link) => {
if(link.dest == settings.dest) {
canDo = false
}
})
const link = {
id: id,
title: settings.title,
url: settings.url,
dest: settings.dest,
}
if(canDo) {
links.push(link)
fs.writeFileSync(__glob.DATA + path.sep + "links.json", JSON.stringify(links, null, 2))
const LinkManager = new ApplyLinks(this.getLinks())
return "OK"
} else {
return "ALREADY_EXIST"
}
}
module.exports.removeLink = function(id) {
const links = this.getLinks()
const newLinks = []
links.forEach((link) => {
if(link.id != id) {
newLinks.push(link)
}
})
fs.writeFileSync(__glob.DATA + path.sep + "links.json", JSON.stringify(newLinks, null, 2))
const LinkManager = new ApplyLinks(this.getLinks())
return "OK"
}
module.exports.updateLink = function(id, settings) {
const links = this.getLinks()
const newLinks = []
links.forEach((link) => {
if(link.id == id) {
link.title = settings.title
link.url = settings.url
}
newLinks.push(link)
})
fs.writeFileSync(__glob.DATA + path.sep + "links.json", JSON.stringify(newLinks, null, 2))
const LinkManager = new ApplyLinks(this.getLinks())
return "OK"
}
function makeid(length) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
}

128
bin/metrics.js Normal file
View File

@ -0,0 +1,128 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
var CryptoJS = require("crypto-js")
const { __glob } = require("./global-variables")
const { captureRejectionSymbol } = require("events")
const clog = new LogType("Metrics")
if(!fs.existsSync(__glob.DATA + path.sep + "metrics.json")) {
fs.writeFileSync(__glob.DATA + path.sep + "metrics.json", JSON.stringify([], null, 2))
}
module.exports.getDataMetrics = function() {
return JSON.parse(fs.readFileSync(__glob.DATA + path.sep + "metrics.json"))
}
module.exports.getMetrics = function() {
const metrics = this.getDataMetrics()
var metricsToReturn = new Array()
return new Promise(async (resolve, reject) => {
// Count the number processed
var processed = 0
if(metrics.length == 0) {
resolve(metricsToReturn)
}
await metrics.forEach(async (metric) => {
// Try to connect to the metric server with the key in query params named "privatekey"
// If the connection is successful, we add the metric to the list of metrics to return
const url = `http://${metric.address}:${metric.port}/metrics?privatekey=${metric.key}`
const fet = await fetch(url, {
method: "GET",
headers: {
"Accept": "application/json",
},
credentials: "include"
}).then(res => res.json())
.then(res => {
if(res) {
metric.data = res
metricsToReturn.push(metric)
processed++
} else {
metric.data = "ERROR"
metricsToReturn.push(metric)
}
if(processed == metrics.length) {
resolve(metricsToReturn)
}
}).catch((err) => {
metric.data = "ERROR"
metricsToReturn.push(metric)
processed++
if(processed == metrics.length) {
resolve(metricsToReturn)
}
})
})
})
}
module.exports.addMetric = function(settings) {
const metrics = this.getDataMetrics()
const metric = {
id: makeid(8),
name: settings.name,
address: settings.address,
port: settings.port,
key: settings.key,
}
metrics.push(metric)
fs.writeFileSync(__glob.DATA + path.sep + "metrics.json", JSON.stringify(metrics, null, 2))
return "OK"
}
module.exports.deleteMetric = function(id) {
const metrics = this.getDataMetrics()
metrics.forEach((metric) => {
if(metric.id == id) {
metrics.splice(metrics.indexOf(metric), 1)
}
})
fs.writeFileSync(__glob.DATA + path.sep + "metrics.json", JSON.stringify(metrics, null, 2))
return "OK"
}
function makeid(length) {
var result = [];
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result.push(characters.charAt(Math.floor(Math.random() *
charactersLength)));
}
return result.join('');
}

138
bin/pipelines.js Normal file
View File

@ -0,0 +1,138 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables")
const clog = new LogType("Pïpeline")
const config = require("./config")
const tokenkey = config.getFile().JENKINS_TOKEN
module.exports.getAllPipelines = function() {
return new Promise((resolve, reject) => {
fetch(`https://jenkins.raphix.fr/api/json`, {
method: "GET",
headers: {
"Accept": "application/json",
"Authorization": `Basic ${tokenkey}`
},
credentials: "include"
})
.then(res => res.json())
.then(async list => {
const pipelinesJobs = new Array()
for(const job of list.jobs) {
await fetch(`${job.url}/api/json`, {
method: "GET",
headers: {
"Accept": "application/json",
"Authorization": `Basic ${tokenkey}`
},
credentials: "include"
})
.then(res => res.json())
.then(async res => {
if(res._class == "org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject") {
await getJobMain(res).then(resJ => {
res.jobs[0] = resJ
pipelinesJobs.push(res)
})
} else {
pipelinesJobs.push(res)
}
})
}
list.jobs = pipelinesJobs
resolve(list)
})
.catch(err => {
resolve("UNAVAILABLE")
})
})
}
module.exports.startPipeline = function(pipeline) {
// If it's a freestyle job, build with params
if(pipeline.type == "org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject") {
return new Promise((resolve, reject) => {
fetch(`${pipeline.url}/job/${pipeline.jobname}/build?delay=0sec`, {
method: "POST",
headers: {
"Authorization": `Basic ${tokenkey}`
},
credentials: "include"
})
.then(res => {
resolve("OK")
})
})
} else {
return new Promise((resolve, reject) => {
const parameters = pipeline.fields
const formatedParams = new URLSearchParams()
for(const param of parameters) {
formatedParams.append(param.name, param.value)
}
fetch(`${pipeline.url}/buildWithParameters?delay=0sec`, {
method: "POST",
headers: {
"Authorization": `Basic ${tokenkey}`,
"Content-Type": "application/x-www-form-urlencoded"
},
credentials: "include",
body: formatedParams
})
.then(res => {
resolve("OK")
})
})
}
}
function getJobMain(res) {
return new Promise(async (resolve, reject) => {
await fetch(`${res.jobs[0].url}/api/json`, {
method: "GET",
headers: {
'Accept': 'application/json',
'Authorization': `Basic ${tokenkey}`
},
credentials: 'include'
}).then(res => res.json())
.then(res => {
resolve(res)
})
})
}

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);
});
}
})
}

296
bin/servers.js Normal file
View File

@ -0,0 +1,296 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables.js")
const auth = require("./auth.js")
const users = require("./users.js")
const files = require("./files.js")
const config = require("./config.js")
const links = require("./links.js")
const service = require("./services.js")
const pipeline = require("./pipelines.js")
const plog = new LogType("Web")
const cook = require("cookie")
const http = require("http")
const servermetrics = require("./server-metrics.js")
const metrics = require("./metrics.js")
const pm2 = require('pm2');
/**
* NOTE INTERNE
*
* Changer les pictures de users autre part
*/
/**
*
* @param {http.Server} server
*/
module.exports.serverIO = function(server) {
const io = require('socket.io')(server, {
maxHttpBufferSize: 1e8,
pingTimeout: 60000
})
io.on("connection", (socket) => {
let token = cook.parse(socket.handshake.headers.cookie).token
var user = auth.getUserByToken(token)
if(user) {
plog.log("Connexion au panel par '" + user.username + "' avec le socket : " + socket.id)
user.setLastLogin(new Date())
/**
* GET REQUEST
*/
// Get Users
GetRequest("USERINFO", () => {
user = auth.getUserByToken(token)
GetAnswer("USERINFO", {username: user.username, display_name: user.display_name ,picture: user.picture, permission: user.permission})
})
/**
* POST REQUEST
*/
PostRequest("US_EDIT_PERSONNAL", async (settings) => {
user = auth.getUserByToken(token)
PostAnswer("US_EDIT_PERSONNAL", await users.editMySelf(settings, user))
})
if(user.checkPermission("FILES_EXPLORER")) {
PostRequest("FX_GET", (root) => {
PostAnswer("FX_GET", files.getFiles(root))
})
PostRequest("FX_NEW_FOLDER", (root) => {
PostAnswer("FX_NEW_FOLDER", files.createFolder(root))
})
PostRequest("FX_DELETE", (root) => {
PostAnswer("FX_DELETE", files.deleteFile(root))
} )
PostRequest("FX_RENAME", (settings) => {
PostAnswer("FX_RENAME", files.renameFile(settings))
})
PostRequest("FX_SHARE", (settings) => {
PostAnswer("FX_SHARE", files.shareFile(settings))
})
PostRequest("FX_GETFILE", (root) => {
PostAnswer("FX_GETFILE", files.getFile(root))
})
PostRequest("FX_SAVEFILE", (settings) => {
PostAnswer("FX_SAVEFILE", files.saveFile(settings))
})
PostRequest("FX_NEW_FILE", (settings) => {
PostAnswer("FX_NEW_FILE", files.newFile(settings))
})
PostRequest("FX_UPLOAD", (settings) => {
PostAnswer("FX_UPLOAD", files.uploadFile(settings))
})
PostRequest("FX_PASTE", (settings) => {
PostAnswer("FX_PASTE", files.pasteFile(settings))
})
}
if(user.checkPermission("SERVICES")) {
PostRequest("SV_GET_SERVICE_STATUS", async (sv) => {
PostAnswer("SV_GET_SERVICE_STATUS", {answer: await service.getServiceStatus(sv), name: sv})
})
PostRequest("SV_START_SERVICE", async (sv) => {
PostAnswer("SV_START_SERVICE", {answer: await service.startService(sv), name: sv})
})
PostRequest("SV_STOP_SERVICE", async (sv) => {
PostAnswer("SV_STOP_SERVICE", {answer: await service.stopService(sv), name: sv})
})
PostRequest("SV_RESTART_SERVICE", async (sv) => {
PostAnswer("SV_RESTART_SERVICE", {answer: await service.restartService(sv), name: sv})
})
}
if(user.checkPermission("LINKS")) {
PostRequest("LINKS_GET_ALL", () => {
PostAnswer("LINKS_GET_ALL", {answer: "OK", links: links.getLinks()})
})
PostRequest("LINKS_ADD", (settings) => {
PostAnswer("LINKS_ADD", {answer: links.addLink(settings)})
})
PostRequest("LINKS_DELETE", (id) => {
PostAnswer("LINKS_DELETE", links.removeLink(id))
})
PostRequest("LINKS_EDIT", (settings) => {
PostAnswer("LINKS_EDIT", links.updateLink(settings.id, settings))
})
}
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});
});
}
if(user.checkPermission("PIPELINES")) {
GetRequest("PL_GET_ALL", async () => {
GetAnswer("PL_GET_ALL", await pipeline.getAllPipelines())
})
PostRequest("PL_START", async (settings) => {
PostAnswer("PL_START", await pipeline.startPipeline(settings))
})
}
if(user.checkPermission("USERS")) {
GetRequest("US_ALL", async () => {
GetAnswer("US_ALL", await users.getAllUsers())
})
PostRequest("US_ADD", async (settings) => {
PostAnswer("US_ADD", await users.addUser(settings))
})
PostRequest("US_DELETE", async (settings) => {
PostAnswer("US_DELETE", await users.deleteUser(settings))
})
PostRequest("US_EDIT", async (settings) => {
PostAnswer("US_EDIT", await users.editUser(settings))
})
PostRequest("US_CLEAR_TOKENS", async (settings) => {
PostAnswer("US_CLEAR_TOKENS", await users.clearTokens(settings))
})
}
if(user.checkPermission("SETTINGS")) {
PostRequest("SETTINGS_SAVE", async (settings) => {
PostAnswer("SETTINGS_SAVE", await config.saveSettings(settings))
})
PostRequest("SERVER_RESTART", async () => {
pm2.restart('Neutral')
})
PostRequest("SERVER_STOP", async () => {
pm2.stop('Neutral')
})
GetRequest("SERVER_GET_LOGS", async () => {
GetAnswer("SERVER_GET_LOGS", await fs.readdirSync(__glob.LOGS))
})
PostRequest("SERVER_READ_LOG", async (logs) => {
PostAnswer("SERVER_READ_LOG", await fs.readFileSync(__glob.LOGS + path.sep + logs).toString())
})
GetRequest("SETTINGS_GET", async () => {
GetAnswer("SETTINGS_GET", await config.getSettings())
})
}
if(user.checkPermission("METRICS")) {
GetRequest("MT_ALL", async () => {
GetAnswer("MT_ALL", await metrics.getMetrics())
})
PostRequest("MT_ADD", async (settings) => {
PostAnswer("MT_ADD", await metrics.addMetric(settings))
})
PostRequest("MT_DELETE", async (settings) => {
PostAnswer("MT_DELETE", await metrics.deleteMetric(settings))
})
}
socket.on("disconnect", (reason) => {
plog.log("Déconnexion du panel par '" + user.username + "' avec le socket : " + socket.id)
})
socket.on("connect_error", (err) => {
console.log(err)
console.log(err.message); // prints the message associated with the error
});
function GetRequest(GQname, GQcallback) {
socket.on("GET/" + GQname, () => {
plog.log(user.username + " - Socket : " + socket.id + " - GET/" + GQname + " - [RECIEVED]")
GQcallback()
})
}
function GetAnswer(GRname, GRvalue) {
plog.log(user.username + " - Socket : " + socket.id + " - GET/" + GRname + " - [ANSWERED]")
socket.emit("ANSWER/GET/" + GRname, GRvalue)
}
function PostRequest(GQname, GQcallback) {
socket.on("POST/" + GQname, (value) => {
plog.log(user.username + " - Socket : " + socket.id + " - POST/" + GQname + " - [RECIEVED]")
GQcallback(value)
})
}
function PostAnswer(GRname, GRvalue) {
plog.log(user.username + " - Socket : " + socket.id + " - POST/" + GRname + " - [ANSWERED]")
socket.emit("ANSWER/POST/" + GRname, GRvalue)
}
} else {
socket.disconnect()
plog.warn("Connexion directe vers le panel avec un token inexistant : " + this.token)
}
})
}

137
bin/services.js Normal file
View File

@ -0,0 +1,137 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables")
const clog = new LogType("Services")
const http = require('http');
const https = require('https');
module.exports.getServiceStatus = function(service) {
const protocol = service.startsWith('https') ? https : http;
const url = new URL(service);
const options = {
method: 'HEAD',
host: url.hostname,
port: url.port,
path: url.pathname,
};
return new Promise((resolve, reject) => {
const req = protocol.request(options, (res) => {
if(res.statusCode !== 502) {
resolve("ONLINE");
} else {
resolve("OFFLINE");
}
});
req.on('error', (err) => {
resolve("OFFLINE");
});
req.end();
});
}
module.exports.stopService = function(service) {
return new Promise((resolve, reject) => {
const child_process = require('child_process');
if(service == `https://subsonics.raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@alpha.raphix.fr sudo -S -u gitlab-ci 'pm2 stop "Subsonics"'`)
resolve("OK")
} else if(service == `https://git.raphix.fr` ) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl stop gitea`)
resolve("OK")
} else if(service == `https://jenkins.raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl stop jenkins `)
resolve("OK")
} else if(service == `https://raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@alpha.raphix.fr sudo -S -u gitlab-ci 'pm2 stop "Website - Raphix"'`)
resolve("OK")
} else if(service == `https://cv.raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@alpha.raphix.fr sudo -S -u gitlab-ci 'pm2 stop "CV - Raphael"'`)
resolve("OK")
} else if(service == `http://omega.raphix.fr:2333`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl stop lavalink `)
resolve("OK")
} else {
resolve(false)
}
});
}
module.exports.startService = function(service) {
return new Promise((resolve, reject) => {
const child_process = require('child_process');
if(service == `https://subsonics.raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@alpha.raphix.fr sudo -S -u gitlab-ci 'pm2 start /home/gitlab-ci/subsonic.config.js'`)
resolve("OK")
} else if(service == `https://git.raphix.fr` ) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl start gitea`)
resolve("OK")
} else if(service == `https://jenkins.raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl start jenkins `)
resolve("OK")
} else if(service == `https://raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@alpha.raphix.fr sudo -S -u gitlab-ci 'pm2 start /home/gitlab-ci/website.config.js'`)
resolve("OK")
} else if(service == `https://cv.raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@alpha.raphix.fr sudo -S -u gitlab-ci 'pm2 start /home/gitlab-ci/cv.config.js'`)
resolve("OK")
} else if(service == `http://omega.raphix.fr:2333`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl start lavalink `)
resolve("OK")
} else {
resolve(false)
}
});
}
module.exports.restartService = function(service) {
return new Promise((resolve, reject) => {
const child_process = require('child_process');
if(service == `https://subsonics.raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@alpha.raphix.fr sudo -S -u gitlab-ci 'pm2 restart "Subsonics"'`)
resolve("OK")
} else if(service == `https://git.raphix.fr` ) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl restart gitea`)
resolve("OK")
} else if(service == `https://jenkins.raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl restart jenkins `)
resolve("OK")
} else if(service == `https://raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@alpha.raphix.fr sudo -S -u gitlab-ci 'pm2 restart "Website - Raphix"'`)
resolve("OK")
} else if(service == `https://cv.raphix.fr`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@alpha.raphix.fr sudo -S -u gitlab-ci 'pm2 restart "CV - Raphael"'`)
resolve("OK")
} else if(service == `http://omega.raphix.fr:2333`) {
let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl restart lavalink `)
resolve("OK")
} else {
resolve(false)
}
});
}

544
bin/users.js Normal file
View File

@ -0,0 +1,544 @@
const { LogType } = require("loguix")
const fs = require("fs")
const path = require("path")
const { __glob } = require("./global-variables")
const ulog = new LogType("Users")
const keygen = require("./keygen")
const uuid = require("uuid")
var usersList = new Map()
setup()
function setup() {
if(!fs.existsSync(__glob.USERS)) {
ulog.log("Création du fichier utilisateur dans : " + __glob.USERS)
fs.writeFileSync(__glob.USERS, JSON.stringify([], null, 2))
}
}
module.exports.getAllUsers = async function() {
return new Promise(async (resolve, reject) => {
const users = await this.fetchUsers()
// Remove for every people the password & the tokens
for(var user of users) {
user = user[1]
user.password = null
user.tokens = null
}
resolve(JSON.stringify(Array.from(users)))
})
}
/**
* Get all users from Users Data Base
*/
module.exports.fetchUsers = function () {
ulog.step.init("fetch_user", "Récupération de tous les utilisateurs inscrit dans la base de donnée")
const userFile = getFile()
usersList = new Map()
for(var userFetched of userFile) {
const user = new this.User({
username: userFetched.username,
password: userFetched.password,
display_name: userFetched.display_name,
permission: userFetched.permission,
tokens: userFetched.tokens,
lastLogin: userFetched.lastLogin,
picture: userFetched.picture
})
usersList.set(user.username, user)
}
if(usersList.size == 0) {
const adminUser = new this.User({
"username": "admin",
"password": "neutral",
"display_name": "Administrateur",
"permission": [
"FILES_EXPLORER",
"SERVICES",
"SERVERS",
"PIPELINES",
"METRICS",
"USERS",
"LINKS",
"SETTINGS"
],
"tokens": [],
"lastLogin": "DEFAULT ACCOUNT",
"picture": "/images/default.jpg"
})
adminUser.register()
}
ulog.step.end("fetch_user")
return usersList
}
/**
* User Class is used to access to default user's properties and methods
* @param {object} properties User properties with : username, password, display_name, permission...
*/
module.exports.User = class {
username = null
password = null;
display_name = null
permission = []
tokens = []
lastLogin = new Date()
picture = "/images/default.jpg"
constructor(properties) {
if(properties) {
this.username = properties.username
this.password = keygen.encrypt(properties.password)
this.display_name = properties.display_name
this.permission = properties.permission
this.tokens = properties.tokens
this.lastLogin = properties.lastLogin
this.picture = properties.picture
const userFile = getFile()
for(var userFetched of userFile) {
if(properties.username == userFetched.username) {
ulog.log("Récupération dans la base de donnée, de l'utilisateur : " + userFetched.username)
this.username = userFetched.username
this.password = userFetched.password
this.display_name = userFetched.display_name
this.permission = userFetched.permission
this.tokens = userFetched.tokens
this.lastLogin = userFetched.lastLogin
}
}
}
if(this.username == null) {
ulog.error("One of user is without username ! [IMPORANT_FIELD_IS_MISSING]")
this.username = Math.random()
}
if(this.password == null) {
ulog.error("'" + this.username + "' is without password ! Password reset to 'default' [IMPORANT_FIELD_IS_MISSING]")
this.password = keygen.encrypt("default")
}
if(this.display_name == null) {
ulog.warn("'" + this.username + "' is without display name !")
this.display_name = this.username
}
if(this.permission == null) {
ulog.warn("'" + this.username + "' has no longer permission !")
}
if(this.tokens == null) {
this.tokens = []
}
if(this.permission == null) {
this.permission = []
}
if(this.lastLogin == null) {
this.lastLogin = new Date()
}
if(this.picture == null) {
this.picture = "/images/default.jpg"
}
}
register() {
var alreadyExist = false
const userFile = getFile()
for(var userFetched of userFile) {
if(userFetched.username == this.username) {
userFile.splice(userFile.indexOf(userFetched), 1)
ulog.log("Mise à jour dans la base de donnée, de l'utilisateur : " + this.username)
alreadyExist = true
}
}
if(!alreadyExist) {
ulog.log("Création dans la base de donnée de l'utilisateur : " + this.username)
}
userFile.push(this)
updateFile(userFile)
usersList.set(this.username, this)
}
unregister() {
var alreadyExist = false
const userFile = getFile()
for(var userFetched of userFile) {
if(userFetched.username == this.username) {
userFile.splice(userFile.indexOf(userFetched), 1)
ulog.log("Mise à jour dans la base de donnée, de l'utilisateur : " + this.username)
alreadyExist = true
}
}
if(!alreadyExist) {
ulog.log("L'utilisateur n'est pas enregistré dans la base de donnée : " + this.username)
}
updateFile(userFile)
usersList.delete(this.username)
}
checkPermission(name) {
this.#sync()
if(this.permission.includes(name)) {
return true
} else {
return false
}
}
addPermission(name) {
this.#sync()
for(var perms of this.permission) {
if(name == perms) {
ulog.warn("'" + this.username + "' a déjà la permission : " + name)
return false
}
}
this.permission.push(name)
this.register()
}
removePermission(name) {
this.#sync()
var havePermission = false
for(var perms of this.permission) {
if(name == perms) {
havePermission = true
}
}
if(havePermission) {
this.permission.splice(this.permission.indexOf(name), 1)
this.register()
} else {
ulog.warn("'" + this.username + "' n'a pas la permission : " + name)
return false
}
}
setPassword(newPassword) {
this.#sync()
this.password = keygen.encrypt(newPassword)
this.register()
ulog.log("Le mot de passe de l'utilisateur a été modifié : " + this.username)
}
getPassword() {
this.#sync()
return keygen.decrypt(this.password)
}
generateToken() {
this.#sync()
const gToken = uuid.v4().toString()
this.tokens.push(gToken)
this.register()
return gToken
}
removeToken(token) {
this.#sync()
var haveToken = false
for(var aToken of this.tokens) {
if(token == aToken) {
haveToken = true
}
}
if(haveToken) {
this.tokens.splice(this.tokens.indexOf(token), 1)
this.register()
} else {
ulog.warn("'" + this.username + "' n'a pas le token : " + token)
return false
}
}
setPicture(text) {
this.#sync()
this.picture = text
this.register()
ulog.log("La photo de l'utilisateur a été modifié : " + this.username)
}
setDisplayName(text) {
this.#sync()
this.display_name = text
this.register()
ulog.log("Le nom d'affichage de l'utilisateur a été modifié : " + this.username)
}
setNewUsername(text) {
this.#sync()
module.exports.deleteUser(this.username)
this.username = text
this.register()
ulog.log("Le nom d'utilisateur de l'utilisateur a été modifié : " + this.username)
}
setLastLogin(text) {
this.#sync()
this.lastLogin = text
this.register()
}
setPicture(file) {
this.#sync()
var pictureDir = __glob.USERS_IMAGES + path.sep + uuid.v4().toString() + ".png"
if(!fs.existsSync(__glob.USERS_IMAGES)) {
fs.mkdirSync(__glob.USERS_IMAGES)
}
fs.writeFileSync(pictureDir, file)
this.picture = pictureDir.replace(__glob.USERS_IMAGES + path.sep, "/users/")
this.register()
}
setPermissions(permissions) {
this.#sync()
this.permission = permissions
this.register()
}
clearTokens() {
this.#sync()
this.tokens = []
this.register()
}
#sync() {
for(var userGet of usersList.keys()) {
const userFetched = usersList.get(userGet)
if(this.username == userFetched.username) {
this.username = userFetched.username
this.password = userFetched.password
this.display_name = userFetched.display_name
this.permission = userFetched.permission
this.tokens = userFetched.tokens
this.lastLogin = userFetched.lastLogin
}
}
}
}
module.exports.addUser = function(settings) {
if(settings.username == '') {
ulog.error("Le nom d'utilisateur est manquant")
return "USERNAME_MISSING"
} else if(settings.password == '') {
ulog.error("Le mot de passe est manquant")
return "PASSWORD_MISSING"
} else if(settings.display_name == '') {
ulog.error("Le nom d'affichage est manquant")
return "DISPLAY_NAME_MISSING"
} else if(this.getUser(settings.username)) {
ulog.error("L'utilisateur existe déjà : " + settings.username)
return "ALREADY_EXIST"
} else {
ulog.step.init("add_user", "Ajout d'un utilisateur dans la base de donnée : " + settings.username)
var pictureDir = null
if(settings.picture == null) {
pictureDir = "/images/default.jpg"
} else {
pictureDir = __glob.USERS_IMAGES + path.sep + uuid.v4().toString() + ".png"
fs.writeFileSync(pictureDir, settings.picture)
}
const user = new this.User({
username: settings.username,
display_name: settings.display_name,
permission: settings.permissions,
picture: pictureDir.replace(__glob.USERS_IMAGES + path.sep, "/images/users/")
})
user.setPassword(settings.password)
user.register()
ulog.step.end("add_user")
}
}
module.exports.deleteUser = function(username) {
ulog.step.init("delete_user", "Suppression d'un utilisateur dans la base de donnée : " + username)
const user = this.getUser(username)
user.unregister()
ulog.step.end("delete_user")
return "OK"
}
module.exports.getUser = function(username) {
return usersList.get(username)
}
module.exports.editUser = function(settings) {
if(settings.username == '') {
ulog.error("Le nom d'utilisateur est manquant")
return "USERNAME_MISSING"
} else if(settings.display_name == '') {
ulog.error("Le nom d'affichage est manquant")
return "DISPLAY_NAME_MISSING"
} else {
ulog.step.init("edit_user", "Modification d'un utilisateur dans la base de donnée : " + settings.username)
const user = this.fetchUsers().get(settings.username)
if(user) {
if(settings.newusername && settings.newusername != settings.username) {
if(this.getUser(settings.newusername)) {
ulog.error("L'utilisateur existe déjà : " + settings.username)
return "ALREADY_EXIST"
} else {
user.setNewUsername(settings.newusername)
}
}
if(settings.display_name) {
user.setDisplayName(settings.display_name)
}
if(settings.password) {
user.setPassword(settings.password)
}
if(settings.picture) {
user.setPicture(settings.picture)
}
if(settings.permissions) {
user.setPermissions(settings.permissions)
}
ulog.step.end("edit_user")
return "OK"
} else {
ulog.step.end("edit_user")
return "NOT_EXIST"
}
}
}
module.exports.editMySelf = function (settings, user) {
if(user.username == settings.username) {
if(settings.username == '') {
ulog.error("Le nom d'utilisateur est manquant")
return "USERNAME_MISSING"
} else if(settings.display_name == '') {
ulog.error("Le nom d'affichage est manquant")
return "DISPLAY_NAME_MISSING"
} else {
ulog.step.init("edit_user", "Modification d'un utilisateur dans la base de donnée : " + settings.username)
const user = this.fetchUsers().get(settings.username)
if(user) {
console.log(settings)
if(settings.newusername && settings.newusername != settings.username) {
if(this.getUser(settings.newusername)) {
ulog.error("L'utilisateur existe déjà : " + settings.username)
return "ALREADY_EXIST"
} else {
user.setNewUsername(settings.newusername)
}
}
if(settings.display_name) {
user.setDisplayName(settings.display_name)
}
if(settings.password) {
user.setPassword(settings.password)
}
if(settings.picture) {
user.setPicture(settings.picture)
}
ulog.step.end("edit_user")
return "OK"
} else {
ulog.step.end("edit_user")
return "NOT_EXIST"
}
}
} else {
ulog.error("Vous ne pouvez pas modifier les informations d'un autre utilisateur !")
return "NOT_ALLOWED"
}
}
module.exports.clearTokens = function(username) {
const user = this.fetchUsers().get(username)
user.clearTokens()
}
/**
*
* @returns User File
*/
function getFile() {
const file = JSON.parse(fs.readFileSync(__glob.USERS))
return file
}
/**
* Update le fichier utilisateur avec un object
* @param {Array} file
*/
function updateFile(file) {
if(fs.existsSync(__glob.USERS)) {
ulog.log("Mise à jour du fichier utilisateur dans : " + __glob.USERS)
fs.writeFileSync(__glob.USERS, JSON.stringify(file, null, 2))
}
}

107
bin/www Normal file
View File

@ -0,0 +1,107 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var log = require("loguix")
var {LogType} = require("loguix")
var { __glob } = require("./global-variables")
log.setup(__glob.LOGS, __glob.PACKAGE_JSON)
const wlog = new LogType("Serveur")
if(process.env.DEV ) {
wlog.log("MODE DEVELOPEMENT ACTIF")
}
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("./servers")
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3001');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
serverIO.serverIO(server)
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
wlog.step.error("start_server", bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
wlog.step.error("start_server" , bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
wlog.log("Serveur entrain d'écouter sur le port : " + server.address().port)
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
wlog.step.end("start_server")
}

87
main.js Normal file
View File

@ -0,0 +1,87 @@
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
var favicon = require('serve-favicon');
const app = express();
const fs = require("fs");
const log = require("loguix")
const { __glob } = require('./bin/global-variables');
const users = require("./bin/users")
const {User} = require("./bin/users")
var wlog = log.getInstance("Serveur")
setup()
function getRouters() {
wlog.log("Récupération de " + fs.readdirSync(__glob.ROUTES).length + " routeurs depuis : " + __glob.ROUTES)
for(var route of fs.readdirSync(__glob.ROUTES)) {
if(route == "index.js") {
app.use("/", require(__glob.ROUTES + "index"))
} else {
app.use("/" + route.replace(".js", ""), require(__glob.ROUTES + route))
}
}
}
function setup() {
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
app.use("/shared", express.static(__glob.SHARED))
app.use("/users", express.static(__glob.USERS_IMAGES))
getRouters()
users.fetchUsers()
// catch 404 and forward to error handler
app.use(function(req, res, next) {
res.locals.message = "Page non trouvé";
res.locals.error = {
"status": "404",
"stack": ""
}
// render the error page
res.status(404 || 404);
res.render('utils/error');
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('utils/error');
});
}
module.exports = app;

2523
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "neutral",
"version": "1.1.0",
"description": "Panel d'administration de Raphix",
"main": "index.js",
"scripts": {
"start": "nodemon ./bin/www",
"dev": "set DEV=true & nodemon ./bin/www"
},
"repository": {
"type": "git",
"url": "https://git.raphix.fr/infrastructure/neutral.git"
},
"nodemonConfig": {
"ext": "js, html",
"ignore": [
"*.json"
],
"delay": "10000000"
},
"keywords": [],
"dependencies": {
"cookie-parser": "~1.4.4",
"crypto-js": "^4.2.0",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"install": "^0.13.0",
"loguix": "^1.4.2",
"nodemon": "^3.0.1",
"os-utils": "^0.0.14",
"pm2": "^5.3.0",
"serve-favicon": "^2.0.4",
"socket.io": "^4.7.2",
"uuid": "^9.0.1"
},
"author": "Raphix",
"license": "ISC"
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/images/FontLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 481.43 481.43">
<defs>
<style>
.cls-1 {
stroke-width: 40px;
}
.cls-1, .cls-2, .cls-3, .cls-4, .cls-5 {
stroke: #1d1d1b;
stroke-miterlimit: 10;
}
.cls-1, .cls-3, .cls-5 {
fill: none;
}
.cls-2, .cls-3 {
stroke-width: 2px;
}
.cls-2, .cls-4 {
fill: #1d1d1b;
}
.cls-5 {
stroke-width: 10px;
}
</style>
</defs>
<circle class="cls-1" cx="240.78" cy="240.65" r="190.13"/>
<circle class="cls-1" cx="240.78" cy="240.65" r="112.42"/>
<circle class="cls-5" cx="240.78" cy="240.65" r="52"/>
<circle class="cls-5" cx="240.78" cy="240.65" r="36"/>
<circle class="cls-3" cx="240.78" cy="240.65" r="17.5"/>
<line class="cls-2" x1="240.78" y1="187.65" x2="240.78" y2="204.13"/>
<line class="cls-2" x1="293.78" y1="240.65" x2="276.29" y2="240.65"/>
<path class="cls-2" d="m228.23,252.44c-3.81,3.81-7.62,7.62-11.43,11.43"/>
<path class="cls-4" d="m240.05,7.01c7.99,14.68,16.4,32.59,23.67,53.57,6.88,19.85,11.25,38.14,14.07,53.93-9.15-2.76-22.44-5.68-38.61-5.59-15.41.08-28.12,2.87-37.04,5.55,2.78-15.73,7.13-34.02,14.04-53.88,7.32-21.05,15.82-38.96,23.86-53.57Z"/>
<path class="cls-4" d="m240.86,474.3c7.99-14.68,16.4-32.59,23.67-53.57,6.88-19.85,11.25-38.14,14.07-53.93-9.15,2.76-22.44,5.68-38.61,5.59-15.41-.08-28.12-2.87-37.04-5.55,2.78,15.73,7.13,34.02,14.04,53.88,7.32,21.05,15.82,38.96,23.86,53.57Z"/>
<path class="cls-4" d="m7.05,240.83c14.68-7.99,32.59-16.4,53.57-23.67,19.85-6.88,38.14-11.25,53.93-14.07-2.76,9.15-5.68,22.44-5.59,38.61.08,15.41,2.87,28.12,5.55,37.04-15.73-2.78-34.02-7.13-53.88-14.04-21.05-7.32-38.96-15.82-53.57-23.86Z"/>
<path class="cls-4" d="m474.27,240.61c-14.68-7.99-32.59-16.4-53.57-23.67-19.85-6.88-38.14-11.25-53.93-14.07,2.76,9.15,5.68,22.44,5.59,38.61-.08,15.41-2.87,28.12-5.55,37.04,15.73-2.78,34.02-7.13,53.88-14.04,21.05-7.32,38.96-15.82,53.57-23.86Z"/>
<line class="cls-2" x1="203.8" y1="240.69" x2="186.31" y2="240.69"/>
<line class="cls-2" x1="240.78" y1="275.18" x2="240.78" y2="291.66"/>
<path class="cls-2" d="m264.13,216.54c-3.94,3.94-7.87,7.87-11.81,11.81"/>
<g>
<path class="cls-2" d="m253.02,253.15c3.81,3.81,7.62,7.62,11.43,11.43"/>
<path class="cls-2" d="m217.12,217.25c3.94,3.94,7.87,7.87,11.81,11.81"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 481.43 481.43">
<defs>
<style>
.cls-1 {
stroke-width: 40px;
}
.cls-1, .cls-2, .cls-3, .cls-4, .cls-5 {
stroke: #ffffff;
stroke-miterlimit: 10;
}
.cls-1, .cls-3, .cls-5 {
fill: none;
}
.cls-2, .cls-3 {
stroke-width: 2px;
}
.cls-2, .cls-4 {
fill: #ffffff;
}
.cls-5 {
stroke-width: 10px;
}
</style>
</defs>
<circle class="cls-1" cx="240.78" cy="240.65" r="190.13"/>
<circle class="cls-1" cx="240.78" cy="240.65" r="112.42"/>
<circle class="cls-5" cx="240.78" cy="240.65" r="52"/>
<circle class="cls-5" cx="240.78" cy="240.65" r="36"/>
<circle class="cls-3" cx="240.78" cy="240.65" r="17.5"/>
<line class="cls-2" x1="240.78" y1="187.65" x2="240.78" y2="204.13"/>
<line class="cls-2" x1="293.78" y1="240.65" x2="276.29" y2="240.65"/>
<path class="cls-2" d="m228.23,252.44c-3.81,3.81-7.62,7.62-11.43,11.43"/>
<path class="cls-4" d="m240.05,7.01c7.99,14.68,16.4,32.59,23.67,53.57,6.88,19.85,11.25,38.14,14.07,53.93-9.15-2.76-22.44-5.68-38.61-5.59-15.41.08-28.12,2.87-37.04,5.55,2.78-15.73,7.13-34.02,14.04-53.88,7.32-21.05,15.82-38.96,23.86-53.57Z"/>
<path class="cls-4" d="m240.86,474.3c7.99-14.68,16.4-32.59,23.67-53.57,6.88-19.85,11.25-38.14,14.07-53.93-9.15,2.76-22.44,5.68-38.61,5.59-15.41-.08-28.12-2.87-37.04-5.55,2.78,15.73,7.13,34.02,14.04,53.88,7.32,21.05,15.82,38.96,23.86,53.57Z"/>
<path class="cls-4" d="m7.05,240.83c14.68-7.99,32.59-16.4,53.57-23.67,19.85-6.88,38.14-11.25,53.93-14.07-2.76,9.15-5.68,22.44-5.59,38.61.08,15.41,2.87,28.12,5.55,37.04-15.73-2.78-34.02-7.13-53.88-14.04-21.05-7.32-38.96-15.82-53.57-23.86Z"/>
<path class="cls-4" d="m474.27,240.61c-14.68-7.99-32.59-16.4-53.57-23.67-19.85-6.88-38.14-11.25-53.93-14.07,2.76,9.15,5.68,22.44,5.59,38.61-.08,15.41-2.87,28.12-5.55,37.04,15.73-2.78,34.02-7.13,53.88-14.04,21.05-7.32,38.96-15.82,53.57-23.86Z"/>
<line class="cls-2" x1="203.8" y1="240.69" x2="186.31" y2="240.69"/>
<line class="cls-2" x1="240.78" y1="275.18" x2="240.78" y2="291.66"/>
<path class="cls-2" d="m264.13,216.54c-3.94,3.94-7.87,7.87-11.81,11.81"/>
<g>
<path class="cls-2" d="m253.02,253.15c3.81,3.81,7.62,7.62,11.43,11.43"/>
<path class="cls-2" d="m217.12,217.25c3.94,3.94,7.87,7.87,11.81,11.81"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
public/images/default.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="main_outline" x="0px" y="0px" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640;" xml:space="preserve"><g><path id="teabag" style="fill:#ffffff00" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"/><g><g><path style="fill: white;" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2 c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5 c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5 c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3 c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1 C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4 c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7 S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55 c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8 l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"/><path style="fill:white;" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4 c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1 c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9 c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3 c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3 c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29 c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8 C343.2,346.5,335,363.3,326.8,380.1z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 100 100"
width="100%"
height="100%"
version="1.1"
id="svg13"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs7">
<linearGradient
id="grad"
x1="0"
y1="0"
x2="100"
y2="100"
spreadMethod="pad"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
stop-color="#ff8c00"
id="stop2"
style="stop-color:#fa9800;stop-opacity:1;" />
<stop
offset="100"
stop-color="#ff0000"
id="stop4"
style="stop-color:#ff005e;stop-opacity:1;" />
</linearGradient>
</defs>
<rect
width="100%"
height="100%"
fill="url(#grad)"
id="rect9"
x="0"
y="0"
style="fill:url(#grad)" />
<path
style="vector-effect:none;fill:#ffffff;fill-opacity:1;stroke-width:1.27808;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
d="m 57,25 h 13 l -7,27 h 12 l -2,8.5 H 48 Z"
id="path724-5" />
<path
style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke-width:1.33907;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000;stop-opacity:1"
d="m 37.5,25 h 14 l -10,40 H 72 L 70,75 H 25 Z"
id="path724" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

1332
public/javascripts/basics.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,786 @@
document.addEventListener("contextmenu", (e) => {
e.preventDefault()
})
explorer.createWindow(() => {
var inCopyOrCut = null;
const View = new ViewWindow({
title: `<i class="fa fa-folder"></i> Gestionnaire de fichiers`,
width: "1000px",
height: "600px"
})
goHomePath()
function goHomePath() {
const rFiles = post("FX_GET", "homepath")
rFiles.then((result) => {
loadFiles(result)
})
}
function goSharePath() {
const rFiles = post("FX_GET", "sharepath")
rFiles.then((result) => {
loadFiles(result)
})
}
View.setContent(`
<div class="fx-window">
<div class='fx-bar'>
<span id='${View.getViewTitle()}_home' class='btn-cover'><i class='fa fa-home'></i></span>
<span id='${View.getViewTitle()}_sharebtn' class='btn-cover'><i class="fa-solid fa-share-from-square"></i></span>
<input class='fx-root-input' type="text" id='${View.getViewTitle()}_rootInput'>
<div class='fx-bar-actions'>
<button id='${View.getViewTitle()}_newFolder' class='btn blue'><span><i class='fa fa-folder'></i> Nouv. dossier</span></button>
<button id='${View.getViewTitle()}_newFile' class='btn green'><span><i class="fa-solid fa-file-arrow-up"></i> Nouveau</span></button>
</div>
</div>
<div id='${View.getViewTitle()}_explorer' class='fx-explorer'>
<div style='font-size: 24px; margin-top: 225px;' class='t-center'>
<p><i class="fa-solid fa-rotate fa-spin"></i> Chargement en cours ...</p>
</div>
</div>
</div>
`)
const rootInput = getID(View.getViewTitle() + '_rootInput')
const explorer = getID(View.getViewTitle() + '_explorer')
const newFolder = getID(View.getViewTitle() + '_newFolder')
const newFile = getID(View.getViewTitle() + '_newFile')
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()
})
sharebtn.addEventListener("click", () => {
goSharePath()
})
newFolder.addEventListener("click", () => {
View.createPopup({
title: `<i class='fa fa-folder'></i> Nouveau dossier`,
content: `
<input type='text' class='field' id='${View.getViewTitle()}_foldername'>
<div id='${View.getViewTitle()}_folderInfo'></div>
<button class='btn green' id='${View.getViewTitle()}_foldercreate'><span><i class='fa fa-add'></i> Créer</span></button>
`})
const foldername = getID(View.getViewTitle() + "_foldername")
const foldercreate = getID(View.getViewTitle() + "_foldercreate")
const folderInfo = new TextResponse(View.getViewTitle() + "_folderInfo")
folderInfo.setSize("13px")
foldercreate.addEventListener("click", () => {
folderInfo.clear()
if(foldername.value.length < 1) {
folderInfo.err("Le nom du dossier est trop court.")
return
}
const regex = new RegExp(/^[a-zA-Z0-9-_]+$/)
if(!regex.test(foldername.value) | foldername.value.replace(/\s/g, '').length < 1) {
folderInfo.err("Le nom du dossier est invalide.")
return
}
const reqFiles = post("FX_NEW_FOLDER", rootInput.value + "/" + foldername.value)
reqFiles.then((result) => {
if(result == "OK") {
View.destroyPopup(`<i class="fa fa-folder"></i> Nouveau dossier`)
const reqFiles = post("FX_GET", rootInput.value)
reqFiles.then((result) => {
loadFiles(result)
})
} else if(result == "EXIST") {
folderInfo.err("Le dossier existe déjà.")
} else if(result == "NOT_PERMITTED") {
folderInfo.err("Vous n'avez pas les permissions pour créer un dossier ici.")
} else {
folderInfo.err("Une erreur est survenue.")
}
})
})
})
// Create a new file with a popup wth 2 options, upload or create, if create give a input name or if upload, upload a file
newFile.addEventListener("click", () => {
View.createPopup({
title: `<i class='fa-solid fa-file-arrow-up'></i> Nouveau fichier`,
content: `
<input type='text' class='field' id='${View.getViewTitle()}_filename'>
<div id='${View.getViewTitle()}_fileInfo'></div>
<button class='btn green' id='${View.getViewTitle()}_filecreate'><span><i class='fa fa-add'></i> Créer</span></button>
<button class='btn blue' id='${View.getViewTitle()}_fileupload'><span><i class='fa fa-upload'></i> Upload</span></button>
`})
const filename = getID(View.getViewTitle() + "_filename")
const filecreate = getID(View.getViewTitle() + "_filecreate")
const fileupload = getID(View.getViewTitle() + "_fileupload")
const fileInfo = new TextResponse(View.getViewTitle() + "_fileInfo")
fileInfo.setSize("13px")
filecreate.addEventListener("click", () => {
fileInfo.clear()
if(filename.value.length < 1) {
fileInfo.err("Le nom du fichier est trop court.")
return
}
const regex = new RegExp(/^[a-zA-Z0-9-_.]+$/)
if(!regex.test(filename.value) | filename.value.replace(/\s/g, '').length < 1) {
fileInfo.err("Le nom du fichier est invalide.")
return
}
const reqFiles = post("FX_NEW_FILE", rootInput.value + "/" + filename.value)
reqFiles.then((result) => {
if(result == "OK") {
View.destroyPopup(`<i class="fa-solid fa-file-arrow-up"></i> Nouveau fichier`)
const reqFiles = post("FX_GET", rootInput.value)
reqFiles.then((result) => {
loadFiles(result)
})
} else if(result == "EXIST") {
fileInfo.err("Le fichier existe déjà.")
} else if(result == "NOT_PERMITTED") {
fileInfo.err("Vous n'avez pas les permissions pour créer un fichier ici.")
} else {
fileInfo.err("Une erreur est survenue.")
}
})
})
fileupload.addEventListener("click", () => {
fileInfo.clear()
View.destroyPopup(`<i class="fa-solid fa-file-arrow-up"></i> Nouveau fichier`)
View.createPopup({
title: `<i class='fa-solid fa-file-arrow-up'></i> Upload`,
content: `
<input type='file' class='field' id='${View.getViewTitle()}_fileuploadInput'>
<div id='${View.getViewTitle()}_fileuploadInfo'></div>
<button class='btn green' id='${View.getViewTitle()}_fileuploadBtn'><span><i class='fa fa-upload'></i> Upload</span></button>
`})
const fileuploadInput = getID(View.getViewTitle() + "_fileuploadInput")
const fileuploadBtn = getID(View.getViewTitle() + "_fileuploadBtn")
const fileuploadInfo = new TextResponse(View.getViewTitle() + "_fileuploadInfo")
fileuploadBtn.addEventListener("click", () => {
fileuploadInfo.clear()
if(fileuploadInput.files.length < 1) {
fileuploadInfo.err("Aucun fichier sélectionné.")
return
}
const file = fileuploadInput.files[0]
console.log(file)
const reqFiles = post("FX_UPLOAD", {name: file.name ,root: rootInput.value, file: file})
reqFiles.then((result) => {
if(result == "OK") {
View.destroyPopup(`<i class="fa-solid fa-file-arrow-up"></i> Upload`)
const reqFiles = post("FX_GET", rootInput.value)
reqFiles.then((result) => {
loadFiles(result)
})
}
if(result == "EXIST") {
fileuploadInfo.err("Le fichier existe déjà.")
}
if(result == "NOT_PERMITTED") {
fileuploadInfo.err("Vous n'avez pas les permissions pour uploader un fichier ici.")
}
if(result == "NOT_FILE") {
fileuploadInfo.err("Le fichier n'est pas valide.")
}
if(result == "NOT_EXIST") {
fileuploadInfo.err("Le fichier n'existe pas.")
}
if(result == "TOO_BIG") {
fileuploadInfo.err("Le fichier est trop volumineux.")
}
if(result == "ERROR") {
fileuploadInfo.err("Une erreur est survenue.")
}
})
})
})
})
rootInput.addEventListener("change", () => {
const reqFiles = post("FX_GET", rootInput.value)
reqFiles.then((result) => {
loadFiles(result)
})
})
function loadFiles(files) {
rootInput.value = files.root
var fileElements = new Array()
if(files.content == "NOT_PERMITTED") {
fileElements.unshift(`<div id='fx-parent' class='fx-element'><p><i class="fa-solid fa-rotate-left"></i> Revenir au dossier parent </p></div>`)
fileElements.push("<p class='yellow' style='text-align: center;'><i class='fa fa-warning'></i> Vous n'avez pas les permissions pour accéder à ce dossier.</p>")
} else if(files.content == "NOT_EXIST") {
fileElements.unshift(`<div id='fx-parent' class='fx-element'><p><i class="fa-solid fa-rotate-left"></i> Revenir au dossier parent </p></div>`)
explorer.innerHTML = "<p class='yellow' style='text-align: center;'><i class='fa fa-warning'></i> Ce dossier n'existe pas.</p>"
} else {
for(const file of files.content) {
if(file.directory) {
file.size = "Dossier"
} else {
console.log('------------')
console.log(file.size)
console.log(bytesToSize(file.size))
console.log('------------')
file.size = bytesToSize(file.size)
}
fileElements.push(`<div id='${file.id}' class='fx-element'>
<div>
${getIcon(file)}
<p>${file.name}</p>
</div>
<p>Taille : ${file.size}</p>
<p>Date de modification : ${getFormattedDate(file.lastedition)}</p>
</div>`)
}
// Sort the files by directory and then by name
fileElements.sort((a, b) => {
if(a.includes("Dossier") && !b.includes("Dossier")) {
return -1
} else if(!a.includes("Dossier") && b.includes("Dossier")) {
return 1
} else {
return 0
}
})
fileElements.unshift(`<div id='fx-parent' class='fx-element'><p><i class="fa-solid fa-rotate-left"></i> Revenir au dossier parent </p></div>`)
}
explorer.innerHTML = fileElements.join("")
const parent = document.getElementById("fx-parent")
parent.addEventListener("click", () => {
const reqFiles = post("FX_GET", files.parent)
reqFiles.then((result) => {
loadFiles(result)
})
})
if(files.content != "NOT_PERMITTED" && files.content != "NOT_EXIST") {
// If it's a directory, get the file directory and make the request to get the files in it and loadIt
for(const file of files.content) {
const element = document.getElementById(file.id)
if(file.directory) {
element.addEventListener("click", () => {
const reqFiles = post("FX_GET", file.fileDirectory)
reqFiles.then((result) => {
loadFiles(result)
})
})
} else {
element.addEventListener("dblclick", () => {
editFile()
})
}
getID(file.id).addEventListener("contextmenu", () => {
const dropMenu = new DroppableMenu()
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.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")
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)
reqFiles.then((result) => {
if(result == "OK") {
const reqFiles = post("FX_GET", files.root)
reqFiles.then((result) => {
loadFiles(result)
})
} else if(result == "NOT_PERMITTED") {
const reqFiles = post("FX_GET", files.root)
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 supprimer ce fichier.</p>`
})
} else {
const reqFiles = post("FX_GET", files.root)
reqFiles.then((result) => {
loadFiles(result)
})
View.createPopup({
title: `<i class='fa fa-warning'></i> Erreur`,
content: `<p class='yellow'>Une erreur est survenue.</p>`
})
}
})
})
dropMenu.get("rename").addEventListener("click", () => {
dropMenu.hide()
View.createPopup({
title: `<i class='fa fa-file-signature'></i> Renommer`,
content: `
<input type='text' class='field' id='${View.getViewTitle()}_rename'>
<div id='${View.getViewTitle()}_renameInfo'></div>
<button class='btn green' id='${View.getViewTitle()}_renameBtn'><span><i class='fa fa-pen'></i> Renommer</span></button>
`})
const rename = getID(View.getViewTitle() + "_rename")
const renameBtn = getID(View.getViewTitle() + "_renameBtn")
const renameInfo = new TextResponse(View.getViewTitle() + "_renameInfo")
renameInfo.setSize("13px")
rename.value = file.name
rename.focus()
rename.select()
renameBtn.addEventListener("click", () => {
renameInfo.clear()
if(rename.value.length < 1) {
renameInfo.err("Le nom du fichier / dossier est trop court.")
return
}
const regex = new RegExp(/^[a-zA-Z0-9-_.]+$/)
if(!regex.test(rename.value) | rename.value.replace(/\s/g, '').length < 1) {
renameInfo.err("Le nom du fichier / dossier est invalide.")
return
}
const reqFiles = post("FX_RENAME", {root: files.root, oldName: file.name, newName: rename.value})
reqFiles.then((result) => {
if(result == "OK") {
View.destroyPopup(`<i class="fa fa-file-signature"></i> Renommer`)
const reqFiles = post("FX_GET", files.root)
reqFiles.then((result) => {
loadFiles(result)
})
} else if(result == "EXIST") {
renameInfo.err("Le fichier / dossier existe déjà.")
} else if(result == "NOT_PERMITTED") {
renameInfo.err("Vous n'avez pas les permissions pour renommer ce fichier / dossier.")
} else {
renameInfo.err("Une erreur est survenue.")
}
})
})
})
if(!file.directory) {
dropMenu.get("share").addEventListener("click", () => {
dropMenu.hide()
const reqFiles = post("FX_SHARE", {root: files.root, name: file.name})
reqFiles.then((result) => {
if(result == "NOT_PERMITTED") {
View.createPopup({
title: `<i class='fa fa-warning'></i> Erreur`,
content: `<p class='yellow'>Vous n'avez pas les permissions pour partager ce fichier.</p>`
})
} else {
View.createPopup({
title: `<i class='fa fa-share'></i> Partager`,
content: `
<input style='width: 300px' type='text' class='field' id='${View.getViewTitle()}_sharelink'>
<div id='${View.getViewTitle()}_shareInfo'></div>
<button class='btn green' id='${View.getViewTitle()}_shareBtn'><span><i class='fa fa-share'></i> Copier</span></button>
`})
const sharelink = getID(View.getViewTitle() + "_sharelink")
const shareBtn = getID(View.getViewTitle() + "_shareBtn")
const shareInfo = new TextResponse(View.getViewTitle() + "_shareInfo")
shareInfo.setSize("13px")
sharelink.value = result
sharelink.focus()
sharelink.select()
shareBtn.addEventListener("click", () => {
sharelink.focus()
sharelink.select()
window.navigator.clipboard.writeText(sharelink.value)
shareInfo.ok("Copié !")
})
}
})
})
// Edit file with an ViewWindow with 2 options close & save and the name of the window is like File - Editor
dropMenu.get("edit").addEventListener("click", () => {
dropMenu.hide()
editFile()
})
dropMenu.get("download").addEventListener("click", () => {
dropMenu.hide()
const reqFiles = post("FX_GETFILE", file.fileDirectory)
reqFiles.then((result) => {
if(result == "NOT_PERMITTED") {
View.createPopup({
title: `<i class='fa fa-warning'></i> Erreur`,
content: `<p class='yellow'>Vous n'avez pas les permissions pour télécharger ce fichier.</p>`
})
} else {
// Make Download using result
const element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
element.setAttribute('download', file.name);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
})
})
}
})
function editFile() {
const reqFiles = post("FX_GETFILE", file.fileDirectory)
//Create a popup for the loading
View.createPopup({
title: `<i class='fa-solid fa-file-pen'></i> Editeur`,
content: `<p><i class='fa-solid fa-rotate fa-spin'></i> Chargement en cours ...</p>`
})
reqFiles.then((result) => {
View.destroyPopup(`<i class='fa-solid fa-file-pen'></i> Editeur}`)
if(result == "NOT_PERMITTED") {
View.createPopup({
title: `<i class='fa fa-warning'></i> Erreur`,
content: `<p class='yellow'>Vous n'avez pas les permissions pour éditer ce fichier.</p>`
})
} else {
const editor = new ViewWindow({
title: `<i class="fa-solid fa-file-pen"></i> Editeur - ${file.fileDirectory}`,
width: "1000px",
height: "650px"
})
if(file.type == "image/png" | file.type == "image/jpeg") {
editor.setContent(`
<div style='width: 100%; text-align: center;'>
<img style='width: 500px;' src='${result}'/>
</div>`)
} else {
editor.setContent(`
<div class='fx-bar'>
<span id='${editor.getViewTitle()}_save' class='btn-cover'><i class='fa fa-save'></i></span>
</div>
<textarea id='${editor.getViewTitle()}_editorContent' class='fx-editor-content'>${result}</textarea>
`)
const editorSave = getID(editor.getViewTitle() + "_save")
const editorContent = getID(editor.getViewTitle() + "_editorContent")
// Sauvegarder le fichier en l'envoyant
editorSave.addEventListener("click", () => {
const reqFiles = post("FX_SAVEFILE", {root: files.root, name: file.name, content: editorContent.value})
reqFiles.then((result) => {
if(result == "OK") {
editor.destroy()
const reqFiles = post("FX_GET", files.root)
reqFiles.then((result) => {
loadFiles(result)
})
} else if(result == "NOT_PERMITTED") {
editor.createPopup({
title: `<i class='fa fa-warning'></i> Erreur`,
content: `<p class='yellow'>Vous n'avez pas les permissions pour éditer ce fichier.</p>`
})
} else {
editor.createPopup({
title: `<i class='fa fa-warning'></i> Erreur`,
content: `<p class='yellow'>Une erreur est survenue.</p>`
})
}
})
})
//forbid textarea resize
editorContent.style.resize = "none"
}
}
})
}
}
}
}
})
function getIcon(file) {
if(file.type == "application/json") {
return '<i style="color:rgb(179, 141, 4);" class="fa-sharp fa-code"></i>'
}
if(file.type == "application/msword" | file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
return '<i style="color:rgb(47, 94, 247);" class="fa-solid fa-file-word"></i>'
}
if(file.type == "application/vnd.ms-powerpoint") {
return '<i style="color:rgb(255, 112, 51);" class="fa-solid fa-file-powerpoint"></i>'
}
if(file.type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | file.type == "application/vnd.ms-excel") {
return '<i style="color:rgb(51, 255, 61);" class="fa-solid fa-file-excel"></i>'
}
if(file.type == "application/java-archive") {
return '<i style="color:rgb(255, 202, 150);" class="fa-brands fa-java"></i>'
}
if(file.type == "application/x-sh") {
return '<i style="color:rgb(171, 226, 255);" class="fa-solid fa-file-code"></i>'
}
if(file.type == "application/x-msdos-program" | file.type == "application/x-msdownload") {
return'<i style="color:rgb(53, 191, 255);" class="fa-brands fa-windows"></i>'
}
if(file.type == "application/javascript") {
return '<i style="color:rgb(179, 141, 4);" class="fa-brands fa-js"></i>'
}
if(file.type == "image/png" | file.type == "image/jpeg") {
return '<i style="color:rgb(189, 104, 189);" class="fa-solid fa-file-image"></i>'
}
if(file.type == "text/html") {
return '<i style="color:tomato;" class="fa-brands fa-html5"></i>'
}
if(file.type == "text/css") {
return '<i style="color:rgb(66, 135, 245);" class="fa-brands fa-css3-alt"></i>'
}
if(file.type == "application/zip") {
return '<i style="color:rgb(255, 139, 38);" class="fa-solid fa-file-zipper"></i>'
}
if(file.type == "audio/mpeg") {
return '<i style="color:rgb(38, 222, 255);" class="fa-solid fa-file-audio"></i>'
}
if(file.type == "application/pdf") {
return '<i style="color:rgb(255, 71, 51);" class="fa-solid fa-file-pdf"></i>'
}
if(file.directory) {
return '<i style="color:yellow;" class="fa fa-folder"></i>'
} else {
return '<i class="fa fa-file"></i>'
}
}
function bytesToSize(bytes) {
var sizes = ['o', 'Ko', 'Mo', 'Go', 'To'];
if (bytes == 0) return '0 o';
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
}

View File

@ -0,0 +1,68 @@
// User Request
loadUserInfo()
function loadUserInfo() {
const infoUsername = getID("infoUsername")
const infoUserimage = getID("infoUserimage")
const infoDisplayname = getID("infoDisplayname")
const views = getID("views")
const panelBox = getID("panel-box")
const REQ_user = get("USERINFO")
REQ_user.then((ANS_user) => {
console.log(ANS_user)
infoUserimage.src = ANS_user.picture
infoUsername.innerHTML = ANS_user.username
infoDisplayname.innerHTML = ANS_user.display_name
const permissions = ANS_user.permission
const AvailableViews = new Array()
//
permissions.forEach((permValue) => {
AllComponents.forEach((component) => {
if(component.permission == permValue) {
component.inject(AvailableViews)
}
})
})
if(AvailableViews.join("") == "") {
AvailableViews.push("<p style='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")
panelBox.style.justifyContent = "unset"
console.log(panelBox.style)
} else {
views.classList.add("views-box")
}
views.innerHTML = AvailableViews.join("")
// BindView
AllComponents.forEach((component) => {
component.bindView()
})
})
}

62
public/javascripts/io.js Normal file
View File

@ -0,0 +1,62 @@
// Socket IO - Communication
var socketLink = null
fetch('/internal/socketlink', {
method: "GET"
}).then(link => {socketLink = link})
const socket = io(socketLink);
socket.on("connect", () => {
console.log("Connecté au serveur par le Socket avec l'ID : " + socket.id)
});
socket.on("disconnect", (log) => {
window.location.href = "/"
})
function get(request) {
return new Promise((resolve, reject) => {
socket.emit("GET/" + request)
console.log("Envoi de la requête GET : " + request)
socket.once("ANSWER/GET/" + request, (answer) => {
console.log("Réponse pour la requête : " + request)
resolve(answer)
})
})
}
function post(request, value) {
return new Promise((resolve, reject) => {
socket.emit("POST/" + request, value)
console.log("Envoi de la requête POST : " + request)
socket.once("ANSWER/POST/" + request, (answer) => {
console.log("Réponse pour la requête : " + request)
resolve(answer)
})
})
}
function getSocket() {
return socket
}

299
public/javascripts/link.js Normal file
View File

@ -0,0 +1,299 @@
links.createWindow(() => {
const View = new ViewWindow({
title: `<i class="fa-solid fa-link"></i> Générateur de liens`,
width: "600px",
height: "600px",
})
View.setContent(`
<div class='ln-bar'>
<button id='${View.getViewTitle()}_add' class='btn blue'><span><i class='fa fa-add'></i> Ajouter un lien</span></button>
</div>
<div class='ln-links' id='${View.getViewTitle()}_links'>
<div style='font-size: 24px; margin-top: 225px;' class='t-center'>
<p><i class="fa-solid fa-rotate fa-spin"></i> Chargement en cours ...</p>
</div>
</div>
`)
const addBtn = getID(`${View.getViewTitle()}_add`)
const linksDiv = getID(`${View.getViewTitle()}_links`)
var links = new Array()
addBtn.addEventListener("click", () => {
View.createPopup({
title: `<i class='fa fa-add'></i> Ajouter un lien`,
content: `
<div class='ln-create'>
<input id='${View.getViewTitle()}_title' class='field' type='text' placeholder='Titre du lien'>
<input id='${View.getViewTitle()}_url' class='field' type='text' placeholder='URL du lien'>
<div class='ln-abstract-div'>
<p class='ln-abstract-label'>Abstraire le lien :</p>
<input id='${View.getViewTitle()}_ablink' type='checkbox'>
</div>
<span id='${View.getViewTitle()}_newlink_span'></span>
<p id='${View.getViewTitle()}_info'></p>
<button id='${View.getViewTitle()}_confirm' class='btn blue'><span><i class='fa fa-add'></i> Ajouter</span></button>
</div>
`
})
const titleInput = getID(`${View.getViewTitle()}_title`)
const urlInput = getID(`${View.getViewTitle()}_url`)
const addBtn = getID(`${View.getViewTitle()}_confirm`)
const info = new TextResponse(`${View.getViewTitle()}_info`)
const ablink = getID(`${View.getViewTitle()}_ablink`)
const newlinkSpan = getID(`${View.getViewTitle()}_newlink_span`)
ablink.checked = true
ablink.addEventListener("click", () => {
if(!ablink.checked) {
newlinkSpan.innerHTML = `
<input id='${View.getViewTitle()}_newlink' class='field' type='text' placeholder='URL du nouveau lien'>
`
} else {
newlinkSpan.innerHTML = ""
}
})
addBtn.addEventListener("click", () => {
info.clear()
console.log(ablink.checked)
if(!titleInput.value) {
info.err("Un titre est nécéssaire")
} else if(!urlInput.value) {
info.err("Une URL est nécéssaire")
} else {
var newlink = getID(`${View.getViewTitle()}_newlink`)
if(!newlink) {
newlink = { value: "" }
} else {
if(!newlink.value) {
info.err("Un nouveau lien est nécéssaire")
return false
}
if(!newlink.value.match(/^[a-zA-Z0-9-_]+$/)) {
info.err("L'URL n'est pas valide")
return false
}
}
const request = post(`LINKS_ADD`, {
title: titleInput.value,
url: urlInput.value,
abstractLink: ablink.checked,
dest: newlink.value
})
request.then((answer) => {
if(answer.answer == "OK") {
info.info("Le lien a bien été ajouté")
View.destroyPopup()
getLinks()
} else if(answer.answer == "ALREADY_EXISTS") {
info.err("Ce lien existe déjà")
} else {
info.err("Impossible d'ajouter le lien")
}
})
}
})
})
getLinks()
function getLinks() {
linksDiv.innerHTML = ""
links = new Array()
const request = post(`LINKS_GET_ALL`)
request.then((answer) => {
if(answer.answer == "OK") {
if(answer.links.length == 0) {
linksDiv.innerHTML = `<p class='lightred' style='text-align: center;'>Aucun lien disponible</p>`
}
answer.links.forEach((link) => {
links.push(link)
})
links.forEach((link) => {
linksDiv.innerHTML += `
<div class='ln-link'>
<p class='ln-link-title'>${link.title}</p>
<div>
<p class='ln-link-url'><i class="fa-solid fa-bookmark"></i> Lien original : <a href='${link.url}'>${link.url}</a></p>
<p class='ln-link-url'><i class="fa-solid fa-paperclip"></i> Lien modifié : <a href='https://neutral.raphix.fr/link/${link.dest}'> https://neutral.raphix.fr/link/${link.dest}</a></p>
</div>
<div class='ln-link-actions'>
<button id='${link.id}_edit' class='btn blue'><span><i class='fa fa-edit'></i> Modifier</span></button>
<button id='${link.id}_remove' class='btn red'><span><i class='fa fa-trash'></i> Supprimer</span></button>
</div>
</div>
`
})
links.forEach((link) => {
const editBtn = getID(`${link.id}_edit`)
editBtn.addEventListener("click", () => {
View.createPopup({
title: `<i class='fa fa-edit'></i> Modifier un lien`,
content: `
<div class='ln-create'>
<input id='${View.getViewTitle()}_edittitle' class='field' type='text' placeholder='Titre du lien' value='${link.title}'>
<input id='${View.getViewTitle()}_editurl' class='field' type='text' placeholder='URL du lien' value='${link.url}'>
<p id='${View.getViewTitle()}_editinfo'></p>
<button id='${View.getViewTitle()}_editconfirm' class='btn blue'><span><i class='fa fa-edit'></i> Modifier</span></button>
</div>
`
})
const titleInput = getID(`${View.getViewTitle()}_edittitle`)
const urlInput = getID(`${View.getViewTitle()}_editurl`)
const editBtnConfirm = getID(`${View.getViewTitle()}_editconfirm`)
const info = new TextResponse(`${View.getViewTitle()}_editinfo`)
titleInput.value = link.title
urlInput.value = link.url
editBtnConfirm.addEventListener("click", () => {
if(!titleInput.value) {
info.err("Un titre est nécéssaire")
} else if(!urlInput.value) {
info.err("Une URL est nécéssaire")
} else {
const request = post(`LINKS_EDIT`, {
id: link.id,
title: titleInput.value,
url: urlInput.value
})
request.then((answer) => {
if(answer == "OK") {
info.info("Le lien a bien été modifié")
View.destroyPopup()
getLinks()
} else {
info.err("Impossible de modifier le lien")
}
})
}
})
})
const delBtn = getID(`${link.id}_remove`)
delBtn.addEventListener("click", () => {
const request = post(`LINKS_DELETE`, link.id)
request.then((answer) => {
if(answer != "OK") {
View.createPopup({
title: `<i class='fa fa-warning'></i> Erreur`,
content: `
<p>Impossible de supprimer le lien</p>
`
})
} else {
getLinks()
}
})
})
})
} else {
info.err("Impossible de récupérer les liens")
}
})
}
})

View File

@ -0,0 +1,67 @@
const username = getID("username")
const password = getID("password")
const submit = getID("submit")
const loginInfo = new TextResponse("login-info")
loginInfo.clear()
submit.addEventListener("click", () => {
if(!username.value) {
loginInfo.err("Un nom d'utilisateur est nécéssaire pour se connecter !")
} else if(!password.value) {
loginInfo.err("Un mot de passe est nécéssaire pour se connecter !")
} else {
login()
}
})
password.addEventListener("keyup", (event) => {
if (event.key === "Enter") {
login()
}
});
function login() {
loginInfo.clear()
fetch('/login', {
method: 'POST',
redirect: 'follow',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username.value,
password: password.value
})
})
.then(response => redirect(response))
async function redirect(response) {
response = await response.text()
if(response == "AUTH_FAILED") {
loginInfo.err("Le nom d'utilisateur et le mot de passe sont incorrects.")
} else if(response == "AUTH_SUCCESS") {
window.location.href = "/"
}
}
}

View File

@ -0,0 +1,144 @@
metrics.createWindow(() => {
const View = new ViewWindow({
title: `<i class="fa-solid fa-square-poll-vertical"></i> Web Metrik`,
width: "600px",
height: "600px",
})
loadMetrics()
function loadMetrics() {
View.setContent(`
<div class="metrics">
<div class='mt-bar'>
<p> <strong>WebMetrik</strong> : <span id='${View.getViewTitle()}_number'><i>Calcul en cours</i></span></p>
<div>
<button id='${View.getViewTitle()}_refresh' class='btn green'><span><i class='fa fa-rotate-left'></i> Rafraîchir</span></button>
<button id='${View.getViewTitle()}_add' class='btn blue'><span><i class='fa fa-add'></i> Ajouter un WebMetrik</span></button>
</div>
</div>
<div class='mt-metrics' id='${View.getViewTitle()}_metrics'>
<div style='font-size: 24px; margin-top: 180px;' class='t-center'>
<p><i class="fa-solid fa-rotate fa-spin"></i> Chargement en cours ...</p>
</div>
</div>`)
const metricsList = new Array()
const metricsComponent = new Array()
const metricsReq = get("MT_ALL")
const metricsDiv = document.getElementById(`${View.getViewTitle()}_metrics`)
const metricsNumber = document.getElementById(`${View.getViewTitle()}_number`)
const addButton = document.getElementById(`${View.getViewTitle()}_add`)
const refreshButton = document.getElementById(`${View.getViewTitle()}_refresh`)
const metricsView = document.getElementById(`${View.getViewTitle()}_metrics`)
refreshButton.addEventListener("click", () => {
loadMetrics()
})
metricsReq.then((ANS_metrics) => {
console.log(ANS_metrics)
if(ANS_metrics != "UNAVAILABLE") {
metricsList.length = 0
if(ANS_metrics.length == 0) {
metricsList.push(`<div style='font-size: 24px; margin-top: 180px;' class='yellow t-center'>
<p><i class="fa-solid fa-warning"></i> Aucune WebMetrik n'a été ajoutée</p>
</div>`)
}
ANS_metrics.forEach((metric) => {
const metricComponent = new Metric({
properties: metric,
Component: metrics,
View: View
})
metricsList.push(metricComponent.generateHTML())
metricsComponent.push(metricComponent)
})
metricsDiv.innerHTML = metricsList.join("")
metricsNumber.innerHTML = ANS_metrics.length
for(const metric of metricsComponent) {
metric.loadScript()
}
} else {
metricsDiv.innerHTML = `<div style='font-size: 24px; margin-top: 180px;' class='t-center'>
<p><i class="fa-solid fa-warning"></i> Une erreur est survenue lors du chargement des WebMetrik</p>
</div>`
}
})
// Generate a pop to add metrics with 3 settings, the adress, the port, the name of the metrics and the key to authentificate
addButton.addEventListener("click", () => {
View.createPopup({
title: "Ajouter un WebMetrik",
content: `
<div class='mt-add'>
<p>Adresse</p>
<input class='field' type="text" id="mt-add-address" placeholder="Adresse">
<p>Port</p>
<input class='field' type="number" id="mt-add-port" placeholder="Port">
<p>Nom</p>
<input class='field' type="text" id="mt-add-name" placeholder="Nom">
<p>Clé d'authentification</p>
<input class='field' type="password" id="mt-add-key" placeholder="Clé d'authentification">
<span id='mtaddinfo'></span>
<button id='mt-add-btn' class='btn blue'><span><i class='fa fa-add'></i> Ajouter</span></button>
</div>
`
})
const addBtn = document.getElementById("mt-add-btn")
const address = document.getElementById("mt-add-address")
const port = document.getElementById("mt-add-port")
const name = document.getElementById("mt-add-name")
const key = document.getElementById("mt-add-key")
const infoMTAdd = new TextResponse("mtaddinfo")
addBtn.addEventListener("click", () => {
// Check if all fields are filled
if(address.value == "" || port.value == "" || name.value == "" || key.value == "") {
infoMTAdd.err("Veuillez remplir tous les champs")
return
}
post("MT_ADD", {
address: address.value,
port: port.value,
name: name.value,
key: key.value
}).then((res) => {
if(res != "ERROR") {
View.destroyPopup()
loadMetrics()
} else {
View.createPopup({
title: "Erreur",
content: `<p class='yellow'><i class='fa fa-warning'></i> Une erreur est survenue lors de l'ajout de la WebMetrik</p>`
})
}
})
})
})
}
})
/*
<div style='font-size: 24px; margin-top: 180px;' class='t-center'>
<p><i class="fa-solid fa-rotate fa-spin"></i> Chargement en cours ...</p>
</div>*/

View File

@ -0,0 +1,59 @@
// 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 Metrik",
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: "Générateur 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,119 @@
const menulogo = getID("menu-logo")
const usersettingsBtn = getID("user-settings-button")
menu.style.display = "none"
menulogo.addEventListener("click", () => {
const menu = getID("menu")
if(menu.style.display == "block") {
menu.style.display = "none"
} else {
menu.style.display = "block"
}
})
usersettingsBtn.addEventListener("click", () => {
const View = new ViewWindow({
title: `<i class="fa fa-user"></i> Mon Compte`,
width: "600px",
height: "650px"
})
View.setContent(`
<div class="user-settings">
<p>Mes informations</p>
<div class="us-settings">
<img class='us-settings-image' id="us-settings-image" src="">
<div class="us-settings-info">
<p>Nom d'utilisateur</p>
<input class="field" type="text" id="us-settings-username" placeholder="Nom d'utilisateur">
<p>Nom d'affichage</p>
<input class="field" type="text" id="us-settings-displayname" placeholder="Nom d'affichage">
<p>Mot de passe</p>
<input class="field" type="password" id="us-settings-password" placeholder="Mot de passe">
<p>Photo de profil</p>
<input type="file" id="us-settings-picture" accept="image/png, image/jpeg">
</div>
<br>
<span id='us-settings-return-info'></span>
<button id="us-settings-save" class="btn green"><span> Sauvegarder</span></button>
</div>
<p>Mes permissions</p>
<div id="us-settings-permissions">
</div>
</div>
`)
const usSettingsImage = getID("us-settings-image")
const usSettingsUsername = getID("us-settings-username")
const usSettingsDisplayname = getID("us-settings-displayname")
const usSettingsPassword = getID("us-settings-password")
const usSettingsPermissions = getID("us-settings-permissions")
const usSettingsSave = getID("us-settings-save")
const usSettingsPicture = getID("us-settings-picture")
const returnInfo = new TextResponse("us-settings-return-info")
const REQ_user = get("USERINFO")
var actualUsername = ""
REQ_user.then((ANS_user) => {
usSettingsImage.src = ANS_user.picture
usSettingsUsername.value = ANS_user.username
usSettingsDisplayname.value = ANS_user.display_name
actualUsername = ANS_user.username
const permissions = ANS_user.permission
const permValid = new Array()
permissions.forEach((permValue) => {
permValid.push("<p>" + permValue + "</p>")
})
usSettingsPermissions.innerHTML = permValid.join("")
})
usSettingsSave.addEventListener("click", () => {
const request = post(`US_EDIT_PERSONNAL`, {username: actualUsername, newusername: usSettingsUsername.value, display_name: usSettingsDisplayname.value, password: usSettingsPassword.value, picture: usSettingsPicture.files[0]})
request.then((answer) => {
if(answer == "ALREADY_EXIST") {
returnInfo.err("L'utilisateur existe déjà !")
} else if(answer == "USERNAME_MISSING") {
returnInfo.err("Le nom d'utilisateur est manquant !")
} else if(answer == "DISPLAY_NAME_MISSING") {
returnInfo.err("Le nom d'affichage est manquant !")
} else if(answer == "PASSWORD_MISSING") {
returnInfo.err("Le mot de passe est manquant !")
} else {
View.destroy()
loadUserInfo()
}
})
})
})
document.addEventListener("click", (e) => {
if(e.target != menulogo) {
menu.style.display = "none"
}
})

View File

@ -0,0 +1,66 @@
pipelines.createWindow(() => {
const View = new ViewWindow({
title: `<i class="fa fa-code-merge"></i> Gestion des pipelines`,
width: "900px",
height: "600px"
})
const pipelinesList = new Array()
View.setContent(`<div style='font-size: 24px; margin-top: 225px;' class='t-center'>
<p><i class="fa-solid fa-rotate fa-spin"></i> Chargement en cours ...</p>
</div>`)
var AllPipelines = new Array()
function getPipelines() {
const pipes = get("PL_GET_ALL")
pipes.then((ANS_pipes) => {
AllPipelines.length = 0
pipelinesList.length = 0
if(ANS_pipes != "UNAVAILABLE") {
ANS_pipes.jobs.forEach((pipe) => {
console.log(pipe)
const pipeline = new Pipeline({
pipeline: pipe,
View: View
})
pipelinesList.push(pipeline.generateHTML())
AllPipelines.push(pipeline)
})
View.setContent(`
<div class="pipelines">
<button id='reload_Btn_pipeline' class='btn blue'><span><i class='fa fa-rotate-left'></i> Recharger les pipelines</span></button>
${pipelinesList.join("")}
</div>`)
for(const pipeline of AllPipelines) {
pipeline.loadScript()
}
getID("reload_Btn_pipeline").addEventListener("click", () => {
View.setContent(`<div style='font-size: 24px; margin-top: 225px;' class='t-center'>
<p><i class="fa-solid fa-rotate fa-spin"></i> Chargement en cours ...</p>
</div>`)
getPipelines()
})
} else {
View.setContent(`<div style='font-size: 24px; margin-top: 225px;' class='t-center lightred'>
<p><i class="fa-solid fa-warning"></i> Une erreur est survenue lors du chargement des pipelines</p>
</div>`)
}
})
}
getPipelines()
})

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

@ -0,0 +1,88 @@
services.createWindow(async () => {
/**
* CODE OF SERVICE.JS
*/
const allServices = new Array()
const View = new ViewWindow({
title: '<i class="fa fa-layer-group"></i> Gestion des services',
width: "700px",
height: "600px"
})
const subsonicsService = new Service({
name: "Subsonics",
description: "Bot de streaming musical sur Discord",
icon: "/images/services/subsonics.png",
url: "https://subsonics.raphix.fr" ,
canAccess: true,
View: View
})
const giteaService = new Service({
name: "Gitea",
description: "Gestionnaire de dépôt Git",
icon: "/images/services/gitea.svg",
url: "https://git.raphix.fr" ,
canAccess: true,
View: View
})
const jenkinsService = new Service({
name: "Jenkins",
description: "Gestionnaire de pipeline",
icon: "/images/services/jenkins.svg",
url: "https://jenkins.raphix.fr" ,
canAccess: true,
View: View
})
const raphixwebsite = new Service({
name: "Raphix.fr",
description: "Site web de Raphix",
icon: "/images/services/raphix.png",
url: "https://raphix.fr",
canAccess: true,
View: View
})
const cvraphix = new Service({
name: "CV Raphix",
description: "Curriculum Vitae de Raphix",
icon: "/images/services/cv.png",
url: "https://cv.raphix.fr",
canAccess: true,
View: View
})
const lavalink = new Service({
name: "Lavalink",
description: "Serveur Lavalink pour Subsonics",
icon: "/images/services/lavalink.svg",
url: "http://omega.raphix.fr:2333",
canAccess: false,
View: View
})
allServices.push(subsonicsService.generateHTML())
allServices.push(lavalink.generateHTML())
allServices.push(giteaService.generateHTML())
allServices.push(jenkinsService.generateHTML())
allServices.push(raphixwebsite.generateHTML())
allServices.push(cvraphix.generateHTML())
View.setContent(`<div class='sv-list'>${allServices.join("")}</div>`)
await subsonicsService.loadScript()
await giteaService.loadScript()
await jenkinsService.loadScript()
await raphixwebsite.loadScript()
await cvraphix.loadScript()
await lavalink.loadScript()
})

View File

@ -0,0 +1,123 @@
settings.createWindow(async () => {
const View = new ViewWindow({
title: `<i class="fa fa-cog"></i> Paramètres`,
width: "500px",
height: "620px"
})
View.setContent(`
<div style='overflow-y: auto; height: 550px;'>
<div class="category">
<p>Options d'alimentation</p>
<div class='st-act'>
<button id="st-restart" class="btn yellow"><span>Redémarrer</span></button>
<button id="st-stop" class="btn red"><span> Arrêter</span></button>
</div>
</div>
<div class="category">
<p>Configuration des Tokens</p>
<p class='user-line-displayname'>Jenkins</p>
<input class='field' type="text" id="jenkins_token" placeholder="Token Jenkins">
<p class='user-line-displayname'> Omega </p>
<input class='field' type="text" id="omega_token" placeholder="Token Omega">
<button id="st-save" class="btn green"><span> Sauvegarder</span></button>
</div>
<div class="category">
<p>Accès aux logs</p>
<select id="all-logs" class="field">
<option>Chargment en cours ...</option>
</select>
<button id="read-logs" class="btn green"><span>Lire</span></button>
</div>
</div>
`)
const allLogs = document.getElementById("all-logs")
const readLogs = document.getElementById("read-logs")
get("SERVER_GET_LOGS").then((logs) => {
logs.reverse()
allLogs.innerHTML = logs.map((log) => {
return `<option value="${log}">${log}</option>`
}).join("")
})
readLogs.addEventListener("click", () => {
const log = allLogs.value
post("SERVER_READ_LOG", log).then((logContent) => {
const logView = new ViewWindow({
title: `<i class="fa fa-file"></i> ${log}`,
width: "1000px",
height: "520px"
})
logContent = logContent.replaceAll("[INFO]", "<span class='blue'>[INFO]</span>")
logContent = logContent.replaceAll("[WARN]", "<span class='yellow'>[WARN]</span>")
logContent = logContent.replaceAll("[ERROR]", "<span class='lightred'>[ERROR]</span>")
logContent = logContent.replaceAll("[STEP]", "<span class='green'>[STEP]</span>")
logContent = logContent.replaceAll("[Users]", "<span style='color:#c7b8ff; '>[Users]</span>")
logContent = logContent.replaceAll("[Web]", "<span style='color:#fffd8a; '>[Web]</span>")
logContent = logContent.replaceAll("[Serveur]", "<span style='color:#ff7a5c; '>[Serveur]</span>")
logContent = logContent.replaceAll("[Authentification]", "<span style='color:#d9e6ff; '>[Authentification]</span>")
// Get every line of logs and add a set the style in blue when it's the date in []
const logLines = logContent.split("\n")
const newLogLines = new Array()
logLines.forEach((line) => {
if(line.startsWith("[") && line.includes("]")) {
const date = line.split("]")[0] + "]"
const content = line.replace(date, "")
newLogLines.push(`<span style='color: #a6c2f7;'>${date}</span>${content}\n`)
} else {
newLogLines.push(`${line}\n`)
}
})
logView.setContent(`
<div style='overflow-y: auto; height: 450px;'>
<pre style='font-size: 12px'>${newLogLines.join("")}</pre>
</div>
`)
})
})
const restartButton = document.getElementById("st-restart")
const stopButton = document.getElementById("st-stop")
const saveButton = document.getElementById("st-save")
const jenkinsToken = document.getElementById("jenkins_token")
const omegaToken = document.getElementById("omega_token")
get("SETTINGS_GET").then((settings) => {
jenkinsToken.value = settings.jenkins_token
omegaToken.value = settings.omega_token
})
restartButton.addEventListener("click", () => {
post("SERVER_RESTART")
})
stopButton.addEventListener("click", () => {
post("SERVER_STOP")
})
saveButton.addEventListener("click", () => {
post("SETTINGS_SAVE", {
jenkins_token: jenkinsToken.value,
omega_token: omegaToken.value
})
get("SETTINGS_GET").then((settings) => {
jenkinsToken.value = settings.jenkins_token
omegaToken.value = settings.omega_token
})
})
})

183
public/javascripts/user.js Normal file
View File

@ -0,0 +1,183 @@
users.createWindow(async () => {
const usersComponent = users
const View = new ViewWindow({
title: `<i class="fa fa-users"></i> Gestion des utilisateurs`,
width: "500px",
height: "700px"
})
loadUsers()
function loadUsers() {
View.setContent(`
<div class="users">
<div class='us-bar'>
<p> <strong>Utilisateurs</strong> : <span id='${View.getViewTitle()}_number'><i>Calcul en cours</i></span></p>
<button id='${View.getViewTitle()}_add' class='btn blue'><span><i class='fa fa-add'></i> Ajouter un utilisateur</span></button>
</div>
<div class='us-users' id='${View.getViewTitle()}_users'>
<div style='font-size: 24px; margin-top: 180px;' class='t-center'>
<p><i class="fa-solid fa-rotate fa-spin"></i> Chargement en cours ...</p>
</div>
</div>
</div>`)
const usersList = new Array()
const users = get("US_ALL")
const usersDiv = document.getElementById(`${View.getViewTitle()}_users`)
const usersNumber = document.getElementById(`${View.getViewTitle()}_number`)
const addButton = document.getElementById(`${View.getViewTitle()}_add`)
addButton.addEventListener("click", () => {
View.createPopup({
title: "Ajouter un utilisateur",
content: `
<div class='us-add'>
<p>Nom d'utilisateur</p>
<input class='field' type="text" id="us-add-username" placeholder="Nom d'utilisateur">
<p>Nom d'affichage</p>
<input class='field' type="text" id="us-add-displayname" placeholder="Nom d'affichage">
<p>Mot de passe</p>
<input class='field' type="password" id="us-add-password" placeholder="Mot de passe">
<p>Permissions</p>
<div class='permissions'>
<div>
<input type="checkbox" id="perm_FILES_EXPLORER">
<label for="perm_FILES_EXPLORER">Fichiers</label>
</div>
<div>
<input type="checkbox" id="perm_SERVICES">
<label for="perm_SERVICES">Services</label>
</div>
<div>
<input type="checkbox" id="perm_LINKS">
<label for="perm_LINKS">Liens</label>
</div>
<div>
<input type="checkbox" id="perm_SERVERS">
<label for="perm_SERVERS">Serveurs</label>
</div>
<div>
<input type="checkbox" id="perm_PIPELINES">
<label for="perm_PIPELINES">Pipelines</label>
</div>
<div>
<input type="checkbox" id="perm_METRICS">
<label for="perm_METRICS">Metrics</label>
</div>
<div>
<input type="checkbox" id="perm_USERS">
<label for="perm_USERS">Utilisateurs</label>
</div>
<div>
<input type="checkbox" id="perm_SETTINGS">
<label for="perm_SETTINGS">Paramètres</label>
</div>
</div>
<p>Photo de profil</p>
<input type="file" id="us-add-picture" accept="image/png, image/jpeg">
<span id='user-addreturn-info'></span>
<button id='us-add-button' class='btn green'><span>Ajouter</span></button>
</div>
`
})
const addCButton = document.getElementById("us-add-button")
const username = document.getElementById("us-add-username")
const displayname = document.getElementById("us-add-displayname")
const password = document.getElementById("us-add-password")
const picture = document.getElementById("us-add-picture")
const permissions = document.getElementsByClassName("permissions")[0].children
const returnInfo = new TextResponse("user-addreturn-info")
addCButton.addEventListener("click", () => {
var permissionsList = new Array()
for(var permission of permissions) {
console.log(permission.children[0].checked)
if(permission.children[0].checked) {
permissionsList.push(permission.children[0].id.replace("perm_", ""))
}
}
post("US_ADD", {
username: username.value,
display_name: displayname.value,
password: password.value,
picture: picture.files[0],
permissions: permissionsList
}).then((answer) => {
if(answer == "ALREADY_EXIST") {
returnInfo.err("L'utilisateur existe déjà !")
} else if(answer == "USERNAME_MISSING") {
returnInfo.err("Le nom d'utilisateur est manquant !")
} else if(answer == "DISPLAY_NAME_MISSING") {
returnInfo.err("Le nom d'affichage est manquant !")
} else if(answer == "PASSWORD_MISSING") {
returnInfo.err("Le mot de passe est manquant !")
} else {
View.destroyPopup()
loadUsers()
}
})
})
})
users.then(users => {
users = new Map(JSON.parse(users))
for(var user of users) {
user = user[1]
console.log(user)
const userComponent = new User({
username: user.username,
display_name: user.display_name,
picture: user.picture,
permission: user.permission,
window: View,
component: usersComponent
})
usersList.push(userComponent)
}
usersNumber.innerHTML = usersList.length
const usersContent = new Array()
for(var user of usersList) {
usersContent.push(user.generateHTML())
}
usersDiv.innerHTML = usersContent.join("")
for(var user of usersList) {
user.loadScript()
}
})
}
})

1370
public/stylesheets/style.css Normal file

File diff suppressed because it is too large Load Diff

25
routes/index.js Normal file
View File

@ -0,0 +1,25 @@
var express = require('express');
var router = express.Router();
var auth = require("../bin/auth")
/* GET home page. */
router.get('/', function(req, res, next) {
if(!auth.check(req.cookies.token)) {
res.clearCookie('token')
res.redirect(302, "/login")
} else {
if(process.env.DEV ) {
res.render('index', {dev: "<p class='view-item yellow'>DÉVELOPEMENT</p>"});
} else {
res.render('index', {dev: ""});
}
}
});
module.exports = router;

16
routes/internal.js Normal file
View File

@ -0,0 +1,16 @@
var express = require('express');
var router = express.Router();
var path = require("path")
router.get("/socketlink", (req,res,next) => {
if(process.env.DEV == true) {
res.send("ws://localhost:3001")
} else {
res.send("ws://neutral.raphix.fr")
}
})
module.exports = router;

23
routes/link.js Normal file
View File

@ -0,0 +1,23 @@
var express = require('express');
var router = express.Router();
var path = require("path")
module.exports = router;
module.exports.ApplyLinks = class {
constructor(links) {
// Remove all routes
router.stack = []
for(var link of links) {
const url = link.url
router.get("/" + link.dest, (req,res,next) => {
res.redirect(302, url)
})
}
module.exports = router;
}
}

61
routes/login.js Normal file
View File

@ -0,0 +1,61 @@
var express = require('express');
var router = express.Router();
var auth = require("../bin/auth")
/* GET home page. */
router.get('/', function(req, res, next) {
if(auth.check(req.cookies.token)) {
res.redirect(302, "/")
} else {
res.clearCookie('token')
res.render('login', {version: require("../package.json").version});
}
});
module.exports = router;
router.post("/", (req, res) => {
const body = req.body
const token = auth.login({
username: body.username,
password: body.password
})
if(token == "AUTH_FAILED") {
setTimeout(() => {
res.status(403).send("AUTH_FAILED")
}, 1000)
} else {
res.cookie('token' , token, { maxAge: 900000000, httpOnly: true })
res.status(200).send("AUTH_SUCCESS")
}
})
router.get('/signout', function(req, res, next) {
if(!auth.check(req.cookies.token)) {
res.clearCookie('token')
res.redirect(302, "/")
} else {
auth.signout(req.cookies.token)
res.clearCookie('token')
res.redirect(302, "/")
}
});

9
routes/stylepage.js Normal file
View File

@ -0,0 +1,9 @@
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('utils/stylepage');
});
module.exports = router;

65
views/index.ejs Normal file
View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<title>Neutral</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body class="LOG_body">
<div id='panel-box' 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">
<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="taskbar-box m-align t-center">
<div id="menu" class="menu-drop">
<div id="user-settings-button" class="menu-settings">
<i class="fas fa-gear"></i>
<p>Paramètres du compte</p>
</div>
<div class="menu-signout menu-settings" onclick="window.location = '/login/signout'">
<i class="fas fa-sign-out-alt"></i>
<p>Déconnexion</p>
</div>
</div>
<div class="taskbar-content">
<div class="taskbar-actions">
<img id="menu-logo" class="taskbar-logo" src="/images/FormatLogo_WHITE.svg">
<div id="views-items" class="views-items">
<%- dev %>
</div>
</div>
<div class="taskbar-user">
<div class="taskbar-userinfo">
<p id="infoDisplayname" class="taskbar-dispname">DisplayName</p>
<p id="infoUsername" class="taskbar-username">Username</p>
</div>
<img id="infoUserimage" class="taskbar-image" src="/images/default.jpg">
</div>
</div>
</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/indexscript.js"></script>
<script src="/javascripts/link.js"></script>
<script src="/javascripts/service.js"></script>
<script src="/javascripts/server.js"></script>
<script src="/javascripts/pipeline.js"></script>
<script src="/javascripts/filexplorer.js"></script>
<script src="/javascripts/user.js"></script>
<script src="/javascripts/metric.js"></script>
<script src="/javascripts/setting.js"></script>
<script src="/javascripts/personal.js"></script>
</body>
</html>

31
views/login.ejs Normal file
View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<title>Neutral</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body class="LOG_body">
<div class=" form-box m-align t-center">
<div class="logo"><img class="logo-img" src="/images/FormatLogo_WHITE.svg"><p>Neutral</p></div>
<div>
<p>Nom d'utilisateur</p>
<input id="username" type="text" class="field m-align">
</div>
<div>
<p>Mot de passe</p>
<input id="password" type="password" class="field m-align">
</div>
<p id="login-info"></p>
<button id="submit" class="btn green m-align LOG_btn"><span><i class="fa-solid fa-right-to-bracket"></i> Connexion</span></button>
<p style="font-size: 12px;">Version : <%- version %></p>
<p style="font-size: 12px;">Panel d'administration</p>
<a href="https://raphix.fr" style="font-size: 12px;">Revenir sur raphix.fr</a>
</div>
<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/loginscript.js"></script>
</body>
</html>

32
views/utils/error.ejs Normal file
View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>Neutral - Erreur</title>
<link rel='stylesheet' href='/stylesheets/style.css'/>
<style>
body {
padding: 2%;
}
div {
background-color: rgb(32, 32, 32);
border-radius: 1vw;
padding: 2%;
}
</style>
</head>
<body>
<div>
<h1>Erreur <%= error.status %></h1>
<h2><%= message %> - <%= error.status %></h1>
<pre><%= error.stack %></pre>
</div>
<br>
<a href="/"><button class="btn red"><span>Retourner sur le panel</span></button></a>
</body>
</html>

75
views/utils/stylepage.ejs Normal file
View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<title>Neutral</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1>Neutral - Page de style</h1>
<p>Cette page est dédiée au développement et n'est pas en relation avec le Panel Neutral.</p>
<a href="/"><button class="btn red"><span>Revenir sur le panel</span></button></a>
<hr>
<p>Police : <code>'Roboto', sans-serif</code></p>
<p>Liens : <a href="/stylepage">https://neutral.raphix.fr/stylepage</a></p>
<hr>
<br>
<style>
.box-color {
width: 100px;
height: 100px;
transition: 0.2s;
border: 1px solid;
}
.box-color:hover {
transform: scale(1.1);
}
.box-color:active {
transform: scale(1);
color: rgb(58, 255, 3);
}
button {
width: fit-content ;
}
</style>
<div class="col">
<p>Classe : <code>btn</code></p>
<div class="row">
<button class="btn red"><span>Error Button</span></button>
<button class="btn blue "><span>Info Button</span></button>
<button class="btn yellow"><span>Warning Button</span></button>
<button class="btn green"><span>Confirm Button</span></button>
<button class="btn"><span>Default Button</span></button>
</div>
<p>Classe : <code>btn min</code></p>
<div class="row">
<button class="btn min red"><span><i class="fa fa-xmark"></i></span></button>
<button class="btn min blue"><span><i class="fa fa-info"></i></span></button>
<button class="btn min"><span><i class="fa fa-def"></i></span></button>
</div>
<div class="row">
<div onclick="copyToClipboard('#323031')" style='background-color: #323031;' class="box-color"><p style="text-align: center;">#323031</p></div>
<div onclick="copyToClipboard('#f10000')" style='background-color: #f10000;' class="box-color"><p style="text-align: center;">#f10000</p></div>
<div onclick="copyToClipboard('#605e5863')" style='background-color: #605e5863;' class="box-color"><p style="text-align: center;">#605e5863</p></div>
</div>
<p>Classe : <code>field</code></p>
<input type="text" class="field">
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
}
</script>
<script defer="" src="https://use.fontawesome.com/releases/v6.4.2/js/all.js" crossorigin="anonymous"></script>
</body>
</html>