diff --git a/bin/global-variables.js b/bin/global-variables.js index 6b12d64..d563837 100644 --- a/bin/global-variables.js +++ b/bin/global-variables.js @@ -10,7 +10,8 @@ const __glob = { 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 + "public" + path.sep + 'images' + path.sep + "users", + USERS_IMAGES: root + path.sep + "data" + path.sep + "user_images", + PACKAGE_JSON: root + path.sep + "package.json", }; diff --git a/bin/metrics.js b/bin/metrics.js new file mode 100644 index 0000000..a461b3b --- /dev/null +++ b/bin/metrics.js @@ -0,0 +1,112 @@ +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 + + 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 res = 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) + } else { + 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(''); +} \ No newline at end of file diff --git a/bin/servers.js b/bin/servers.js index 336917a..54c0609 100644 --- a/bin/servers.js +++ b/bin/servers.js @@ -13,6 +13,7 @@ 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'); /** @@ -56,6 +57,11 @@ module.exports.serverIO = function(server) { * 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) => { @@ -217,6 +223,22 @@ module.exports.serverIO = function(server) { }) } + 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) diff --git a/bin/users.js b/bin/users.js index dd2f29e..0861aeb 100644 --- a/bin/users.js +++ b/bin/users.js @@ -77,7 +77,7 @@ module.exports.fetchUsers = function () { ], "tokens": [], "lastLogin": "DEFAULT ACCOUNT", - "picture": "/images/users/default.jpg" + "picture": "/images/default.jpg" }) @@ -103,7 +103,7 @@ module.exports.User = class { permission = [] tokens = [] lastLogin = new Date() - picture = "/images/users/default.jpg" + picture = "/images/default.jpg" constructor(properties) { @@ -164,7 +164,7 @@ module.exports.User = class { this.lastLogin = new Date() } if(this.picture == null) { - this.picture = "/images/users/default.jpg" + this.picture = "/images/default.jpg" } @@ -330,7 +330,7 @@ module.exports.User = class { var pictureDir = __glob.USERS_IMAGES + path.sep + uuid.v4().toString() + ".png" fs.writeFileSync(pictureDir, file) - this.picture = pictureDir.replace(__glob.USERS_IMAGES + path.sep, "/images/users/") + this.picture = pictureDir.replace(__glob.USERS_IMAGES + path.sep, "/users/") this.register() } @@ -383,7 +383,7 @@ module.exports.addUser = function(settings) { if(settings.picture == null) { - pictureDir = "/images/users/default.jpg" + pictureDir = "/images/default.jpg" } else { pictureDir = __glob.USERS_IMAGES + path.sep + uuid.v4().toString() + ".png" fs.writeFileSync(pictureDir, settings.picture) @@ -465,6 +465,53 @@ module.exports.editUser = function(settings) { } } +module.exports.editMySelf = function (settings, user) { + if(user.username == settings.actualUsername) { + 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) diff --git a/bin/www b/bin/www index 111feaa..aeb5560 100644 --- a/bin/www +++ b/bin/www @@ -6,7 +6,7 @@ var log = require("loguix") var {LogType} = require("loguix") var { __glob } = require("./global-variables") -log.setup(__glob.LOGS) +log.setup(__glob.LOGS, __glob.PACKAGE_JSON) const wlog = new LogType("Serveur") diff --git a/main.js b/main.js index 955a27c..5a258c0 100644 --- a/main.js +++ b/main.js @@ -48,6 +48,7 @@ function setup() { 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() diff --git a/package-lock.json b/package-lock.json index 7990bd0..e358e81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "neutral", - "version": "0.6.0", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "neutral", - "version": "0.6.0", + "version": "0.7.0", "license": "ISC", "dependencies": { "cookie-parser": "~1.4.4", @@ -16,7 +16,7 @@ "express": "~4.16.1", "http-errors": "~1.6.3", "install": "^0.13.0", - "loguix": "1.4.1", + "loguix": "^1.4.2", "nodemon": "^3.0.1", "os-utils": "^0.0.14", "pm2": "^5.3.0", @@ -1326,8 +1326,9 @@ } }, "node_modules/loguix": { - "version": "1.4.1", - "license": "ISC" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loguix/-/loguix-1.4.2.tgz", + "integrity": "sha512-fWp699F5Dqszpnriwotr3XvsaPXYAjmV3X2L4tmqUXgwHggvx1Fak1z3Ac7CeuVdIdYY5l85UQrEYPmhAb+IUw==" }, "node_modules/lru-cache": { "version": "6.0.0", diff --git a/package.json b/package.json index 2803d9a..4abaaa8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neutral", - "version": "0.6.1", + "version": "1.0.0", "description": "Panel d'administration de Raphix", "main": "index.js", "scripts": { @@ -27,7 +27,7 @@ "express": "~4.16.1", "http-errors": "~1.6.3", "install": "^0.13.0", - "loguix": "1.4.1", + "loguix": "^1.4.2", "nodemon": "^3.0.1", "os-utils": "^0.0.14", "pm2": "^5.3.0", diff --git a/public/images/users/default.jpg b/public/images/default.jpg similarity index 100% rename from public/images/users/default.jpg rename to public/images/default.jpg diff --git a/public/images/users/8e63fe73-66c4-4f48-9220-c07fef237eee.png b/public/images/users/8e63fe73-66c4-4f48-9220-c07fef237eee.png deleted file mode 100644 index ca40fc8..0000000 Binary files a/public/images/users/8e63fe73-66c4-4f48-9220-c07fef237eee.png and /dev/null differ diff --git a/public/javascripts/basics.js b/public/javascripts/basics.js index 860fd92..08ed476 100644 --- a/public/javascripts/basics.js +++ b/public/javascripts/basics.js @@ -1180,6 +1180,86 @@ class User { } } +/** + * Permet de créer un composant de vue de type "Metrics" + */ +class Metric { + properties; + View; + Component; + data; + constructor(settings) { + this.properties = settings.properties + this.View = settings.View + this.Component = settings.Component + this.data = settings.properties.data + } + + generateHTML() { + return ` +
+
+
+

