mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-18 17:21:33 +00:00
Upates
* Added Statistics menu to Administration items in dashboard. * Added /api/stats route. Imo, my implementation of this whole thing is rather dirty-ish, but hey as long as it works. I'll be using lolisafe2 for future devs tbh.
This commit is contained in:
parent
5180005625
commit
8c3fb78135
@ -564,6 +564,9 @@ uploadsController.formatInfoMap = (req, res, user, infoMap) => {
|
|||||||
userid: user !== undefined ? user.id : null,
|
userid: user !== undefined ? user.id : null,
|
||||||
timestamp: Math.floor(Date.now() / 1000)
|
timestamp: Math.floor(Date.now() / 1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Update last upload timestamp
|
||||||
|
utils.lastUpload = Date.now()
|
||||||
} else {
|
} else {
|
||||||
utils.deleteFile(info.data.filename, req.app.get('uploads-set')).catch(console.error)
|
utils.deleteFile(info.data.filename, req.app.get('uploads-set')).catch(console.error)
|
||||||
existingFiles.push(dbFile)
|
existingFiles.push(dbFile)
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
|
const { spawn } = require('child_process')
|
||||||
const config = require('./../config')
|
const config = require('./../config')
|
||||||
const db = require('knex')(config.database)
|
const db = require('knex')(config.database)
|
||||||
const fetch = require('node-fetch')
|
const fetch = require('node-fetch')
|
||||||
const ffmpeg = require('fluent-ffmpeg')
|
const ffmpeg = require('fluent-ffmpeg')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
const os = require('os')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const perms = require('./permissionController')
|
const perms = require('./permissionController')
|
||||||
const sharp = require('sharp')
|
const sharp = require('sharp')
|
||||||
|
|
||||||
const utilsController = {}
|
const utilsController = {
|
||||||
|
lastUpload: Date.now(),
|
||||||
|
lastStatsBuilt: 0,
|
||||||
|
cachedStats: {}
|
||||||
|
}
|
||||||
|
|
||||||
const uploadsDir = path.join(__dirname, '..', config.uploads.folder)
|
const uploadsDir = path.join(__dirname, '..', config.uploads.folder)
|
||||||
const thumbsDir = path.join(uploadsDir, 'thumbs')
|
const thumbsDir = path.join(uploadsDir, 'thumbs')
|
||||||
const thumbUnavailable = path.join(__dirname, '../public/images/unavailable.png')
|
const thumbUnavailable = path.join(__dirname, '../public/images/unavailable.png')
|
||||||
@ -402,4 +409,106 @@ utilsController.purgeCloudflareCache = async (names, uploads, thumbs) => {
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utilsController.getLinuxMemoryUsage = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const prc = spawn('free', ['-b'])
|
||||||
|
prc.stdout.setEncoding('utf8')
|
||||||
|
prc.stdout.on('data', data => {
|
||||||
|
const parsed = {}
|
||||||
|
const str = data.toString()
|
||||||
|
const lines = str.split(/\n/g)
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
lines[i] = lines[i].split(/\s+/)
|
||||||
|
if (i === 0) continue
|
||||||
|
const id = lines[i][0].toLowerCase().slice(0, -1)
|
||||||
|
if (!id) continue
|
||||||
|
if (!parsed[id]) parsed[id] = {}
|
||||||
|
for (let j = 1; j < lines[i].length; j++) {
|
||||||
|
const bytes = parseInt(lines[i][j])
|
||||||
|
parsed[id][lines[0][j]] = isNaN(bytes) ? null : bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(parsed)
|
||||||
|
})
|
||||||
|
prc.on('close', code => {
|
||||||
|
reject(new Error(`Process exited with code ${code}.`))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
utilsController.stats = async (req, res, next) => {
|
||||||
|
const user = await utilsController.authorize(req, res)
|
||||||
|
if (!user) return
|
||||||
|
|
||||||
|
const isadmin = perms.is(user, 'admin')
|
||||||
|
if (!isadmin) return res.status(403).end()
|
||||||
|
|
||||||
|
const platform = os.platform()
|
||||||
|
const system = { platform: `${platform}-${os.arch()}` }
|
||||||
|
if (platform === 'linux') {
|
||||||
|
const memoryUsage = await utilsController.getLinuxMemoryUsage()
|
||||||
|
system.memory = {
|
||||||
|
used: memoryUsage.mem.used,
|
||||||
|
total: memoryUsage.mem.total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
system['node.js'] = `${process.versions.node}`
|
||||||
|
system['memory usage'] = process.memoryUsage().rss
|
||||||
|
|
||||||
|
if (platform !== 'win32')
|
||||||
|
system.loadavg = `${os.loadavg().map(load => load.toFixed(2)).join(', ')}`
|
||||||
|
|
||||||
|
// Return cached stats
|
||||||
|
if (utilsController.lastStatsBuilt > utilsController.lastUpload)
|
||||||
|
return res.json({ success: true, system, stats: utilsController.cachedStats })
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
uploads: {
|
||||||
|
count: 0,
|
||||||
|
size: 0,
|
||||||
|
types: {
|
||||||
|
images: 0,
|
||||||
|
videos: 0,
|
||||||
|
others: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
count: 0,
|
||||||
|
disabled: 0,
|
||||||
|
permissions: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(perms.permissions).forEach(p => {
|
||||||
|
stats.users.permissions[p] = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const uploads = await db.table('files')
|
||||||
|
stats.uploads.count = uploads.length
|
||||||
|
for (const upload of uploads) {
|
||||||
|
stats.uploads.size += parseInt(upload.size)
|
||||||
|
const extname = utilsController.extname(upload.name)
|
||||||
|
if (utilsController.imageExtensions.includes(extname))
|
||||||
|
stats.uploads.types.images++
|
||||||
|
else if (utilsController.videoExtensions.includes(extname))
|
||||||
|
stats.uploads.types.videos++
|
||||||
|
else
|
||||||
|
stats.uploads.types.others++
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await db.table('users')
|
||||||
|
stats.users.count = users.length
|
||||||
|
for (const user of users) {
|
||||||
|
if (user.enabled === false || user.enabled === 0) stats.users.disabled++
|
||||||
|
user.permission = user.permission || 0
|
||||||
|
for (const p of Object.keys(stats.users.permissions))
|
||||||
|
if (user.permission === perms.permissions[p]) stats.users.permissions[p]++
|
||||||
|
}
|
||||||
|
|
||||||
|
utilsController.cachedStats = stats
|
||||||
|
utilsController.lastStatsBuilt = Date.now()
|
||||||
|
return res.json({ success: true, system, stats })
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = utilsController
|
module.exports = utilsController
|
||||||
|
21
package.json
21
package.json
@ -23,29 +23,30 @@
|
|||||||
"pull": "git stash; git pull; yarn; git stash pop"
|
"pull": "git stash; git pull; yarn; git stash pop"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^3.0.4",
|
"bcrypt": "^3.0.5",
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
"clamdjs": "^1.0.1",
|
"clamdjs": "^1.0.2",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-rate-limit": "^3.3.2",
|
"express-rate-limit": "^3.4.0",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"helmet": "^3.15.0",
|
"helmet": "^3.16.0",
|
||||||
"jszip": "^3.1.5",
|
"jszip": "^3.2.1",
|
||||||
"knex": "^0.16.3",
|
"knex": "^0.16.3",
|
||||||
"multer": "^1.4.1",
|
"multer": "^1.4.1",
|
||||||
"node-fetch": "^2.3.0",
|
"node-fetch": "^2.3.0",
|
||||||
"nunjucks": "^3.1.6",
|
"nunjucks": "^3.2.0",
|
||||||
|
"os": "^0.1.1",
|
||||||
"randomstring": "^1.1.5",
|
"randomstring": "^1.1.5",
|
||||||
"readline": "^1.3.0",
|
"readline": "^1.3.0",
|
||||||
"sharp": "^0.21.0",
|
"sharp": "^0.22.0",
|
||||||
"sqlite3": "^4.0.6"
|
"sqlite3": "^4.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^5.12.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-config-standard": "^12.0.0",
|
"eslint-config-standard": "^12.0.0",
|
||||||
"eslint-plugin-import": "^2.14.0",
|
"eslint-plugin-import": "^2.16.0",
|
||||||
"eslint-plugin-node": "^8.0.1",
|
"eslint-plugin-node": "^8.0.1",
|
||||||
"eslint-plugin-promise": "^4.0.1",
|
"eslint-plugin-promise": "^4.1.1",
|
||||||
"eslint-plugin-standard": "^4.0.0"
|
"eslint-plugin-standard": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,6 +233,10 @@ ul#albumsContainer li {
|
|||||||
height: 2.25em;
|
height: 2.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table .cell-indent {
|
||||||
|
padding-left: 2.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.is-linethrough {
|
.is-linethrough {
|
||||||
text-decoration: line-through
|
text-decoration: line-through
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,13 @@ page.prepareDashboard = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (page.permissions.admin) {
|
if (page.permissions.admin) {
|
||||||
|
const itemServerStats = document.getElementById('itemServerStats')
|
||||||
|
itemServerStats.removeAttribute('disabled')
|
||||||
|
itemServerStats.addEventListener('click', function () {
|
||||||
|
page.setActiveMenu(this)
|
||||||
|
page.getServerStats()
|
||||||
|
})
|
||||||
|
|
||||||
const itemManageUsers = document.getElementById('itemManageUsers')
|
const itemManageUsers = document.getElementById('itemManageUsers')
|
||||||
itemManageUsers.removeAttribute('disabled')
|
itemManageUsers.removeAttribute('disabled')
|
||||||
itemManageUsers.addEventListener('click', function () {
|
itemManageUsers.addEventListener('click', function () {
|
||||||
@ -1968,6 +1975,96 @@ page.paginate = function (totalItems, itemsPerPage, currentPage) {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page.getServerStats = function (element) {
|
||||||
|
if (!page.permissions.admin)
|
||||||
|
return swal('An error occurred!', 'You can not do this!', 'error')
|
||||||
|
|
||||||
|
const url = 'api/stats'
|
||||||
|
axios.get(url).then(function (response) {
|
||||||
|
if (response.data.success === false)
|
||||||
|
if (response.data.description === 'No token provided') {
|
||||||
|
return page.verifyToken(page.token)
|
||||||
|
} else {
|
||||||
|
return swal('An error occurred!', response.data.description, 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
const system = response.data.system
|
||||||
|
let systemRows = ''
|
||||||
|
for (const s of Object.keys(system)) {
|
||||||
|
let value
|
||||||
|
if (s === 'memory') {
|
||||||
|
const mem = system[s]
|
||||||
|
value = `${page.getPrettyBytes(mem.used)} / ${page.getPrettyBytes(mem.total)} (${Math.round((mem.used / mem.total) * 100)}%)`
|
||||||
|
} else if (s === 'memory usage') {
|
||||||
|
value = page.getPrettyBytes(system[s])
|
||||||
|
} else {
|
||||||
|
value = system[s].toLocaleString()
|
||||||
|
}
|
||||||
|
systemRows += `
|
||||||
|
<tr>
|
||||||
|
<th>${s.toUpperCase()}</th>
|
||||||
|
<td>${value}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const types = response.data.stats.uploads.types
|
||||||
|
let typesRows = ''
|
||||||
|
for (const t of Object.keys(types))
|
||||||
|
typesRows += `
|
||||||
|
<tr>
|
||||||
|
<th class="cell-indent">${t.toUpperCase()}</th>
|
||||||
|
<td>${types[t].toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
|
||||||
|
const permissions = response.data.stats.users.permissions
|
||||||
|
let permissionsRows = ''
|
||||||
|
for (const p of Object.keys(permissions))
|
||||||
|
permissionsRows += `
|
||||||
|
<tr>
|
||||||
|
<th class="cell-indent">${p.toUpperCase()}</th>
|
||||||
|
<td>${permissions[p].toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
|
||||||
|
page.dom.innerHTML = `
|
||||||
|
<h2 class="subtitle">Statistics</h2>
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<tbody>
|
||||||
|
${systemRows}
|
||||||
|
<tr>
|
||||||
|
<th>DISK USAGE</th>
|
||||||
|
<td>${page.getPrettyBytes(response.data.stats.uploads.size)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="cell-indent">IN BYTES</th>
|
||||||
|
<td>${response.data.stats.uploads.size.toLocaleString()} B</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>TOTAL UPLOADS</th>
|
||||||
|
<td>${response.data.stats.uploads.count.toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
${typesRows}
|
||||||
|
<tr>
|
||||||
|
<th>TOTAL USERS</th>
|
||||||
|
<td>${response.data.stats.users.count.toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
${permissionsRows}
|
||||||
|
<tr>
|
||||||
|
<th class="cell-indent">DISABLED</th>
|
||||||
|
<td>${response.data.stats.users.disabled.toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
page.fadeIn()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
page.getPrettyDate = function (date) {
|
page.getPrettyDate = function (date) {
|
||||||
return date.getFullYear() + '-' +
|
return date.getFullYear() + '-' +
|
||||||
(date.getMonth() < 9 ? '0' : '') + // month's index starts from zero
|
(date.getMonth() < 9 ? '0' : '') + // month's index starts from zero
|
||||||
|
@ -4,6 +4,7 @@ const uploadController = require('./../controllers/uploadController')
|
|||||||
const albumsController = require('./../controllers/albumsController')
|
const albumsController = require('./../controllers/albumsController')
|
||||||
const tokenController = require('./../controllers/tokenController')
|
const tokenController = require('./../controllers/tokenController')
|
||||||
const authController = require('./../controllers/authController')
|
const authController = require('./../controllers/authController')
|
||||||
|
const utilsController = require('./../controllers/utilsController')
|
||||||
|
|
||||||
routes.get('/check', (req, res, next) => {
|
routes.get('/check', (req, res, next) => {
|
||||||
return res.json({
|
return res.json({
|
||||||
@ -45,5 +46,6 @@ routes.get('/users', (req, res, next) => authController.listUsers(req, res, next
|
|||||||
routes.get('/users/:page', (req, res, next) => authController.listUsers(req, res, next))
|
routes.get('/users/:page', (req, res, next) => authController.listUsers(req, res, next))
|
||||||
routes.post('/users/edit', (req, res, next) => authController.editUser(req, res, next))
|
routes.post('/users/edit', (req, res, next) => authController.editUser(req, res, next))
|
||||||
routes.post('/users/disable', (req, res, next) => authController.disableUser(req, res, next))
|
routes.post('/users/disable', (req, res, next) => authController.disableUser(req, res, next))
|
||||||
|
routes.get('/stats', (req, res, next) => utilsController.stats(req, res, next))
|
||||||
|
|
||||||
module.exports = routes
|
module.exports = routes
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
v3: CSS and JS files (libs such as bulma, lazyload, etc).
|
v3: CSS and JS files (libs such as bulma, lazyload, etc).
|
||||||
v4: Renders in /public/render/* directories (to be used by render.js).
|
v4: Renders in /public/render/* directories (to be used by render.js).
|
||||||
#}
|
#}
|
||||||
{% set v1 = "dplQUZqTnf" %}
|
{% set v1 = "xz10E2pAFd" %}
|
||||||
{% set v2 = "hiboQUzAzp" %}
|
{% set v2 = "hiboQUzAzp" %}
|
||||||
{% set v3 = "hiboQUzAzp" %}
|
{% set v3 = "hiboQUzAzp" %}
|
||||||
{% set v4 = "dplQUZqTnf" %}
|
{% set v4 = "dplQUZqTnf" %}
|
||||||
|
@ -74,6 +74,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<p class="menu-label">Administration</p>
|
<p class="menu-label">Administration</p>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
|
<li>
|
||||||
|
<a id="itemServerStats" disabled>Statistics</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="itemManageUploads" disabled>Manage uploads</a>
|
<a id="itemManageUploads" disabled>Manage uploads</a>
|
||||||
</li>
|
</li>
|
||||||
|
Loading…
Reference in New Issue
Block a user