diff --git a/.gitignore b/.gitignore index b512c09..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -node_modules \ No newline at end of file +node_modules diff --git a/config.conf b/config.conf index 3f3a251..22004d4 100644 --- a/config.conf +++ b/config.conf @@ -1 +1,2 @@ logs = enable +log_to_file = false diff --git a/package-lock.json b/package-lock.json index 80743ea..7321b84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,10 +7,15 @@ "": { "name": "webui", "version": "1.0.0", - "license": "ISC", + "license": "GPLv2", "dependencies": { - "express": "^4.17.1", - "multer": "^1.4.5-lts.1" + "chalk": "^5.3.0", + "express": "^4.19.2", + "morgan": "^1.10.0", + "multer": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/accepts": { @@ -38,6 +43,24 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -117,18 +140,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", "engines": [ - "node >= 0.8" + "node >= 6.0" ], "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", - "readable-stream": "^2.2.2", + "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, @@ -168,12 +203,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -483,12 +512,6 @@ "node": ">= 0.10" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -579,6 +602,34 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -586,22 +637,21 @@ "license": "MIT" }, "node_modules/multer": { - "version": "1.4.5-lts.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", - "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", - "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" + "type-is": "^1.6.18", + "xtend": "^4.0.2" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 10.16.0" } }, "node_modules/negotiator": { @@ -646,6 +696,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -661,12 +720,6 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -720,26 +773,19 @@ } }, "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -916,20 +962,14 @@ } }, "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", diff --git a/package.json b/package.json index 2c6ed80..89838a0 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,20 @@ { "name": "webui", "version": "1.0.0", - "description": "WebUI is a lightweight and easy-to-use web application for managing files on your server. It provides a simple interface to upload, download, delete, and preview files.", - "main": "script.js", + "description": "WebUI for managing server files", + "main": "server.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", "start": "node server.js" }, - "repository": { - "type": "git", - "url": "https://codeberg.org/Soccera/webui.git" - }, - "keywords": [], - "author": "", "dependencies": { - "express": "^4.17.1", - "multer": "^1.4.5-lts.1" - } + "chalk": "^5.3.0", + "express": "^4.19.2", + "morgan": "^1.10.0", + "multer": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "author": "Coasteen", + "license": "GPLv2" } diff --git a/server.js b/server.js index 9ce1e10..a9d52f7 100644 --- a/server.js +++ b/server.js @@ -1,122 +1,116 @@ -const express = require('express'); -const multer = require('multer'); -const path = require('path'); -const fs = require('fs'); +const express = require('express') +const multer = require('multer') +const path = require('path') +const fs = require('fs') +const morgan = require('morgan') +const chalk = require('chalk').default -const app = express(); +const app = express() +const uploadDir = path.join(__dirname, 'uploads') +if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true }) function loadConfig() { - try { - const configPath = path.join(__dirname, 'config.conf'); - const content = fs.readFileSync(configPath, 'utf8'); - const match = content.match(/^logs\s*=\s*(.*)$/m); - if (match && match[1].trim().toLowerCase() === 'disable') { - return { loggingEnabled: false }; - } - } catch (e) { - } - return { loggingEnabled: true }; + try { + const content = fs.readFileSync(path.join(__dirname, 'config.conf'), 'utf8') + const match = content.match(/^logs\s*=\s*(.*)$/m) + if (match && match[1].trim().toLowerCase() === 'disable') return { loggingEnabled: false } + } catch {} + return { loggingEnabled: true } } -const config = loadConfig(); +const config = loadConfig() -function logAction(message) { - if (config.loggingEnabled) { - console.log(`[ACTION] ${message}`); - } +const colors = { + info: chalk.cyanBright, + warn: chalk.yellowBright, + error: chalk.redBright, + action: chalk.greenBright, + sys: chalk.magentaBright } -const storage = multer.diskStorage({ - destination: (req, file, cb) => { - cb(null, 'uploads/'); - }, - filename: (req, file, cb) => { - const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); - cb(null, uniqueSuffix + '-' + file.originalname); - } -}); +function log(level, msg) { + if (!config.loggingEnabled) return + const time = new Date().toISOString().replace('T', ' ').split('.')[0] + const colorFn = colors[level.toLowerCase()] || chalk.white + console.log(`[${time}] [${level.toUpperCase()}] ${colorFn(msg)}`) +} -const upload = multer({ storage: storage }); +const storage = multer.diskStorage({ + destination: (req, file, cb) => cb(null, uploadDir), + filename: (req, file, cb) => cb(null, `${Date.now()}-${Math.round(Math.random() * 1e9)}-${file.originalname}`) +}) +const upload = multer({ storage }).array('files') -app.use(express.static(__dirname)); +app.use(express.static(__dirname)) +if (config.loggingEnabled) app.use(morgan('dev')) -app.post('/upload', upload.array('files'), (req, res) => { - if (!req.files || req.files.length === 0) { - logAction(`FAILED upload attempt: No files received.`); - return res.status(400).send('No files were uploaded.'); - } - const uploadedNames = req.files.map(f => f.filename).join(', '); - logAction(`UPLOADED ${req.files.length} file(s): ${uploadedNames}`); - res.send(`Successfully uploaded ${req.files.length} file(s)!`); -}); +app.post('/upload', (req, res) => { + upload(req, res, err => { + if (err) { + log('error', `UPLOAD ERROR: ${err.message}`) + return res.status(500).send('Upload error') + } + if (!req.files?.length) { + log('warn', `UPLOAD FAILED: No files received`) + return res.status(400).send('No files uploaded') + } + const names = req.files.map(f => f.filename).join(', ') + log('action', `UPLOADED ${req.files.length} file(s): ${names}`) + res.send(`Uploaded ${req.files.length} file(s)!`) + }) +}) app.get('/files', (req, res) => { - logAction(`LIST files requested.`); - const directoryPath = path.join(__dirname, 'uploads'); - fs.readdir(directoryPath, (err, files) => { - if (err) { - return res.status(500).send('Unable to scan directory: ' + err); - } - const fileListPromises = files.map(file => { - return new Promise((resolve) => { - const filePath = path.join(directoryPath, file); - fs.stat(filePath, (err, stats) => { - if (err) { - return resolve(null); - } - resolve({ - name: file, - path: `/uploads/${file}`, - size: stats.size, - date: stats.mtime - }); - }); - }); - }); - - Promise.all(fileListPromises).then(fileList => { - res.json(fileList.filter(file => file !== null)); - }); - }); -}); + log('info', 'Listing all files') + fs.readdir(uploadDir, (err, files) => { + if (err) { + log('error', `Unable to read uploads directory: ${err.message}`) + return res.status(500).send('Unable to scan directory') + } + const fileList = files.map(file => { + const stat = fs.statSync(path.join(uploadDir, file)) + return { name: file, path: `/uploads/${file}`, size: stat.size, date: stat.mtime } + }) + res.json(fileList) + }) +}) app.get('/uploads/:filename', (req, res) => { - const filename = req.params.filename; - logAction(`PREVIEW/ACCESS file: ${filename}`); - const filePath = path.join(__dirname, 'uploads', filename); - res.sendFile(filePath, err => { - if (err) { - res.status(404).send(`File not found: ${filename}`); - } - }); -}); + const filename = req.params.filename + const filePath = path.join(uploadDir, filename) + log('info', `Serving file: ${filename}`) + res.sendFile(filePath, err => { + if (err) { + log('error', `File not found: ${filename}`) + res.status(404).send('File not found') + } + }) +}) app.get('/download/:filename', (req, res) => { - const filename = req.params.filename; - logAction(`DOWNLOAD file: ${filename}`); - const filePath = path.join(__dirname, 'uploads', filename); - res.download(filePath, err => { - if (err) { - res.status(404).send(`File not found for download: ${filename}`); - } - }); -}); + const filename = req.params.filename + const filePath = path.join(uploadDir, filename) + log('action', `Downloading file: ${filename}`) + res.download(filePath, err => { + if (err) { + log('error', `Download failed for: ${filename}`) + res.status(404).send('File not found') + } + }) +}) app.delete('/delete/:filename', (req, res) => { - const filename = req.params.filename; - const filePath = path.join(__dirname, 'uploads', filename); - fs.unlink(filePath, err => { - if (err) { - logAction(`FAILED deletion of file: ${filename}`); - return res.status(404).send('File not found'); - } - logAction(`DELETED file: ${filename}`); - res.send('File deleted successfully'); - }); -}); + const filename = req.params.filename + const filePath = path.join(uploadDir, filename) + fs.unlink(filePath, err => { + if (err) { + log('error', `Failed to delete: ${filename}`) + return res.status(404).send('File not found') + } + log('action', `Deleted file: ${filename}`) + res.send('File deleted successfully') + }) +}) -app.listen(3000, () => { - if (config.loggingEnabled) { - console.log('[*] Server is running on http://localhost:3000'); - } -}); +const PORT = 3000 +app.listen(PORT, () => log('sys', `Server running on http://localhost:${PORT}`)) diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..9d671f3 --- /dev/null +++ b/start.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +node server.js diff --git a/uploads/1759997756404-485664297-1_MiTE FML.zip b/uploads/1759997756404-485664297-1_MiTE FML.zip new file mode 100644 index 0000000..ce050bf Binary files /dev/null and b/uploads/1759997756404-485664297-1_MiTE FML.zip differ