Colors, auto mkdir for uploads/, et cetera

This commit is contained in:
coast 2025-10-09 11:47:33 +03:30
parent 243ba4d1c7
commit 36345026a7
7 changed files with 214 additions and 177 deletions

2
.gitignore vendored
View file

@ -1 +1 @@
node_modules node_modules

View file

@ -1 +1,2 @@
logs = enable logs = enable
log_to_file = false

164
package-lock.json generated
View file

@ -7,10 +7,15 @@
"": { "": {
"name": "webui", "name": "webui",
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "GPLv2",
"dependencies": { "dependencies": {
"express": "^4.17.1", "chalk": "^5.3.0",
"multer": "^1.4.5-lts.1" "express": "^4.19.2",
"morgan": "^1.10.0",
"multer": "^2.0.0"
},
"engines": {
"node": ">=18.0.0"
} }
}, },
"node_modules/accepts": { "node_modules/accepts": {
@ -38,6 +43,24 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT" "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": { "node_modules/body-parser": {
"version": "1.20.3", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@ -117,18 +140,30 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/concat-stream": {
"version": "1.6.2", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"engines": [ "engines": [
"node >= 0.8" "node >= 6.0"
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
"inherits": "^2.0.3", "inherits": "^2.0.3",
"readable-stream": "^2.2.2", "readable-stream": "^3.0.2",
"typedarray": "^0.0.6" "typedarray": "^0.0.6"
} }
}, },
@ -168,12 +203,6 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT" "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": { "node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -483,12 +512,6 @@
"node": ">= 0.10" "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": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -579,6 +602,34 @@
"mkdirp": "bin/cmd.js" "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": { "node_modules/ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -586,22 +637,21 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/multer": { "node_modules/multer": {
"version": "1.4.5-lts.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
"integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
"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.",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"append-field": "^1.0.0", "append-field": "^1.0.0",
"busboy": "^1.0.0", "busboy": "^1.6.0",
"concat-stream": "^1.5.2", "concat-stream": "^2.0.0",
"mkdirp": "^0.5.4", "mkdirp": "^0.5.6",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"type-is": "^1.6.4", "type-is": "^1.6.18",
"xtend": "^4.0.0" "xtend": "^4.0.2"
}, },
"engines": { "engines": {
"node": ">= 6.0.0" "node": ">= 10.16.0"
} }
}, },
"node_modules/negotiator": { "node_modules/negotiator": {
@ -646,6 +696,15 @@
"node": ">= 0.8" "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": { "node_modules/parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -661,12 +720,6 @@
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT" "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": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -720,26 +773,19 @@
} }
}, },
"node_modules/readable-stream": { "node_modules/readable-stream": {
"version": "2.3.8", "version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "inherits": "^2.0.3",
"inherits": "~2.0.3", "string_decoder": "^1.1.1",
"isarray": "~1.0.0", "util-deprecate": "^1.0.1"
"process-nextick-args": "~2.0.0", },
"safe-buffer": "~5.1.1", "engines": {
"string_decoder": "~1.1.1", "node": ">= 6"
"util-deprecate": "~1.0.1"
} }
}, },
"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": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -916,20 +962,14 @@
} }
}, },
"node_modules/string_decoder": { "node_modules/string_decoder": {
"version": "1.1.1", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT", "license": "MIT",
"dependencies": { "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": { "node_modules/toidentifier": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",

View file

@ -1,20 +1,20 @@
{ {
"name": "webui", "name": "webui",
"version": "1.0.0", "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.", "description": "WebUI for managing server files",
"main": "script.js", "main": "server.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js" "start": "node server.js"
}, },
"repository": {
"type": "git",
"url": "https://codeberg.org/Soccera/webui.git"
},
"keywords": [],
"author": "",
"dependencies": { "dependencies": {
"express": "^4.17.1", "chalk": "^5.3.0",
"multer": "^1.4.5-lts.1" "express": "^4.19.2",
} "morgan": "^1.10.0",
"multer": "^2.0.0"
},
"engines": {
"node": ">=18.0.0"
},
"author": "Coasteen",
"license": "GPLv2"
} }

198
server.js
View file

@ -1,122 +1,116 @@
const express = require('express'); const express = require('express')
const multer = require('multer'); const multer = require('multer')
const path = require('path'); const path = require('path')
const fs = require('fs'); 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() { function loadConfig() {
try { try {
const configPath = path.join(__dirname, 'config.conf'); const content = fs.readFileSync(path.join(__dirname, 'config.conf'), 'utf8')
const content = fs.readFileSync(configPath, 'utf8'); const match = content.match(/^logs\s*=\s*(.*)$/m)
const match = content.match(/^logs\s*=\s*(.*)$/m); if (match && match[1].trim().toLowerCase() === 'disable') return { loggingEnabled: false }
if (match && match[1].trim().toLowerCase() === 'disable') { } catch {}
return { loggingEnabled: false }; return { loggingEnabled: true }
}
} catch (e) {
}
return { loggingEnabled: true };
} }
const config = loadConfig(); const config = loadConfig()
function logAction(message) { const colors = {
if (config.loggingEnabled) { info: chalk.cyanBright,
console.log(`[ACTION] ${message}`); warn: chalk.yellowBright,
} error: chalk.redBright,
action: chalk.greenBright,
sys: chalk.magentaBright
} }
const storage = multer.diskStorage({ function log(level, msg) {
destination: (req, file, cb) => { if (!config.loggingEnabled) return
cb(null, 'uploads/'); const time = new Date().toISOString().replace('T', ' ').split('.')[0]
}, const colorFn = colors[level.toLowerCase()] || chalk.white
filename: (req, file, cb) => { console.log(`[${time}] [${level.toUpperCase()}] ${colorFn(msg)}`)
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); }
cb(null, uniqueSuffix + '-' + file.originalname);
}
});
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) => { app.post('/upload', (req, res) => {
if (!req.files || req.files.length === 0) { upload(req, res, err => {
logAction(`FAILED upload attempt: No files received.`); if (err) {
return res.status(400).send('No files were uploaded.'); log('error', `UPLOAD ERROR: ${err.message}`)
} return res.status(500).send('Upload error')
const uploadedNames = req.files.map(f => f.filename).join(', '); }
logAction(`UPLOADED ${req.files.length} file(s): ${uploadedNames}`); if (!req.files?.length) {
res.send(`Successfully uploaded ${req.files.length} file(s)!`); 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) => { app.get('/files', (req, res) => {
logAction(`LIST files requested.`); log('info', 'Listing all files')
const directoryPath = path.join(__dirname, 'uploads'); fs.readdir(uploadDir, (err, files) => {
fs.readdir(directoryPath, (err, files) => { if (err) {
if (err) { log('error', `Unable to read uploads directory: ${err.message}`)
return res.status(500).send('Unable to scan directory: ' + err); return res.status(500).send('Unable to scan directory')
} }
const fileListPromises = files.map(file => { const fileList = files.map(file => {
return new Promise((resolve) => { const stat = fs.statSync(path.join(uploadDir, file))
const filePath = path.join(directoryPath, file); return { name: file, path: `/uploads/${file}`, size: stat.size, date: stat.mtime }
fs.stat(filePath, (err, stats) => { })
if (err) { res.json(fileList)
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));
});
});
});
app.get('/uploads/:filename', (req, res) => { app.get('/uploads/:filename', (req, res) => {
const filename = req.params.filename; const filename = req.params.filename
logAction(`PREVIEW/ACCESS file: ${filename}`); const filePath = path.join(uploadDir, filename)
const filePath = path.join(__dirname, 'uploads', filename); log('info', `Serving file: ${filename}`)
res.sendFile(filePath, err => { res.sendFile(filePath, err => {
if (err) { if (err) {
res.status(404).send(`File not found: ${filename}`); log('error', `File not found: ${filename}`)
} res.status(404).send('File not found')
}); }
}); })
})
app.get('/download/:filename', (req, res) => { app.get('/download/:filename', (req, res) => {
const filename = req.params.filename; const filename = req.params.filename
logAction(`DOWNLOAD file: ${filename}`); const filePath = path.join(uploadDir, filename)
const filePath = path.join(__dirname, 'uploads', filename); log('action', `Downloading file: ${filename}`)
res.download(filePath, err => { res.download(filePath, err => {
if (err) { if (err) {
res.status(404).send(`File not found for download: ${filename}`); log('error', `Download failed for: ${filename}`)
} res.status(404).send('File not found')
}); }
}); })
})
app.delete('/delete/:filename', (req, res) => { app.delete('/delete/:filename', (req, res) => {
const filename = req.params.filename; const filename = req.params.filename
const filePath = path.join(__dirname, 'uploads', filename); const filePath = path.join(uploadDir, filename)
fs.unlink(filePath, err => { fs.unlink(filePath, err => {
if (err) { if (err) {
logAction(`FAILED deletion of file: ${filename}`); log('error', `Failed to delete: ${filename}`)
return res.status(404).send('File not found'); return res.status(404).send('File not found')
} }
logAction(`DELETED file: ${filename}`); log('action', `Deleted file: ${filename}`)
res.send('File deleted successfully'); res.send('File deleted successfully')
}); })
}); })
app.listen(3000, () => { const PORT = 3000
if (config.loggingEnabled) { app.listen(PORT, () => log('sys', `Server running on http://localhost:${PORT}`))
console.log('[*] Server is running on http://localhost:3000');
}
});

2
start.sh Executable file
View file

@ -0,0 +1,2 @@
#!/usr/bin/env sh
node server.js

Binary file not shown.