${this.properties.name}

+

${this.properties.id}

+
+
+
+
+
+ + +
+ + +
+ ` + + } + + loadScript() { + + const contentDiv = getID(`${this.properties.id}_content`) + + for(const data of this.data) { + contentDiv.innerHTML += `

${data.description}

${data.name}

Valeur : ${data.value}

` + + } + + const deleteButton = getID(`${this.properties.id}_metricpower`) + + deleteButton.addEventListener("click", () => { + this.View.createPopup({ + title: ` Supprimer la métrique : ${this.properties.name}`, + content: ` +

Voulez-vous vraiment supprimer la métrique ${this.properties.name} ?

+ + + + ` + }) + + const deleteCButton = getID(`${this.properties.id}_deleteconfirm`) + + deleteCButton.addEventListener("click", () => { + const request = post(`MT_DELETE`, this.properties.id) + request.then((answer) => { + if(answer == "OK") { + this.View.destroyPopup() + this.View.destroy() + this.Component.forceWindow() + + } else { + console.log(answer) + } + }) + }) + }) + } + + + +} /** * Permet de créer un item de la barre des tâches @@ -1196,7 +1276,6 @@ class ViewItem { show() { const viewItems = getID("views-items") - const item = document.createElement("div") item.id = `${this.window.ViewProperties.title}_item` item.classList.add("view-item") diff --git a/public/javascripts/filexplorer.js b/public/javascripts/filexplorer.js index 55db436..8fc329b 100644 --- a/public/javascripts/filexplorer.js +++ b/public/javascripts/filexplorer.js @@ -14,7 +14,7 @@ explorer.createWindow(() => { }) - + @@ -58,6 +58,9 @@ explorer.createWindow(() => {
+
+

Chargement en cours ...

+
diff --git a/public/javascripts/indexscript.js b/public/javascripts/indexscript.js index 81d6cc6..450226b 100644 --- a/public/javascripts/indexscript.js +++ b/public/javascripts/indexscript.js @@ -7,6 +7,7 @@ loadUserInfo() function loadUserInfo() { + const infoUsername = getID("infoUsername") const infoUserimage = getID("infoUserimage") @@ -25,7 +26,7 @@ function loadUserInfo() { const permissions = ANS_user.permission const AvailableViews = new Array() - + // permissions.forEach((permValue) => { AllComponents.forEach((component) => { @@ -64,3 +65,4 @@ function loadUserInfo() { } + diff --git a/public/javascripts/link.js b/public/javascripts/link.js index 7a4adbb..370bc97 100644 --- a/public/javascripts/link.js +++ b/public/javascripts/link.js @@ -11,7 +11,11 @@ links.createWindow(() => {
- + `) const addBtn = getID(`${View.getViewTitle()}_add`) @@ -182,8 +186,6 @@ links.createWindow(() => { ` - - }) links.forEach((link) => { diff --git a/public/javascripts/metric.js b/public/javascripts/metric.js index e69de29..94b860c 100644 --- a/public/javascripts/metric.js +++ b/public/javascripts/metric.js @@ -0,0 +1,138 @@ +metrics.createWindow(() => { + + const View = new ViewWindow({ + title: ` Web Metrik`, + width: "600px", + height: "600px", + }) + + loadMetrics() + + function loadMetrics() { + + View.setContent(` +
+
+

WebMetrik : Calcul en cours

+
+ + +
+
+
+ +
+

Chargement en cours ...

+
+ +
`) + + 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) => { + if(ANS_metrics != "UNAVAILABLE") { + metricsList.length = 0 + 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 = `
+

Une erreur est survenue lors du chargement des WebMetrik

+
` + } + }) + + // 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: ` +
+

Adresse

+ +

Port

+ +

Nom

+ +

Clé d'authentification

+ + + +
+ ` + }) + + 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: `

Une erreur est survenue lors de l'ajout de la WebMetrik

` + }) + } + }) + }) + + }) + + + } +}) + + + + /* +
+

Chargement en cours ...

+
*/ \ No newline at end of file diff --git a/public/javascripts/middle.js b/public/javascripts/middle.js index bffa2fc..89eb4e5 100644 --- a/public/javascripts/middle.js +++ b/public/javascripts/middle.js @@ -7,6 +7,8 @@ const explorer = new ViewComponent({ icon: "fa-solid fa-folder", permission: "FILES_EXPLORER" }) + + const services = new ViewComponent({ name: "Gestion des services", @@ -27,7 +29,7 @@ const explorer = new ViewComponent({ }) const metrics = new ViewComponent({ - name: "Web Metrics", + name: "Web Metrik", icon: "fa-solid fa-square-poll-vertical", permission: "METRICS" }) diff --git a/public/javascripts/personal.js b/public/javascripts/personal.js new file mode 100644 index 0000000..977c2dc --- /dev/null +++ b/public/javascripts/personal.js @@ -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: ` Mon Compte`, + width: "600px", + height: "650px" + }) + + View.setContent(` +
+

Mes informations

+
+ + + +
+

Nom d'utilisateur

+ +

Nom d'affichage

+ +

Mot de passe

+ +

Photo de profil

+ + + + +
+
+ + +
+

Mes permissions

+
+
+ +
+ `) + + 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("

" + permValue + "

") + + }) + + 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" + } +}) \ No newline at end of file diff --git a/public/javascripts/pipeline.js b/public/javascripts/pipeline.js index 3260870..2e9fbfd 100644 --- a/public/javascripts/pipeline.js +++ b/public/javascripts/pipeline.js @@ -36,7 +36,7 @@ pipelines.createWindow(() => { View.setContent(`
-
Recharger les pipelines
+ ${pipelinesList.join("")}
`) diff --git a/public/javascripts/user.js b/public/javascripts/user.js index d5d7657..8a20bd8 100644 --- a/public/javascripts/user.js +++ b/public/javascripts/user.js @@ -18,7 +18,9 @@ users.createWindow(async () => {
- +
+

Chargement en cours ...

+
`) @@ -87,7 +89,7 @@ users.createWindow(async () => { }) - const addButton = document.getElementById("us-add-button") + 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") @@ -96,7 +98,7 @@ users.createWindow(async () => { const returnInfo = new TextResponse("user-addreturn-info") - addButton.addEventListener("click", () => { + addCButton.addEventListener("click", () => { var permissionsList = new Array() for(var permission of permissions) { console.log(permission.children[0].checked) @@ -164,6 +166,7 @@ users.createWindow(async () => { for(var user of usersList) { usersContent.push(user.generateHTML()) + } usersDiv.innerHTML = usersContent.join("") diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 245d381..f95ea20 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -1110,7 +1110,7 @@ a { flex-direction: column; gap: 10px; overflow-y: auto ; - height: 700px; + height: 550px; } .user-edit-actual { @@ -1201,4 +1201,162 @@ a { .us-delete { font-size: 12px; +} + +.mt-metrics { + + overflow-y: auto; + height: 470px; + + +} + +.mt-bar { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px; + +} + +.mt-bar p { + font-size: 15px; +} + + +.mt-add { + + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + justify-content: center; +} + +.mt-add p { + font-size: 14px; + margin-block: 0 !important; +} + +#mt-add-btn { + + margin-top: 10px; +} + + +.metric { + + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 10px; + border-radius: 10px; + background-color: #1b1b1bc1; + transition: 0.1s; + align-items: center; + gap: 10px; + margin: 10px; +} + + +.metric-content { + + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + font-size: 14px; +} + +.metric-id { + + font-size: 14px; + color: #c0c0c0; +} + + +.metric-data { + + display: flex; + align-items: center; + gap: 30px; +} + +.metric-data-id { + + font-size: 12px; + color: #c0c0c0; +} + +.metric-data-value { + + color: #c7c7c7; +} + +.metric-data-value span { + + color: #ffffff; +} + + +.menu-drop { + + display: flex; + flex-direction: column; + position: absolute; + bottom: 110%; + left: 0.7%; +} + +.menu-settings { + + display: flex; + gap: 10px; + padding: 10px; + align-items: center; + background-color: #1b1b1bc1; + transition: 0.1s; + font-size: 14px; + cursor: pointer; +} + +.menu-settings:hover { + + background-color: rgba(255, 255, 255, 0.614); + color: black; +} + +.user-settings { + + padding: 10px; +} + +.us-settings-info { + font-size: 12px; +} + +.us-settings-image { + + width: 125px; + height: 125px; + border-radius: 100%; +} + +#us-settings-permissions { + + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr ; + gap: 10px; + width: 50%; + font-size: 12px; +} + +#us-settings-permissions p { + margin: 0 !important; + background-color: #323031; + padding: 10px; + border-radius: 10px; + text-align: center; + width: 100px; } \ No newline at end of file diff --git a/views/index.ejs b/views/index.ejs index d8f0cda..ee7d080 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -12,14 +12,22 @@
+
- +
@@ -28,10 +36,11 @@

DisplayName

Username

- +
+ @@ -49,6 +58,7 @@ - + +