feat: configurable uploads/albums/users per page

please check sample.config.js for new options

if missing from config, defaults to 25 per page (old defaults)
This commit is contained in:
Bobby Wibowo 2022-07-31 15:51:32 +07:00
parent a6e1943655
commit 285e79c5a7
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
6 changed files with 146 additions and 83 deletions

View File

@ -655,6 +655,15 @@ module.exports = {
}
},
/*
Dashboard config.
*/
dashboard: {
uploadsPerPage: 24,
albumsPerPage: 10,
usersPerPage: 10
},
/*
Cloudflare support.
*/

View File

@ -20,8 +20,14 @@ const self = {
onHold: new Set() // temporarily held random album identifiers
}
/** Preferences */
const homeDomain = utils.conf.homeDomain || utils.conf.domain
const albumsPerPage = config.dashboard
? Math.max(Math.min(config.dashboard.albumsPerPage || 0, 100), 1)
: 25
const zipMaxTotalSize = parseInt(config.cloudflare.zipMaxTotalSize)
const zipMaxTotalSizeBytes = zipMaxTotalSize * 1e6
const zipOptions = config.uploads.jsZipOptions
@ -112,41 +118,48 @@ self.list = async (req, res) => {
}
}
// Base result object
const result = { success: true, albums: [], albumsPerPage, count: 0, homeDomain }
// Query albums count for pagination
const count = await utils.db.table('albums')
result.count = await utils.db.table('albums')
.where(filter)
.count('id as count')
.then(rows => rows[0].count)
if (!count) {
return res.json({ success: true, albums: [], count })
if (!result.count) {
return res.json(result)
}
const fields = ['id', 'name']
let albums
if (simple) {
albums = await utils.db.table('albums')
result.albums = await utils.db.table('albums')
.where(filter)
.select(fields)
return res.json({ success: true, albums, count })
} else {
let offset = req.path_parameters && Number(req.path_parameters.page)
if (isNaN(offset)) offset = 0
else if (offset < 0) offset = Math.max(0, Math.ceil(count / 25) + offset)
fields.push('identifier', 'enabled', 'timestamp', 'editedAt', 'zipGeneratedAt', 'download', 'public', 'description')
if (all) fields.push('userid')
albums = await utils.db.table('albums')
.where(filter)
.limit(25)
.offset(25 * offset)
.select(fields)
return res.json(result)
}
let offset = req.path_parameters && Number(req.path_parameters.page)
if (isNaN(offset)) {
offset = 0
} else if (offset < 0) {
offset = Math.max(0, Math.ceil(result.count / albumsPerPage) + offset)
}
fields.push('identifier', 'enabled', 'timestamp', 'editedAt', 'zipGeneratedAt', 'download', 'public', 'description')
if (all) {
fields.push('userid')
}
result.albums = await utils.db.table('albums')
.where(filter)
.limit(albumsPerPage)
.offset(albumsPerPage * offset)
.select(fields)
const albumids = {}
for (const album of albums) {
for (const album of result.albums) {
album.download = album.download !== 0
album.public = album.public !== 0
album.uploads = 0
@ -171,7 +184,7 @@ self.list = async (req, res) => {
}
}
await Promise.all(albums.map(album => getAlbumZipSize(album)))
await Promise.all(result.albums.map(album => getAlbumZipSize(album)))
const uploads = await utils.db.table('files')
.whereIn('albumid', Object.keys(albumids))
@ -186,19 +199,17 @@ self.list = async (req, res) => {
// If we are not listing all albums, send response
if (!all) {
return res.json({ success: true, albums, count, homeDomain })
return res.json(result)
}
// Otherwise proceed to querying usernames
const userids = albums
const userids = result.albums
.map(album => album.userid)
.filter((v, i, a) => {
return v !== null && v !== undefined && v !== '' && a.indexOf(v) === i
})
.filter(utils.filterUniquifySqlArray)
// If there are no albums attached to a registered user, send response
if (!userids.length) {
return res.json({ success: true, albums, count, homeDomain })
return res.json(result)
}
// Query usernames of user IDs from currently selected files
@ -206,12 +217,13 @@ self.list = async (req, res) => {
.whereIn('id', userids)
.select('id', 'username')
const users = {}
result.users = {}
for (const user of usersTable) {
users[user.id] = user.username
result.users[user.id] = user.username
}
return res.json({ success: true, albums, count, users, homeDomain })
return res.json(result)
}
self.create = async (req, res) => {

View File

@ -26,9 +26,15 @@ const self = {
}
}
/** Preferences */
// https://github.com/kelektiv/node.bcrypt.js/tree/v5.0.1#a-note-on-rounds
const saltRounds = 10
const usersPerPage = config.dashboard
? Math.max(Math.min(config.dashboard.usersPerPage || 0, 100), 1)
: 25
self.verify = async (req, res) => {
utils.assertRequestType(req, 'application/json')
@ -360,24 +366,30 @@ self.listUsers = async (req, res) => {
const isadmin = perms.is(user, 'admin')
if (!isadmin) throw new ClientError('', { statusCode: 403 })
const count = await utils.db.table('users')
// Base result object
const result = { success: true, users: [], usersPerPage, count: 0 }
result.count = await utils.db.table('users')
.count('id as count')
.then(rows => rows[0].count)
if (!count) {
return res.json({ success: true, users: [], count })
if (!result.count) {
return res.json(result)
}
let offset = req.path_parameters && Number(req.path_parameters.page)
if (isNaN(offset)) offset = 0
else if (offset < 0) offset = Math.max(0, Math.ceil(count / 25) + offset)
if (isNaN(offset)) {
offset = 0
} else if (offset < 0) {
offset = Math.max(0, Math.ceil(result.count / usersPerPage) + offset)
}
const users = await utils.db.table('users')
.limit(25)
.offset(25 * offset)
result.users = await utils.db.table('users')
.limit(usersPerPage)
.offset(usersPerPage * offset)
.select('id', 'username', 'enabled', 'timestamp', 'permission', 'registration')
const pointers = {}
for (const user of users) {
for (const user of result.users) {
user.groups = perms.mapPermissions(user)
delete user.permission
user.uploads = 0
@ -394,7 +406,7 @@ self.listUsers = async (req, res) => {
pointers[upload.userid].usage += parseInt(upload.size)
}
return res.json({ success: true, users, count })
return res.json(result)
}
module.exports = self

View File

@ -62,6 +62,10 @@ const enableHashing = config.uploads.hash === undefined
const queryDatabaseForIdentifierMatch = config.uploads.queryDatabaseForIdentifierMatch ||
config.uploads.queryDbForFileCollisions // old config name for identical behavior
const uploadsPerPage = config.dashboard
? Math.max(Math.min(config.dashboard.uploadsPerPage || 0, 100), 1)
: 25
/** Chunks helper class & function **/
class ChunksData {
@ -1686,18 +1690,24 @@ self.list = async (req, res) => {
})
}
// Base result object
const result = { success: true, files: [], uploadsPerPage, count: 0, basedomain }
// Query uploads count for pagination
const count = await utils.db.table('files')
result.count = await utils.db.table('files')
.where(filter)
.count('id as count')
.then(rows => rows[0].count)
if (!count) {
return res.json({ success: true, files: [], count })
if (!result.count) {
return res.json(result)
}
let offset = req.path_parameters && Number(req.path_parameters.page)
if (isNaN(offset)) offset = 0
else if (offset < 0) offset = Math.max(0, Math.ceil(count / 25) + offset)
if (isNaN(offset)) {
offset = 0
} else if (offset < 0) {
offset = Math.max(0, Math.ceil(result.count / uploadsPerPage) + offset)
}
const columns = ['id', 'name', 'original', 'userid', 'size', 'timestamp']
if (utils.retentions.enabled) columns.push('expirydate')
@ -1724,33 +1734,33 @@ self.list = async (req, res) => {
orderByRaw = '`id` desc'
}
const files = await utils.db.table('files')
result.files = await utils.db.table('files')
.where(filter)
.orderByRaw(orderByRaw)
.limit(25)
.offset(25 * offset)
.limit(uploadsPerPage)
.offset(uploadsPerPage * offset)
.select(columns)
if (!files.length) {
return res.json({ success: true, files, count, basedomain })
if (!result.files.length) {
return res.json(result)
}
for (const file of files) {
for (const file of result.files) {
file.extname = utils.extname(file.name)
if (utils.mayGenerateThumb(file.extname)) {
file.thumb = `thumbs/${file.name.slice(0, -file.extname.length)}.png`
}
}
result.albums = {}
// If we queried albumid, query album names
let albums = {}
if (columns.includes('albumid')) {
const albumids = files
const albumids = result.files
.map(file => file.albumid)
.filter((v, i, a) => {
return v !== null && v !== undefined && v !== '' && a.indexOf(v) === i
})
albums = await utils.db.table('albums')
.filter(utils.filterUniquifySqlArray)
result.albums = await utils.db.table('albums')
.whereIn('id', albumids)
.where('enabled', 1)
.select('id', 'name')
@ -1766,21 +1776,18 @@ self.list = async (req, res) => {
// If we are not listing all uploads, send response
if (!all) {
return res.json({ success: true, files, count, albums, basedomain })
return res.json(result)
}
// Otherwise proceed to querying usernames
let usersTable = filterObj.uploaders
if (!usersTable.length) {
const userids = files
const userids = result.files
.map(file => file.userid)
.filter((v, i, a) => {
return v !== null && v !== undefined && v !== '' && a.indexOf(v) === i
})
.filter(utils.filterUniquifySqlArray)
// If there are no uploads attached to a registered user, send response
if (!userids.length) {
return res.json({ success: true, files, count, albums, basedomain })
return res.json(result)
}
// Query usernames of user IDs from currently selected files
@ -1789,12 +1796,13 @@ self.list = async (req, res) => {
.select('id', 'username')
}
const users = {}
result.users = {}
for (const user of usersTable) {
users[user.id] = user.username
result.users[user.id] = user.username
}
return res.json({ success: true, files, count, users, albums, basedomain })
return res.json(result)
}
/** Get file info */

View File

@ -386,6 +386,13 @@ self.mask = string => {
}
}
self.filterUniquifySqlArray = (value, index, array) => {
return value !== null &&
value !== undefined &&
value !== '' &&
array.indexOf(value) === index
}
self.assertRequestType = (req, type) => {
if (!req.is(type)) {
throw new ClientError(`Request Content-Type must be ${type}.`)

View File

@ -545,7 +545,8 @@ page.getUploads = (params = {}) => {
}
}
const pages = Math.ceil(response.data.count / 25)
const uploadsPerPage = response.data.uploadsPerPage || 25
const pages = Math.ceil(response.data.count / uploadsPerPage)
const files = response.data.files
if (params.pageNum && (files.length === 0)) {
page.updateTrigger(params.trigger)
@ -564,8 +565,10 @@ page.getUploads = (params = {}) => {
const users = response.data.users
const basedomain = response.data.basedomain
if (params.pageNum < 0) params.pageNum = Math.max(0, pages + params.pageNum)
const pagination = page.paginate(response.data.count, 25, params.pageNum)
if (params.pageNum < 0) {
params.pageNum = Math.max(0, pages + params.pageNum)
}
const pagination = page.paginate(response.data.count, uploadsPerPage, params.pageNum)
const filter = `
<div class="column">
@ -671,7 +674,7 @@ page.getUploads = (params = {}) => {
.replace(/(data-action="page-ellipsis")/g, `$1 data-jumpid="${bottomJumpId}"`)
// Whether there are any unselected items
let unselected = false
let unselected = true
const showOriginalNames = page.views[page.currentView].originalNames
const hasExpiryDateColumn = files.some(file => typeof file.expirydate !== 'undefined')
@ -720,7 +723,9 @@ page.getUploads = (params = {}) => {
// Update selected status
files[i].selected = page.selected[page.currentView].includes(files[i].id)
if (!files[i].selected) unselected = true
if (files[i].selected) {
unselected = false
}
// Appendix (display album or user)
if (params.all) {
@ -888,7 +893,7 @@ page.getUploads = (params = {}) => {
}
const selectAll = document.querySelector('#selectAll')
if (selectAll && !unselected && files.length) {
if (selectAll && !unselected) {
selectAll.checked = true
selectAll.title = 'Unselect all'
}
@ -1615,7 +1620,8 @@ page.getAlbums = (params = {}) => {
}
}
const pages = Math.ceil(response.data.count / 25)
const albumsPerPage = response.data.albumsPerPage || 25
const pages = Math.ceil(response.data.count / albumsPerPage)
const albums = response.data.albums
if (params.pageNum && (albums.length === 0)) {
page.updateTrigger(params.trigger)
@ -1633,8 +1639,10 @@ page.getAlbums = (params = {}) => {
const users = response.data.users
const homeDomain = response.data.homeDomain || window.location.origin
if (params.pageNum < 0) params.pageNum = Math.max(0, pages + params.pageNum)
const pagination = page.paginate(response.data.count, 25, params.pageNum)
if (params.pageNum < 0) {
params.pageNum = Math.max(0, pages + params.pageNum)
}
const pagination = page.paginate(response.data.count, albumsPerPage, params.pageNum)
const filter = `
<div class="column">
@ -1722,7 +1730,7 @@ page.getAlbums = (params = {}) => {
.replace(/(data-action="page-ellipsis")/g, `$1 data-jumpid="${bottomJumpId}"`)
// Whether there are any unselected items
let unselected = false
let unselected = true
const createNewAlbum = `
<h2 class="subtitle">Create new album</h2>
@ -1793,7 +1801,9 @@ page.getAlbums = (params = {}) => {
const albumUrl = homeDomain + albumUrlText
const selected = page.selected[page.currentView].includes(album.id)
if (!selected) unselected = true
if (selected) {
unselected = false
}
// Prettify
album.hasZip = album.zipSize !== null
@ -2386,7 +2396,8 @@ page.getUsers = (params = {}) => {
}
}
const pages = Math.ceil(response.data.count / 25)
const usersPerPage = response.data.usersPerPage || 25
const pages = Math.ceil(response.data.count / usersPerPage)
const users = response.data.users
if (params.pageNum && (users.length === 0)) {
page.updateTrigger(params.trigger)
@ -2401,8 +2412,10 @@ page.getUsers = (params = {}) => {
page.currentView = 'users'
page.cache = {}
if (params.pageNum < 0) params.pageNum = Math.max(0, pages + params.pageNum)
const pagination = page.paginate(response.data.count, 25, params.pageNum)
if (params.pageNum < 0) {
params.pageNum = Math.max(0, pages + params.pageNum)
}
const pagination = page.paginate(response.data.count, usersPerPage, params.pageNum)
const filter = `
<div class="column">
@ -2494,7 +2507,7 @@ page.getUsers = (params = {}) => {
.replace(/(data-action="page-ellipsis")/g, `$1 data-jumpid="${bottomJumpId}"`)
// Whether there are any unselected items
let unselected = false
let unselected = true
page.dom.innerHTML = `
${pagination}
@ -2528,7 +2541,9 @@ page.getUsers = (params = {}) => {
for (let i = 0; i < users.length; i++) {
const user = users[i]
const selected = page.selected[page.currentView].includes(user.id)
if (!selected) unselected = true
if (selected) {
unselected = false
}
let displayGroup = null
const groups = Object.keys(user.groups)