Added pagination to uploads and users list.
With that, /api/uploads and /api/users API routes will now add "count"
property to their response object.

Enabled Delete user button in users list.
With that also added /api/users/disable API route.
As usual, you can only disable users whose usergroup is lower than
your own.

Click event will no longer trigger on "disabled" elements (basically any
elements with "disabled" attribute).

Changed all arrow functions into regular functions in public JS files
(there were only a few that I somehow missed).

Bumped v1 version string.
This commit is contained in:
Bobby Wibowo 2019-01-02 02:39:08 +07:00
parent c0d09c395c
commit 31a6940ab4
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
7 changed files with 153 additions and 64 deletions

View File

@ -159,23 +159,29 @@ authController.editUser = async (req, res, next) => {
else if (target.username === 'root')
return res.json({ success: false, description: 'Root user may not be edited.' })
const username = `${req.body.username}`
if (username.length < 4 || username.length > 32)
return res.json({ success: false, description: 'Username must have 4-32 characters.' })
const update = {}
let permission = req.body.group ? perms.permissions[req.body.group] : target.permission
if (typeof permission !== 'number' || permission < 0) permission = target.permission
if (req.body.username !== undefined) {
update.username = `${req.body.username}`
if (update.username.length < 4 || update.username.length > 32)
return res.json({ success: false, description: 'Username must have 4-32 characters.' })
}
if (req.body.enabled !== undefined)
update.enabled = Boolean(req.body.enabled)
if (req.body.group !== undefined) {
update.permission = perms.permissions[req.body.group] || target.permission
if (typeof update.permission !== 'number' || update.permission < 0)
update.permission = target.permission
}
await db.table('users')
.where('id', id)
.update({
username,
enabled: Boolean(req.body.enabled),
permission
})
.update(update)
if (!req.body.resetPassword)
return res.json({ success: true, username })
return res.json({ success: true, update })
const password = randomstring.generate(16)
bcrypt.hash(password, 10, async (error, hash) => {
@ -188,10 +194,19 @@ authController.editUser = async (req, res, next) => {
.where('id', id)
.update('password', hash)
return res.json({ success: true, password })
return res.json({ success: true, update, password })
})
}
authController.disableUser = async (req, res, next) => {
const body = {
id: req.body.id,
enabled: false
}
req.body = body
return authController.editUser(req, res, next)
}
authController.listUsers = async (req, res, next) => {
const user = await utils.authorize(req, res)
if (!user) return
@ -199,17 +214,19 @@ authController.listUsers = async (req, res, next) => {
const isadmin = perms.is(user, 'admin')
if (!isadmin) return res.status(403).end()
const count = await db.table('users')
.count('id as count')
.then(rows => rows[0].count)
if (!count) return res.json({ success: true, users: [], count })
let offset = req.params.page
if (offset === undefined) offset = 0
const users = await db.table('users')
// .orderBy('id', 'DESC')
.limit(25)
.offset(25 * offset)
.select('id', 'username', 'enabled', 'fileLength', 'permission')
if (!users.length) return res.json({ success: true, users })
const userids = []
for (const user of users) {
@ -242,7 +259,7 @@ authController.listUsers = async (req, res, next) => {
user.diskUsage = maps[user.id].size
}
return res.json({ success: true, users })
return res.json({ success: true, users, count })
}
module.exports = authController

View File

@ -660,9 +660,11 @@ uploadsController.processFilesForDisplay = async (req, res, files, existingFiles
uploadsController.delete = async (req, res) => {
const id = parseInt(req.body.id)
req.body.field = 'id'
req.body.values = isNaN(id) ? undefined : [id]
delete req.body.id
const body = {
field: 'id',
values: isNaN(id) ? undefined : [id]
}
req.body = body
return uploadsController.bulkDelete(req, res)
}
@ -687,25 +689,31 @@ uploadsController.list = async (req, res) => {
const user = await utils.authorize(req, res)
if (!user) return
let offset = req.params.page
if (offset === undefined) offset = 0
// Headers is string-only, this seem to be the safest and lightest
const all = req.headers.all === '1'
const ismoderator = perms.is(user, 'moderator')
if (all && !ismoderator) return res.status(403).end()
function filter () {
if (req.params.id === undefined)
this.where('id', '<>', '')
else
this.where('albumid', req.params.id)
if (!all || !ismoderator)
this.where('userid', user.id)
}
const count = await db.table('files')
.where(filter)
.count('id as count')
.then(rows => rows[0].count)
if (!count) return res.json({ success: true, files: [], count })
let offset = req.params.page
if (offset === undefined) offset = 0
const files = await db.table('files')
.where(function () {
if (req.params.id === undefined)
this.where('id', '<>', '')
else
this.where('albumid', req.params.id)
})
.where(function () {
if (!all || !ismoderator)
this.where('userid', user.id)
})
.where(filter)
.orderBy('id', 'DESC')
.limit(25)
.offset(25 * offset)
@ -741,10 +749,10 @@ uploadsController.list = async (req, res) => {
}
// If we are a normal user, send response
if (!ismoderator) return res.json({ success: true, files })
if (!ismoderator) return res.json({ success: true, files, count })
// If we are a moderator but there are no uploads attached to a user, send response
if (userids.length === 0) return res.json({ success: true, files })
if (userids.length === 0) return res.json({ success: true, files, count })
const users = await db.table('users').whereIn('id', userids)
for (const dbUser of users)
@ -752,7 +760,7 @@ uploadsController.list = async (req, res) => {
if (file.userid === dbUser.id)
file.username = dbUser.username
return res.json({ success: true, files })
return res.json({ success: true, files, count })
}
module.exports = uploadsController

View File

@ -142,3 +142,14 @@ hr {
opacity: 1;
}
}
.pagination-link.is-current {
background-color: #3794d2;
border-color: #3794d2;
}
.pagination-link:hover,
.pagination-next:hover,
.pagination-previous:hover {
border-color: #3794d2;
}

View File

@ -7,7 +7,7 @@ const page = {
byteUnits: ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
}
page.getPrettyBytes = num => {
page.getPrettyBytes = function (num) {
// MIT License
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)

View File

@ -196,12 +196,15 @@ page.domClick = function (event) {
// Skip elements that have no action data
if (!element.dataset || !element.dataset.action) return
// Skip disabled elements
if (element.hasAttribute('disabled')) return
event.stopPropagation() // maybe necessary
const id = page.getItemID(element)
const action = element.dataset.action
// Handle pagination actions
if (['page-prev', 'page-next'].includes(action)) {
if (['page-prev', 'page-next', 'page-goto'].includes(action)) {
const views = {}
let func = null
@ -223,6 +226,9 @@ page.domClick = function (event) {
case 'page-next':
views.pageNum = page.views[page.currentView].pageNum + 1
return func(views, element)
case 'page-goto':
views.pageNum = parseInt(element.dataset.goto)
return func(views, element)
}
}
@ -259,6 +265,8 @@ page.domClick = function (event) {
return page.getNewToken(element)
case 'edit-user':
return page.editUser(id)
case 'disable-user':
return page.disableUser(id)
}
}
@ -310,12 +318,7 @@ page.getUploads = function ({ album, pageNum, all } = {}, element) {
page.currentView = 'uploads'
page.cache.uploads = {}
const pagination = `
<nav class="pagination is-centered">
<a class="button pagination-previous" data-action="page-prev">Previous</a>
<a class="button pagination-next" data-action="page-next">Next page</a>
</nav>
`
const pagination = page.paginate(response.data.count, 25, pageNum)
const controls = `
<div class="columns">
@ -362,6 +365,7 @@ page.getUploads = function ({ album, pageNum, all } = {}, element) {
${controls}
<div id="table" class="columns is-multiline is-mobile is-centered">
</div>
<hr>
${pagination}
`
page.fadeIn()
@ -1531,7 +1535,7 @@ page.setActiveMenu = function (activeItem) {
activeItem.classList.add('is-active')
}
page.getPrettyDate = date => {
page.getPrettyDate = function (date) {
return date.getFullYear() + '-' +
(date.getMonth() < 9 ? '0' : '') + // month's index starts from zero
(date.getMonth() + 1) + '-' +
@ -1545,7 +1549,7 @@ page.getPrettyDate = date => {
date.getSeconds()
}
page.getPrettyBytes = num => {
page.getPrettyBytes = function (num) {
// MIT License
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
@ -1562,7 +1566,7 @@ page.getPrettyBytes = num => {
return `${neg ? '-' : ''}${numStr} ${unit}`
}
page.getUsers = ({ pageNum } = {}, element) => {
page.getUsers = function ({ pageNum } = {}, element) {
if (element) page.isLoading(element, true)
if (pageNum === undefined) pageNum = 0
@ -1587,12 +1591,7 @@ page.getUsers = ({ pageNum } = {}, element) => {
page.currentView = 'users'
page.cache.users = {}
const pagination = `
<nav class="pagination is-centered">
<a class="button pagination-previous" data-action="page-prev">Previous</a>
<a class="button pagination-next" data-action="page-next">Next page</a>
</nav>
`
const pagination = page.paginate(response.data.count, 25, pageNum)
const controls = `
<div class="columns">
@ -1686,7 +1685,7 @@ page.getUsers = ({ pageNum } = {}, element) => {
<i class="icon-pencil-1"></i>
</span>
</a>
<a class="button is-small is-warning" title="Disable user (WIP)" data-action="disable-user" disabled>
<a class="button is-small is-warning" title="Disable user" data-action="disable-user" ${enabled ? '' : 'disabled'}>
<span class="icon">
<i class="icon-hammer"></i>
</span>
@ -1721,7 +1720,7 @@ page.editUser = function (id) {
const user = page.cache.users[id]
if (!user) return
const groupOptions = Object.keys(page.permissions).map((g, i, a) => {
const groupOptions = Object.keys(page.permissions).map(function (g, i, a) {
const selected = g === user.displayGroup
const disabled = !(a[i + 1] && page.permissions[a[i + 1]])
return `<option value="${g}"${selected ? ' selected' : ''}${disabled ? ' disabled' : ''}>${g}</option>`
@ -1773,8 +1772,8 @@ page.editUser = function (id) {
closeModal: false
}
}
}).then(function (value) {
if (!value) return
}).then(function (proceed) {
if (!proceed) return
axios.post('api/users/edit', {
id,
@ -1803,8 +1802,8 @@ page.editUser = function (id) {
icon: 'success',
content: div
})
} else if (response.data.name !== user.name) {
swal('Success!', `${user.username} was renamed into: ${response.data.name}.`, 'success')
} else if (response.data.update && response.data.update.username !== user.username) {
swal('Success!', `${user.username} was renamed into: ${response.data.update.name}.`, 'success')
} else {
swal('Success!', 'The user was edited!', 'success')
}
@ -1817,12 +1816,17 @@ page.editUser = function (id) {
})
}
/*
page.disableUser = function (id) {
const user = page.cache.users[id]
if (!user || !user.enabled) return
const content = document.createElement('div')
content.innerHTML = `You will be disabling a user with the username <b>${page.cache.users[id].username}</b>!`
swal({
title: 'Are you sure?',
text: `You will be disabling a user with the username <b>${page.cache.users.id.username}!</b>`,
icon: 'warning',
content,
dangerMode: true,
buttons: {
cancel: true,
@ -1837,12 +1841,11 @@ page.disableUser = function (id) {
axios.post('api/users/disable', { id }).then(function (response) {
if (!response) return
if (response.data.success === false) {
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')
}
swal('Success!', 'The user has been disabled.', 'success')
page.getUsers(page.views.users)
@ -1852,7 +1855,56 @@ page.disableUser = function (id) {
})
})
}
*/
page.paginate = function (totalItems, itemsPerPage, currentPage) {
// Roughly based on https://github.com/mayuska/pagination/blob/master/index.js
currentPage = currentPage + 1
const step = 3
const numPages = Math.ceil(totalItems / itemsPerPage)
let template = ''
const elementsToShow = step * 2
const add = {
pageNum (start, end) {
for (let i = start; i <= end; ++i)
template += `<li><a class="pagination-link ${i === currentPage ? ' is-current' : ''}" aria-label="Goto page ${i}" data-action="page-goto" data-goto="${i - 1}">${i}</a></li>`
},
startDots () {
template += `
<li><a class="pagination-link" aria-label="Goto page 1" data-action="page-goto" data-goto="0">1</a></li>
<li><span class="pagination-ellipsis">&hellip;</span></li>
`
},
endDots () {
template += `
<li><span class="pagination-ellipsis">&hellip;</span></li>
<li><a class="pagination-link" aria-label="Goto page ${numPages}" data-action="page-goto" data-goto="${numPages - 1}">${numPages}</a></li>
`
}
}
if (elementsToShow >= numPages) {
add.pageNum(1, numPages)
} else if (currentPage < elementsToShow) {
add.pageNum(1, elementsToShow)
add.endDots()
} else if (currentPage > numPages - elementsToShow) {
add.startDots()
add.pageNum(numPages - elementsToShow, numPages)
} else {
add.startDots()
add.pageNum(currentPage - step, currentPage, step)
add.endDots()
}
return `
<nav class="pagination is-centered is-small">
<a class="button pagination-previous" data-action="page-prev">Previous</a>
<a class="button pagination-next" data-action="page-next">Next page</a>
<ul class="pagination-list">${template}</ul>
</nav>
`
}
window.onload = function () {
// Add 'no-touch' class to non-touch devices

View File

@ -44,5 +44,6 @@ routes.post('/filelength/change', (req, res, next) => authController.changeFileL
routes.get('/users', (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/disable', (req, res, next) => authController.disableUser(req, res, next))
module.exports = routes

View File

@ -15,7 +15,7 @@
v2: Images and config files (manifest.json, browserconfig.xml, etc).
v3: CSS and JS files (libs such as bulma, lazyload, etc).
#}
{% set v1 = "9cK64TLE3Z" %}
{% set v1 = "DXJsv4Spfk" %}
{% set v2 = "Ii3JYKIhb0" %}
{% set v3 = "ll7yHY3b2b" %}